diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index def3b91a8..62b5e39da 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -5,6 +5,8 @@ from pathlib import Path from typing import Dict, List, Optional, Tuple import ccxt +from cachetools import TTLCache +from threading import Lock from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError @@ -50,6 +52,11 @@ class Binance(Exchange): (TradingMode.FUTURES, MarginMode.ISOLATED) ] + def __init__(self, *args, **kwargs) -> None: + super(Binance, self).__init__(*args, **kwargs) + self._spot_delist_schedule_cache: TTLCache = TTLCache(maxsize=100, ttl=300) + self._spot_delist_schedule_cache_lock = Lock() + def get_tickers(self, symbols: Optional[List[str]] = None, cached: bool = False) -> Tickers: tickers = super().get_tickers(symbols=symbols, cached=cached) if self.trading_mode == TradingMode.FUTURES: @@ -218,43 +225,41 @@ class Binance(Exchange): else: return {} - def get_spot_delist_schedule(self, refresh: bool) -> list: + def get_spot_pair_delist_time(self, pair, refresh: bool) -> int | None: """ - Calculates bid/ask target - bid rate - between current ask price and last price - ask rate - either using ticker bid or first bid based on orderbook - or remain static in any other case since it's not updating. - :param pair: Pair to get rate for - :param refresh: allow cached data - :param side: "buy" or "sell" - :return: float: Price - :raises PricingError if orderbook price could not be determined. + Get the delisting time for a pair if it will be delisted + :param pair: Pair to get the delisting time for + :param refresh: true if you need fresh data + :return: int: delisting time None if not delisting """ + + cache = self._spot_delist_schedule_cache + lock = self._spot_delist_schedule_cache_lock + if not refresh: - with self._cache_lock: - rate = cache_rate.get(pair) - # Check if cache has been invalidated - if rate: - logger.debug(f"Using cached {side} rate for {pair}.") - return rate + with lock: + delist_time = cache.get(f"{pair}") + if delist_time: + return delist_time + try: + delist_schedule = self._api.sapi_get_spot_delist_schedule() - if conf_strategy.get('use_order_book', False): + if delist_schedule is None: + return - order_book_top = conf_strategy.get('order_book_top', 1) - if order_book is None: - order_book = self.fetch_l2_order_book(pair, order_book_top) - rate = self._get_rate_from_ob(pair, side, order_book, name, price_side, - order_book_top) - else: - logger.debug(f"Using Last {price_side.capitalize()} / Last Price") - if ticker is None: - ticker = self.fetch_ticker(pair) - rate = self._get_rate_from_ticker(side, ticker, conf_strategy, price_side) + with lock: + for schedule in delist_schedule: + for pair in schedule['symbols']: + cache[f"{pair}"] = int(schedule['delistTime']) - if rate is None: - raise PricingError(f"{name}-Rate for {pair} was empty.") - with self._cache_lock: - cache_rate[pair] = rate + with lock: + return cache.get(f"{pair}") - return delistings \ No newline at end of file + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get delist schedule {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e \ No newline at end of file diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index ac87c0ca3..605dc9b43 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -621,7 +621,7 @@ def test_get_maintenance_ratio_and_amt_binance( def test_get_spot_delist_schedule(mocker, default_conf) -> None: exchange = get_patched_exchange(mocker, default_conf, id='binance') - exchange._api.sapi_get_spot_delist_schedule = get_mock_coro([{'delistTime': '1712113200000', 'symbols': ['DREPBTC', 'DREPUSDT', 'MOBBTC', 'MOBUSDT', 'PNTUSDT']}]) + exchange._api.sapi_get_spot_delist_schedule = MagicMock(return_value=[{'delistTime': '1712113200000', 'symbols': ['DREPBTC', 'DREPUSDT', 'MOBBTC', 'MOBUSDT', 'PNTUSDT']}]) - assert exchange.get_spot_delist_schedule(True) == ['DREP/BTC', 'DREP/USDT', 'MOB/BTC', 'MOB/USDT', 'PNT/USDT'] \ No newline at end of file + assert exchange.get_spot_pair_delist_time('DREP/USDT', False) == 1712113200000 \ No newline at end of file