diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 650df12d0..940b0312b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1894,28 +1894,6 @@ class Exchange: 'maintAmt': float(info['cum']) if 'cum' in info else None, } - @retrier - def get_leverage_tiers_for_pair(self, pair: str) -> List: - # When exchanges can load all their leverage tiers at once in the constructor - # then this method does nothing, it should only be implemented when the leverage - # tiers requires per symbol fetching to avoid excess api calls - if pair not in self._leverage_tiers: - self._leverage_tiers[pair] = [] - if ( - self._api.has['fetchLeverageTiers'] and - not self._ft_has['can_fetch_multiple_tiers'] and - self.trading_mode == TradingMode.FUTURES - ): - try: - tiers = self._api.fetch_leverage_tiers(pair) - for tier in tiers[pair]: - self._leverage_tiers[pair].append(self.parse_leverage_tier(tier)) - - except ccxt.BadRequest: - return [] - - return self._leverage_tiers[pair] - def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float: """ Returns the maximum leverage that a pair can be traded at @@ -1926,7 +1904,7 @@ class Exchange: if self.trading_mode == TradingMode.SPOT: return 1.0 - if self._api.has['fetchLeverageTiers']: + if self.trading_mode == TradingMode.FUTURES: # Checks and edge cases if stake_amount is None: @@ -1934,20 +1912,21 @@ class Exchange: f'{self.name}.get_max_leverage requires argument stake_amount' ) - pair_tiers = self.get_leverage_tiers_for_pair(pair) - num_tiers = len(pair_tiers) - if num_tiers < 1: + if pair not in self._leverage_tiers: + # Maybe raise exception because it can't be traded on futures? return 1.0 + pair_tiers = self._leverage_tiers[pair] + if stake_amount == 0: return self._leverage_tiers[pair][0]['lev'] # Max lev for lowest amount - for tier_index in range(num_tiers): + for tier_index in range(len(pair_tiers)): tier = pair_tiers[tier_index] lev = tier['lev'] - if tier_index < num_tiers - 1: + if tier_index < len(pair_tiers) - 1: next_tier = pair_tiers[tier_index+1] next_floor = next_tier['min'] / next_tier['lev'] if next_floor > stake_amount: # Next tier min too high for stake amount @@ -2280,13 +2259,13 @@ class Exchange: if self._api.has['fetchLeverageTiers']: - pair_tiers = self.get_leverage_tiers_for_pair(pair) - - if len(pair_tiers) < 1: + if pair not in self._leverage_tiers: raise InvalidOrderException( f"Maintenance margin rate for {pair} is unavailable for {self.name}" ) + pair_tiers = self._leverage_tiers[pair] + for tier in reversed(pair_tiers): if nominal_value >= tier['min']: return (tier['mmr'], tier['maintAmt']) diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index e1293fdfe..b11627bb1 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -59,14 +59,25 @@ class Okx(Exchange): return float('inf') # Not actually inf, but this probably won't matter for SPOT if pair not in self._leverage_tiers: - tiers = self.get_leverage_tiers_for_pair(pair) - if not tiers: # Not a leveraged market - return float('inf') - else: - self._leverage_tiers[pair] = tiers + return float('inf') pair_tiers = self._leverage_tiers[pair] return pair_tiers[-1]['max'] / leverage def load_leverage_tiers(self) -> Dict[str, List[Dict]]: - return {} + if self.trading_mode == TradingMode.FUTURES: + markets = self.markets + symbols = [] + + for symbol, market in markets.items(): + if (market["swap"] and market["linear"]): + symbols.append(market["symbol"]) + + tiers = {} + for symbol in symbols: + res = self._api.fetchLeverageTiers(symbol) + tiers[symbol] = res[symbol] + + return tiers + else: + return {} diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d1fd357a8..211f15d0a 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4300,115 +4300,6 @@ def test_parse_leverage_tier(mocker, default_conf): } -def test_get_leverage_tiers_for_pair( - mocker, - default_conf, - leverage_tiers, -): - - api_mock = MagicMock() - api_mock.fetch_leverage_tiers = MagicMock(return_value={ - 'DOGE/USDT:USDT': [ - { - 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 500, - 'maintenanceMarginRatio': 0.02, - 'maxLeverage': 75, - 'info': { - 'baseMaxLoan': '', - 'imr': '0.013', - 'instId': '', - 'maxLever': '75', - 'maxSz': '500', - 'minSz': '0', - 'mmr': '0.01', - 'optMgnFactor': '0', - 'quoteMaxLoan': '', - 'tier': '1', - 'uly': 'DOGE-USDT' - } - }, - { - 'tier': 2, - 'notionalFloor': 501, - 'notionalCap': 1000, - 'maintenanceMarginRatio': 0.025, - 'maxLeverage': 50, - 'info': { - 'baseMaxLoan': '', - 'imr': '0.02', - 'instId': '', - 'maxLever': '50', - 'maxSz': '1000', - 'minSz': '501', - 'mmr': '0.015', - 'optMgnFactor': '0', - 'quoteMaxLoan': '', - 'tier': '2', - 'uly': 'DOGE-USDT' - } - } - ], - }) - - # Spot - type(api_mock)._ft_has = PropertyMock(return_value={'fetchLeverageTiers': True}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - exchange._ft_has['can_fetch_multiple_tiers'] = False - assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == [] - - # 'can_fetch_multiple_tiers': True - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - exchange._ft_has['can_fetch_multiple_tiers'] = True - assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == [] - - # 'fetchLeverageTiers': False - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - exchange._ft_has['can_fetch_multiple_tiers'] = False - assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == [] - - # 'fetchLeverageTiers': True - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - exchange._ft_has['can_fetch_multiple_tiers'] = False - assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == [ - { - 'min': 0, - 'max': 500, - 'mmr': 0.02, - 'lev': 75, - 'maintAmt': None - }, - { - 'min': 501, - 'max': 1000, - 'mmr': 0.025, - 'lev': 50, - 'maintAmt': None - } - ] - - # exception_handlers - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) - default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock) - - ccxt_exceptionhandlers( - mocker, - default_conf, - api_mock, - "binance", - "get_leverage_tiers_for_pair", - "fetch_leverage_tiers", - pair='DOGE/USDT:USDT', - ) - - def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage_tiers): api_mock = MagicMock() default_conf['trading_mode'] = 'futures' @@ -4423,7 +4314,6 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT', -1) exchange._leverage_tiers = {} - exchange.get_leverage_tiers_for_pair = MagicMock(return_value=[]) with pytest.raises( InvalidOrderException, diff --git a/tests/exchange/test_okex.py b/tests/exchange/test_okex.py index 9f9d02d09..b97b90c27 100644 --- a/tests/exchange/test_okex.py +++ b/tests/exchange/test_okex.py @@ -162,5 +162,4 @@ def test_get_max_pair_stake_amount_okex(default_conf, mocker, leverage_tiers): assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0) == 1000000000 assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0, 10.0) == 100000000 - exchange.get_leverage_tiers_for_pair = MagicMock(return_value=None) assert exchange.get_max_pair_stake_amount('TTT/USDT', 1.0) == float('inf') # Not in tiers