mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-01-19 21:40:24 +00:00
Merge pull request #12532 from stash86/bitget-delist
add delisting check for bitget futures
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import ccxt
|
||||
|
||||
from freqtrade.constants import BuySell
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import (
|
||||
DDosProtection,
|
||||
OperationalException,
|
||||
@@ -14,7 +14,7 @@ from freqtrade.exceptions import (
|
||||
from freqtrade.exchange import Exchange
|
||||
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
|
||||
from freqtrade.util import dt_from_ts, dt_now, dt_ts
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -37,6 +37,7 @@ class Bitget(Exchange):
|
||||
_ft_has_futures: FtHas = {
|
||||
"mark_ohlcv_timeframe": "4h",
|
||||
"funding_fee_candle_limit": 100,
|
||||
"has_delisting": True,
|
||||
}
|
||||
|
||||
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
|
||||
@@ -236,3 +237,35 @@ class Bitget(Exchange):
|
||||
raise OperationalException(
|
||||
"Freqtrade currently only supports isolated futures for bitget"
|
||||
)
|
||||
|
||||
def check_delisting_time(self, pair: str) -> datetime | None:
|
||||
"""
|
||||
Check if the pair gonna be delisted.
|
||||
By default, it returns None.
|
||||
:param pair: Market symbol
|
||||
:return: Datetime if the pair gonna be delisted, None otherwise
|
||||
"""
|
||||
if self._config["runmode"] in OPTIMIZE_MODES:
|
||||
return None
|
||||
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
return self._check_delisting_futures(pair)
|
||||
return None
|
||||
|
||||
def _check_delisting_futures(self, pair: str) -> datetime | None:
|
||||
delivery_time = self.markets.get(pair, {}).get("info", {}).get("limitOpenTime", None)
|
||||
if delivery_time:
|
||||
if isinstance(delivery_time, str) and (delivery_time != ""):
|
||||
delivery_time = int(delivery_time)
|
||||
|
||||
if not isinstance(delivery_time, int) or delivery_time <= 0:
|
||||
return None
|
||||
|
||||
max_delivery = dt_ts() + (
|
||||
14 * 24 * 60 * 60 * 1000
|
||||
) # Assume exchange don't announce delisting more than 14 days in advance
|
||||
|
||||
if delivery_time < max_delivery:
|
||||
return dt_from_ts(delivery_time)
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from copy import deepcopy
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode
|
||||
from freqtrade.exceptions import OperationalException, RetryableOrderError
|
||||
from freqtrade.exchange.common import API_RETRY_COUNT
|
||||
from freqtrade.util import dt_now, dt_ts
|
||||
from freqtrade.util import dt_now, dt_ts, dt_utc
|
||||
from tests.conftest import EXMS, get_patched_exchange
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
@@ -193,3 +194,43 @@ def test__lev_prep_bitget(default_conf, mocker):
|
||||
assert api_mock.set_margin_mode.call_count == 0
|
||||
assert api_mock.set_leverage.call_count == 1
|
||||
api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=19.99)
|
||||
|
||||
|
||||
def test_check_delisting_time_bitget(default_conf_usdt, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt, exchange="bitget")
|
||||
exchange._config["runmode"] = RunMode.BACKTEST
|
||||
delist_fut_mock = MagicMock(return_value=None)
|
||||
mocker.patch.object(exchange, "_check_delisting_futures", delist_fut_mock)
|
||||
|
||||
# Invalid run mode
|
||||
resp = exchange.check_delisting_time("BTC/USDT")
|
||||
assert resp is None
|
||||
assert delist_fut_mock.call_count == 0
|
||||
|
||||
# Delist spot called
|
||||
exchange._config["runmode"] = RunMode.DRY_RUN
|
||||
resp1 = exchange.check_delisting_time("BTC/USDT")
|
||||
assert resp1 is None
|
||||
assert delist_fut_mock.call_count == 0
|
||||
|
||||
# Delist futures called
|
||||
exchange.trading_mode = TradingMode.FUTURES
|
||||
resp1 = exchange.check_delisting_time("BTC/USDT:USDT")
|
||||
assert resp1 is None
|
||||
assert delist_fut_mock.call_count == 1
|
||||
|
||||
|
||||
def test__check_delisting_futures_bitget(default_conf_usdt, mocker, markets):
|
||||
markets["BTC/USDT:USDT"] = deepcopy(markets["SOL/BUSD:BUSD"])
|
||||
markets["BTC/USDT:USDT"]["info"]["limitOpenTime"] = "-1"
|
||||
markets["SOL/BUSD:BUSD"]["info"]["limitOpenTime"] = "-1"
|
||||
markets["ADA/USDT:USDT"]["info"]["limitOpenTime"] = "1760745600000" # 2025-10-18
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt, exchange="bitget")
|
||||
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets))
|
||||
|
||||
resp_sol = exchange._check_delisting_futures("SOL/BUSD:BUSD")
|
||||
# No delisting date
|
||||
assert resp_sol is None
|
||||
# Has a delisting date
|
||||
resp_ada = exchange._check_delisting_futures("ADA/USDT:USDT")
|
||||
assert resp_ada == dt_utc(2025, 10, 18)
|
||||
|
||||
Reference in New Issue
Block a user