From 0dfd7324bda3a370e95548fdb0fdb79792e5832e Mon Sep 17 00:00:00 2001 From: matstedt Date: Tue, 17 Feb 2026 20:33:27 +0100 Subject: [PATCH] fix: extract triggerPrice from priceTriggerOptions for stoploss orders CCXT's krakenfutures parse_order misses triggerPrice when the /orders/status endpoint nests it inside priceTriggerOptions. Override _order_contracts_to_amount to populate triggerPrice and stopPrice from info.order.priceTriggerOptions.triggerPrice. --- freqtrade/exchange/krakenfutures.py | 19 ++++++++ tests/exchange/test_krakenfutures.py | 69 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/freqtrade/exchange/krakenfutures.py b/freqtrade/exchange/krakenfutures.py index a774ba288..a3eee37c7 100644 --- a/freqtrade/exchange/krakenfutures.py +++ b/freqtrade/exchange/krakenfutures.py @@ -132,6 +132,25 @@ class Krakenfutures(Exchange): except (ValueError, TypeError): return None + def _order_contracts_to_amount(self, order: CcxtOrder) -> CcxtOrder: + """Normalize order and fix missing trigger price from CCXT parsing. + + CCXT's krakenfutures parse_order reads triggerPrice from the top level of + the order details, but the /orders/status endpoint nests it inside + priceTriggerOptions.triggerPrice. This extracts it so stopPrice/triggerPrice + are populated correctly for stoploss order handling. + """ + order = super()._order_contracts_to_amount(order) + if order.get("triggerPrice") is None and order.get("stopPrice") is None: + info = order.get("info", {}) + inner = info.get("order", {}) if isinstance(info, dict) else {} + opts = inner.get("priceTriggerOptions", {}) if isinstance(inner, dict) else {} + trigger = self._safe_float(opts.get("triggerPrice")) if isinstance(opts, dict) else None + if trigger is not None: + order["triggerPrice"] = trigger + order["stopPrice"] = trigger + return order + @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) def fetch_order( self, order_id: str, pair: str, params: dict[str, Any] | None = None diff --git a/tests/exchange/test_krakenfutures.py b/tests/exchange/test_krakenfutures.py index 00c4afd79..e62f33b4f 100644 --- a/tests/exchange/test_krakenfutures.py +++ b/tests/exchange/test_krakenfutures.py @@ -55,6 +55,75 @@ def test_krakenfutures_ohlcv_candle_limit_funding_rate(mocker, default_conf): assert ex.ohlcv_candle_limit("1h", candle_type=CandleType.FUNDING_RATE) == 700 +# --- _order_contracts_to_amount trigger price fix tests --- + + +def test_krakenfutures_order_contracts_fixes_missing_trigger_price(mocker, default_conf): + """Extract triggerPrice from info.order.priceTriggerOptions when CCXT misses it.""" + ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures") + order = { + "id": "abc", + "symbol": "BTC/USD:USD", + "triggerPrice": None, + "stopPrice": None, + "info": { + "order": { + "type": "TRIGGER_ORDER", + "priceTriggerOptions": { + "triggerPrice": 71641, + "triggerSignal": "LAST_PRICE", + }, + }, + "status": "TRIGGER_PLACED", + }, + } + result = ex._order_contracts_to_amount(order) + assert result["triggerPrice"] == 71641.0 + assert result["stopPrice"] == 71641.0 + + +def test_krakenfutures_order_contracts_preserves_existing_trigger_price(mocker, default_conf): + """Don't overwrite triggerPrice when CCXT already parsed it correctly.""" + ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures") + order = { + "id": "abc", + "symbol": "BTC/USD:USD", + "triggerPrice": 70000.0, + "stopPrice": 70000.0, + "info": { + "order": { + "priceTriggerOptions": { + "triggerPrice": 71641, + }, + }, + }, + } + result = ex._order_contracts_to_amount(order) + assert result["triggerPrice"] == 70000.0 + assert result["stopPrice"] == 70000.0 + + +def test_krakenfutures_order_contracts_no_trigger_options(mocker, default_conf): + """Regular (non-trigger) orders should pass through unchanged.""" + ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures") + order = { + "id": "abc", + "symbol": "BTC/USD:USD", + "triggerPrice": None, + "stopPrice": None, + "info": { + "order": { + "type": "lmt", + "orderId": "abc", + }, + "status": "placed", + }, + } + result = ex._order_contracts_to_amount(order) + assert result["triggerPrice"] is None + assert result["stopPrice"] is None + + # --- fetch_order fallback tests ---