diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index d3cb1ad83..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, @@ -142,6 +143,19 @@ 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) + + 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: """ 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()