diff --git a/freqtrade/exchange/krakenfutures.py b/freqtrade/exchange/krakenfutures.py index 02507a9f7..441cba75d 100644 --- a/freqtrade/exchange/krakenfutures.py +++ b/freqtrade/exchange/krakenfutures.py @@ -6,7 +6,7 @@ import logging from typing import Any from freqtrade.enums import MarginMode, PriceType, TradingMode -from freqtrade.exceptions import ExchangeError, RetryableOrderError +from freqtrade.exceptions import ExchangeError, RetryableOrderError, TemporaryError from freqtrade.exchange.exchange import Exchange from freqtrade.exchange.exchange_types import FtHas @@ -169,19 +169,33 @@ class Krakenfutures(Exchange): """ Kraken Futures fetchOrder is backed by /orders/status which only returns open orders or orders closed within the last 5 seconds. - Fall back to fetchClosedOrders for older orders. + Fall back to closed/canceled order endpoints for older orders. """ params = params or {} try: return super().fetch_order(order_id, pair, params=params) - except RetryableOrderError as err: - if not self.exchange_has("fetchClosedOrders"): - raise + except (RetryableOrderError, TemporaryError) as err: + order = self._fetch_order_from_closed_or_canceled(order_id, pair, params) + if order is not None: + return order + raise err + + def _fetch_order_from_closed_or_canceled( + self, order_id: str, pair: str, params: dict[str, Any] + ) -> dict[str, Any] | None: + if self.exchange_has("fetchClosedOrders"): orders = self._api.fetch_closed_orders(pair, params=params) for order in orders or []: if str(order.get("id")) == str(order_id): return self._order_contracts_to_amount(order) - raise err + + if self.exchange_has("fetchCanceledOrders"): + orders = self._api.fetch_canceled_orders(pair, params=params) + for order in orders or []: + if str(order.get("id")) == str(order_id): + return self._order_contracts_to_amount(order) + + return None def get_funding_fees(self, pair: str, amount: float, is_short: bool, open_date) -> float: """CCXT currently does not support Kraken Futures fetchFundingHistory.""" diff --git a/tests/exchange/test_krakenfutures.py b/tests/exchange/test_krakenfutures.py index adf1ee339..171b0ab0b 100644 --- a/tests/exchange/test_krakenfutures.py +++ b/tests/exchange/test_krakenfutures.py @@ -6,7 +6,7 @@ from copy import deepcopy from unittest.mock import MagicMock from freqtrade.enums import CandleType, MarginMode, TradingMode -from freqtrade.exceptions import RetryableOrderError +from freqtrade.exceptions import RetryableOrderError, TemporaryError from freqtrade.exchange.exchange import Exchange from freqtrade.exchange.krakenfutures import Krakenfutures from tests.conftest import EXMS, get_patched_exchange @@ -35,10 +35,12 @@ def test_krakenfutures_fetch_order_falls_back_to_closed_orders(mocker, default_c """Fallback to fetch_closed_orders when fetch_order can't find the order.""" ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures") + mocker.patch.object(Exchange, "fetch_order", side_effect=RetryableOrderError("not found")) mocker.patch.object( - Exchange, "fetch_order", side_effect=RetryableOrderError("not found") + ex, + "exchange_has", + side_effect=lambda endpoint: endpoint == "fetchClosedOrders", ) - mocker.patch.object(ex, "exchange_has", return_value=True) mocker.patch.object( ex._api, "fetch_closed_orders", @@ -50,6 +52,29 @@ def test_krakenfutures_fetch_order_falls_back_to_closed_orders(mocker, default_c assert res["id"] == "abc" +def test_krakenfutures_fetch_order_falls_back_to_canceled_orders(mocker, default_conf): + """Fallback to fetch_canceled_orders when closed orders don't contain the order.""" + ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures") + + mocker.patch.object( + Exchange, "fetch_order", side_effect=TemporaryError("UUID string too large") + ) + mocker.patch.object( + ex, + "exchange_has", + side_effect=lambda endpoint: endpoint == "fetchCanceledOrders", + ) + mocker.patch.object( + ex._api, + "fetch_canceled_orders", + return_value=[{"id": "def", "symbol": "BTC/USD:USD", "status": "canceled"}], + create=True, + ) + + res = ex.fetch_order("def", "BTC/USD:USD") + assert res["id"] == "def" + + def test_krakenfutures_create_stoploss_uses_trigger_price_type(mocker, default_conf): """Test create_stoploss uses triggerPrice, triggerSignal, and reduceOnly.""" api_mock = MagicMock()