From 6e270c5d062e25a017bd1ff38739929c1d1a9b4c Mon Sep 17 00:00:00 2001 From: ABS <53243996+ABSllk@users.noreply.github.com> Date: Fri, 17 Apr 2026 00:18:45 +0800 Subject: [PATCH 1/3] fix(bitget): Use correct planType for Bitget futures stoploss cancellation --- freqtrade/exchange/bitget.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 9691f72f8..c769fc0c8 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -136,6 +136,15 @@ class Bitget(Exchange): return self._fetch_stop_order_fallback(order_id, pair) + def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict: + cancel_params = params.copy() if params else {} + cancel_params["stop"] = True + + if self.trading_mode != TradingMode.FUTURES: + return self.cancel_order(order_id, pair, cancel_params) + + return self.cancel_order(order_id, pair, {**cancel_params, "planType": "pos_loss"}) + @retrier def additional_exchange_init(self) -> None: """ From b467eff8bec55f6ad6a278c3af2890a16970b067 Mon Sep 17 00:00:00 2001 From: ABS <53243996+ABSllk@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:11:23 +0800 Subject: [PATCH 2/3] fix(bitget): add legacy fallback for futures stoploss cancel --- freqtrade/exchange/bitget.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 3be721cff..1119a6455 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -7,6 +7,7 @@ from freqtrade.constants import BuySell from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, TradingMode from freqtrade.exceptions import ( DDosProtection, + InvalidOrderException, OperationalException, RetryableOrderError, TemporaryError, @@ -149,7 +150,11 @@ class Bitget(Exchange): if self.trading_mode != TradingMode.FUTURES: return self.cancel_order(order_id, pair, cancel_params) - return self.cancel_order(order_id, pair, {**cancel_params, "planType": "pos_loss"}) + try: + return self.cancel_order(order_id, pair, {**cancel_params, "planType": "pos_loss"}) + except (InvalidOrderException, IndexError): + # Keep compatibility with stoploss orders created by older versions. + return self.cancel_order(order_id, pair, cancel_params) @retrier def additional_exchange_init(self) -> None: From af2e7414652bdba13fab24ede06d933e5dee33b9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Apr 2026 20:03:58 +0200 Subject: [PATCH 3/3] test: add test for bitget stoploss canceling --- tests/exchange/test_bitget.py | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_bitget.py b/tests/exchange/test_bitget.py index b4dafb0ae..575979aca 100644 --- a/tests/exchange/test_bitget.py +++ b/tests/exchange/test_bitget.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, PropertyMock import pytest from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode -from freqtrade.exceptions import OperationalException, RetryableOrderError +from freqtrade.exceptions import InvalidOrderException, OperationalException, RetryableOrderError from freqtrade.exchange.common import API_RETRY_COUNT from freqtrade.util import dt_now, dt_ts, dt_utc from tests.conftest import EXMS, get_patched_exchange @@ -77,6 +77,41 @@ def test_fetch_stoploss_order_bitget_exceptions(default_conf_usdt, mocker): ) +@pytest.mark.usefixtures("init_persistence") +def test_cancel_stoploss_order_bitget(default_conf_usdt, mocker): + default_conf_usdt["dry_run"] = False + api_mock = MagicMock() + + exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange="bitget") + + # Spot scenario + exchange.cancel_order = MagicMock(return_value={"id": "1234"}) + assert exchange.cancel_stoploss_order("1234", "ETH/USDT", {}) == {"id": "1234"} + assert exchange.cancel_order.call_count == 1 + exchange.cancel_order.assert_called_once_with("1234", "ETH/USDT", {"stop": True}) + + # Futures scenario + default_conf_usdt["trading_mode"] = TradingMode.FUTURES + default_conf_usdt["margin_mode"] = MarginMode.ISOLATED + exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange="bitget") + exchange.cancel_order = MagicMock(return_value={"id": "1234"}) + assert exchange.cancel_stoploss_order("1234", "ETH/USDT:USDT", {}) == {"id": "1234"} + assert exchange.cancel_order.call_count == 1 + exchange.cancel_order.assert_called_once_with( + "1234", "ETH/USDT:USDT", {"stop": True, "planType": "pos_loss"} + ) + + exchange.cancel_order = MagicMock( + side_effect=[InvalidOrderException("API error"), {"id": "1234"}] + ) + assert exchange.cancel_stoploss_order("1234", "ETH/USDT:USDT", {}) == {"id": "1234"} + assert exchange.cancel_order.call_count == 2 + exchange.cancel_order.assert_any_call( + "1234", "ETH/USDT:USDT", {"stop": True, "planType": "pos_loss"} + ) + exchange.cancel_order.assert_any_call("1234", "ETH/USDT:USDT", {"stop": True}) + + def test_bitget_ohlcv_candle_limit(mocker, default_conf_usdt): # This test is also a live test - so we're sure our limits are correct. api_mock = MagicMock()