diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index d25a6a38f..3bae2bfe2 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -246,6 +246,7 @@ class Binance(Exchange): raise OperationalException( "Freqtrade only supports isolated futures for leverage trading") + @retrier def load_leverage_tiers(self) -> Dict[str, List[Dict]]: if self._config['dry_run']: leverage_tiers_path = ( diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 55c75ca0a..30e70461e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1858,6 +1858,7 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + @retrier def load_leverage_tiers(self) -> Dict[str, List[Dict]]: if self.trading_mode == TradingMode.FUTURES and self.exchange_has('fetchLeverageTiers'): try: @@ -1874,7 +1875,6 @@ class Exchange: else: return {} - @retrier def fill_leverage_tiers(self) -> None: """ Assigns property _leverage_tiers to a dictionary of information about the leverage @@ -1888,7 +1888,7 @@ class Exchange: self._leverage_tiers[pair] = pair_tiers def parse_leverage_tier(self, tier) -> Dict: - info = tier['info'] + info = tier.get('info', {}) return { 'min': tier['notionalFloor'], 'max': tier['notionalCap'], diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index c933153f5..99b19903b 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -1,9 +1,12 @@ import logging from typing import Dict, List, Tuple +import ccxt + from freqtrade.enums import MarginMode, TradingMode -from freqtrade.exceptions import OperationalException +from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange +from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -29,6 +32,25 @@ class Okx(Exchange): (TradingMode.FUTURES, MarginMode.ISOLATED), ] + def _get_params( + self, + ordertype: str, + leverage: float, + reduceOnly: bool, + time_in_force: str = 'gtc', + ) -> Dict: + # TODO-lev: Test me + params = super()._get_params( + ordertype=ordertype, + leverage=leverage, + reduceOnly=reduceOnly, + time_in_force=time_in_force, + ) + if self.trading_mode == TradingMode.FUTURES and self.margin_mode: + params['tdMode'] = self.margin_mode.value + return params + + @retrier def _lev_prep( self, pair: str, @@ -40,13 +62,22 @@ class Okx(Exchange): raise OperationalException( f"{self.name}.margin_mode must be set for {self.trading_mode.value}" ) - self._api.set_leverage( - leverage, - pair, - params={ - "mgnMode": self.margin_mode.value, - "posSide": "long" if side == "buy" else "short", - }) + try: + # TODO-lev: Test me properly (check mgnMode passed) + self._api.set_leverage( + leverage=leverage, + symbol=pair, + params={ + "mgnMode": self.margin_mode.value, + # "posSide": "net"", + }) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e def get_max_pair_stake_amount( self, @@ -64,6 +95,7 @@ class Okx(Exchange): pair_tiers = self._leverage_tiers[pair] return pair_tiers[-1]['max'] / leverage + @retrier def load_leverage_tiers(self) -> Dict[str, List[Dict]]: # * This is slow(~45s) on Okex, must make 90-some api calls to load all linear swap markets if self.trading_mode == TradingMode.FUTURES: @@ -82,11 +114,9 @@ class Okx(Exchange): f"Initializing leverage_tiers for {len(symbols)} markets. " "This will take about a minute.") - for symbol in symbols: - res = self._api.fetchLeverageTiers(symbol) - tiers[symbol] = [] - for tier in res[symbol]: - tiers[symbol].append(self.parse_leverage_tier(tier)) + for symbol in sorted(symbols): + res = self._api.fetch_leverage_tiers(symbol) + tiers[symbol] = res[symbol] logger.info(f"Done initializing {len(symbols)} markets.") return tiers diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 676499642..7baa91720 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -231,9 +231,9 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 10 active markets: " - "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, " - "TKN/BTC, XLTCUSDT, XRP/BTC.\n" + assert ("Exchange Bittrex has 12 active markets: " + "ADA/USDT:USDT, BLK/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, " + "LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) @@ -246,7 +246,7 @@ def test_list_markets(mocker, markets_static, capsys): pargs['config'] = None start_list_markets(pargs, False) captured = capsys.readouterr() - assert re.match("\nExchange Binance has 10 active markets:\n", + assert re.match("\nExchange Binance has 12 active markets:\n", captured.out) patch_exchange(mocker, api_mock=api_mock, id="bittrex", mock_markets=markets_static) @@ -258,9 +258,9 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 12 markets: " - "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, " - "TKN/BTC, XLTCUSDT, XRP/BTC.\n" + assert ("Exchange Bittrex has 14 markets: " + "ADA/USDT:USDT, BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, " + "LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) # Test list-pairs subcommand: active pairs @@ -297,8 +297,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 6 active markets with ETH, LTC as base currencies: " - "ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 7 active markets with ETH, LTC as base currencies: " + "ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, base=LTC @@ -323,8 +323,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 3 active markets with USDT, USD as quote currencies: " - "ETH/USDT, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 5 active markets with USDT, USD as quote currencies: " + "ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, quote=USDT @@ -336,8 +336,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 2 active markets with USDT as quote currency: " - "ETH/USDT, XLTCUSDT.\n" + assert ("Exchange Bittrex has 4 active markets with USDT as quote currency: " + "ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, XLTCUSDT.\n" in captured.out) # active markets, base=LTC, quote=USDT @@ -399,7 +399,7 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 10 active markets:\n" + assert ("Exchange Bittrex has 12 active markets:\n" in captured.out) # Test tabular output, no markets found @@ -422,8 +422,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC",' - '"TKN/BTC","XLTCUSDT","XRP/BTC"]' + assert ('["ADA/USDT:USDT","BLK/BTC","ETH/BTC","ETH/USDT","ETH/USDT:USDT",' + '"LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC","TKN/BTC","XLTCUSDT","XRP/BTC"]' in captured.out) # Test --print-csv diff --git a/tests/conftest.py b/tests/conftest.py index 04c4fd70d..00c8c3916 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -891,8 +891,8 @@ def get_markets(): 'future': True, 'swap': True, 'margin': True, - 'linear': False, - 'inverse': True, + 'linear': None, + 'inverse': False, 'type': 'spot', 'contractSize': None, 'taker': 0.0006, @@ -1398,7 +1398,9 @@ def markets_static(): # market list. Do not modify this list without a good reason! Do not modify market parameters # of listed pairs in get_markets() without a good reason either! static_markets = ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', - 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC'] + 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', + 'ADA/USDT:USDT', 'ETH/USDT:USDT', + ] all_markets = get_markets() return {m: all_markets[m] for m in static_markets} diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index cb516e0d0..64f18220f 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -486,7 +486,7 @@ def test_fill_leverage_tiers_binance(default_conf, mocker): api_mock, "binance", "fill_leverage_tiers", - "fetch_leverage_tiers" + "fetch_leverage_tiers", ) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 44bd68a31..ce559e9dd 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -341,9 +341,9 @@ class TestCCXTExchange(): def test_ccxt_get_max_leverage_futures(self, exchange_futures): futures, futures_name = exchange_futures - # TODO-lev: binance, gateio, and okx test if futures: leverage_in_market_futures = EXCHANGES[futures_name]['leverage_in_market']['futures'] + # TODO-lev: binance, gateio, and okx don't have leverage_in_market if leverage_in_market_futures: futures_pair = EXCHANGES[futures_name].get( 'futures_pair', diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 0de8ddfbc..d43c0518d 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3003,8 +3003,9 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): # 'XLTCUSDT': 'active': True, not a pair # 'XRP/BTC': 'active': False ([], [], False, False, False, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', - 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC'], + ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', + 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', 'ADA/USDT:USDT', + 'ETH/USDT:USDT'], 'all markets'), ([], [], False, False, True, False, ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', @@ -3012,7 +3013,7 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): 'all markets, only spot pairs'), ([], [], False, True, False, False, ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', - 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC'], + 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], 'active markets'), ([], [], True, False, False, False, ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', @@ -3023,7 +3024,8 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): 'TKN/BTC', 'XRP/BTC'], 'active pairs'), (['ETH', 'LTC'], [], False, False, False, False, - ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT'], + ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT', + 'ETH/USDT:USDT'], 'all markets, base=ETH, LTC'), (['LTC'], [], False, False, False, False, ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT'], @@ -3032,13 +3034,13 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT'], 'spot markets, base=LTC'), ([], ['USDT'], False, False, False, False, - ['ETH/USDT', 'LTC/USDT', 'XLTCUSDT'], + ['ETH/USDT', 'LTC/USDT', 'XLTCUSDT', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], 'all markets, quote=USDT'), ([], ['USDT'], False, False, False, True, - ['ETH/USDT', 'LTC/USDT'], + ['ADA/USDT:USDT', 'ETH/USDT:USDT'], 'Futures markets, quote=USDT'), ([], ['USDT', 'USD'], False, False, False, False, - ['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT'], + ['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], 'all markets, quote=USDT, USD'), ([], ['USDT', 'USD'], False, False, True, False, ['ETH/USDT', 'LTC/USD', 'LTC/USDT'], @@ -4247,7 +4249,7 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers): mocker, default_conf, api_mock, - "binance", + "ftx", "load_leverage_tiers", "fetch_leverage_tiers", ) diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index 597c75632..2eaa1736d 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -309,7 +309,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets): exchange.trading_mode = TradingMode.FUTURES exchange.margin_mode = MarginMode.ISOLATED exchange.markets = markets - assert exchange.load_leverage_tiers() == { + # Initialization of load_leverage_tiers happens as part of exchange init. + exchange._leverage_tiers == { 'ADA/USDT:USDT': [ { 'tier': 1,