From 4177809f7131fbe64b86169c7dd75e44b0500815 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Aug 2025 20:19:43 +0200 Subject: [PATCH] feat(bitget): stoploss order fetching attempts --- freqtrade/exchange/bitget.py | 67 +++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 89a549ad1..fa13f75a2 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -1,9 +1,18 @@ import logging from datetime import timedelta +import ccxt + from freqtrade.enums import CandleType +from freqtrade.exceptions import ( + DDosProtection, + OperationalException, + RetryableOrderError, + TemporaryError, +) from freqtrade.exchange import Exchange -from freqtrade.exchange.exchange_types import FtHas +from freqtrade.exchange.common import API_RETRY_COUNT, retrier +from freqtrade.exchange.exchange_types import CcxtOrder, FtHas from freqtrade.util.datetime_helpers import dt_now, dt_ts @@ -21,6 +30,10 @@ class Bitget(Exchange): """ _ft_has: FtHas = { + "stoploss_on_exchange": True, + "stop_price_param": "stopPrice", + "stop_price_prop": "stopPrice", + "stoploss_order_types": {"limit": "limit", "market": "market"}, "ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones. "order_time_in_force": ["GTC", "FOK", "IOC", "PO"], } @@ -50,3 +63,55 @@ class Bitget(Exchange): return 1000 return super().ohlcv_candle_limit(timeframe, candle_type, since_ms) + + def _convert_stop_order(self, pair: str, order_id: str, order: CcxtOrder) -> CcxtOrder: + if ( + order.get("status", "open") == "closed" + and (real_order_id := order.get("info", {}).get("ordId")) is not None + ): + # Once a order triggered, we fetch the regular followup order. + order_reg = self.fetch_order(real_order_id, pair) + self._log_exchange_response("fetch_stoploss_order1", order_reg) + order_reg["id_stop"] = order_reg["id"] + order_reg["id"] = order_id + order_reg["type"] = "stoploss" + order_reg["status_stop"] = "triggered" + return order_reg + order = self._order_contracts_to_amount(order) + order["type"] = "stoploss" + return order + + def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> CcxtOrder: + params2 = { + "stop": True, + } + for method in ( + self._api.fetch_open_orders, + self._api.fetch_closed_orders, + ): + try: + orders = method(pair, params=params2) + orders_f = [order for order in orders if order["id"] == order_id] + if orders_f: + order = orders_f[0] + return self._convert_stop_order(pair, order_id, order) + except (ccxt.OrderNotFound, ccxt.InvalidOrder): + pass + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.OperationFailed, ccxt.ExchangeError) as e: + raise TemporaryError( + f"Could not get order due to {e.__class__.__name__}. Message: {e}" + ) from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + raise RetryableOrderError(f"StoplossOrder not found (pair: {pair} id: {order_id}).") + + @retrier(retries=API_RETRY_COUNT) + def fetch_stoploss_order( + self, order_id: str, pair: str, params: dict | None = None + ) -> CcxtOrder: + if self._config["dry_run"]: + return self.fetch_dry_run_order(order_id) + + return self._fetch_stop_order_fallback(order_id, pair)