krakenfutures: recompute average and cost from fills for terminal orders
This commit is contained in:
@@ -17,7 +17,7 @@ from freqtrade.exceptions import (
|
|||||||
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT, retrier
|
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT, retrier
|
||||||
from freqtrade.exchange.exchange import Exchange
|
from freqtrade.exchange.exchange import Exchange
|
||||||
from freqtrade.exchange.exchange_types import CcxtBalances, CcxtOrder, FtHas
|
from freqtrade.exchange.exchange_types import CcxtBalances, CcxtOrder, FtHas
|
||||||
from freqtrade.misc import safe_value_fallback, safe_value_nested
|
from freqtrade.misc import safe_value_nested
|
||||||
from freqtrade.util.datetime_helpers import dt_from_ts
|
from freqtrade.util.datetime_helpers import dt_from_ts
|
||||||
|
|
||||||
|
|
||||||
@@ -156,14 +156,13 @@ class Krakenfutures(Exchange):
|
|||||||
|
|
||||||
Kraken Futures' /orders/status endpoint does not include execution data,
|
Kraken Futures' /orders/status endpoint does not include execution data,
|
||||||
so CCXT sets price/average to the limitPrice (the order's limit, not the
|
so CCXT sets price/average to the limitPrice (the order's limit, not the
|
||||||
actual fill price). For closed/filled orders we fetch trades from /fills
|
actual fill price). For closed/filled orders we ALWAYS fetch trades and
|
||||||
and compute the VWAP average.
|
compute VWAP because CCXT's average field is unreliable.
|
||||||
|
|
||||||
|
See: https://github.com/ccxt/ccxt/issues/27979
|
||||||
"""
|
"""
|
||||||
if (
|
filled = self._safe_float(order.get("filled")) or 0.0
|
||||||
order.get("average") is None
|
if order.get("status") in ("canceled", "closed") and filled > 0:
|
||||||
and order.get("status") in ("canceled", "closed")
|
|
||||||
and safe_value_fallback(order, "filled", default_value=0) > 0
|
|
||||||
):
|
|
||||||
trades = self.get_trades_for_order(
|
trades = self.get_trades_for_order(
|
||||||
order["id"], order["symbol"], since=dt_from_ts(order["timestamp"])
|
order["id"], order["symbol"], since=dt_from_ts(order["timestamp"])
|
||||||
)
|
)
|
||||||
@@ -171,6 +170,9 @@ class Krakenfutures(Exchange):
|
|||||||
total_amount = sum(t["amount"] for t in trades)
|
total_amount = sum(t["amount"] for t in trades)
|
||||||
if total_amount:
|
if total_amount:
|
||||||
order["average"] = sum(t["price"] * t["amount"] for t in trades) / total_amount
|
order["average"] = sum(t["price"] * t["amount"] for t in trades) / total_amount
|
||||||
|
trade_costs = [t["cost"] for t in trades if t.get("cost") is not None]
|
||||||
|
if trade_costs:
|
||||||
|
order["cost"] = sum(trade_costs)
|
||||||
return order
|
return order
|
||||||
|
|
||||||
def get_trades_for_order(
|
def get_trades_for_order(
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ def test_krakenfutures_adjust_order_computes_average_from_trades(mocker, default
|
|||||||
|
|
||||||
result = ex._adjust_krakenfutures_order(order)
|
result = ex._adjust_krakenfutures_order(order)
|
||||||
assert result["average"] == pytest.approx(67850.0)
|
assert result["average"] == pytest.approx(67850.0)
|
||||||
|
assert result["cost"] == pytest.approx(27.14)
|
||||||
|
|
||||||
|
|
||||||
def test_krakenfutures_adjust_order_skips_open_orders(mocker, default_conf):
|
def test_krakenfutures_adjust_order_skips_open_orders(mocker, default_conf):
|
||||||
@@ -174,8 +175,27 @@ def test_krakenfutures_adjust_order_skips_open_orders(mocker, default_conf):
|
|||||||
trades_mock.assert_not_called()
|
trades_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_krakenfutures_adjust_order_preserves_existing_average(mocker, default_conf):
|
def test_krakenfutures_adjust_order_handles_none_filled(mocker, default_conf):
|
||||||
"""Don't overwrite average when already present."""
|
"""Don't crash or fetch trades when filled is None."""
|
||||||
|
ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures")
|
||||||
|
|
||||||
|
order = {
|
||||||
|
"id": "abc",
|
||||||
|
"symbol": "BTC/USD:USD",
|
||||||
|
"status": "closed",
|
||||||
|
"filled": None,
|
||||||
|
"average": None,
|
||||||
|
"timestamp": 1771354195241,
|
||||||
|
}
|
||||||
|
trades_mock = mocker.patch.object(ex, "get_trades_for_order")
|
||||||
|
|
||||||
|
result = ex._adjust_krakenfutures_order(order)
|
||||||
|
assert result["average"] is None
|
||||||
|
trades_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_krakenfutures_adjust_order_recomputes_existing_average(mocker, default_conf):
|
||||||
|
"""Recompute average from fills even when CCXT already provided one."""
|
||||||
ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures")
|
ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures")
|
||||||
|
|
||||||
order = {
|
order = {
|
||||||
@@ -186,11 +206,30 @@ def test_krakenfutures_adjust_order_preserves_existing_average(mocker, default_c
|
|||||||
"average": 67843.0,
|
"average": 67843.0,
|
||||||
"timestamp": 1771354195241,
|
"timestamp": 1771354195241,
|
||||||
}
|
}
|
||||||
trades_mock = mocker.patch.object(ex, "get_trades_for_order")
|
trades = [
|
||||||
|
{
|
||||||
|
"amount": 0.0002,
|
||||||
|
"price": 67800.0,
|
||||||
|
"cost": 13.56,
|
||||||
|
"takerOrMaker": "taker",
|
||||||
|
"symbol": "BTC/USD:USD",
|
||||||
|
"fee": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": 0.0002,
|
||||||
|
"price": 67900.0,
|
||||||
|
"cost": 13.58,
|
||||||
|
"takerOrMaker": "taker",
|
||||||
|
"symbol": "BTC/USD:USD",
|
||||||
|
"fee": None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
trades_mock = mocker.patch.object(ex, "get_trades_for_order", return_value=trades)
|
||||||
|
|
||||||
result = ex._adjust_krakenfutures_order(order)
|
result = ex._adjust_krakenfutures_order(order)
|
||||||
assert result["average"] == 67843.0
|
assert result["average"] == pytest.approx(67850.0)
|
||||||
trades_mock.assert_not_called()
|
assert result["cost"] == pytest.approx(27.14)
|
||||||
|
trades_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_krakenfutures_adjust_order_no_trades_found(mocker, default_conf):
|
def test_krakenfutures_adjust_order_no_trades_found(mocker, default_conf):
|
||||||
|
|||||||
Reference in New Issue
Block a user