Merge branch 'leverage-tiers' of https://github.com/samgermain/freqtrade into leverage-tiers

This commit is contained in:
Sam Germain 2022-02-16 03:50:24 -06:00
commit dbd2df6406
9 changed files with 81 additions and 45 deletions

View File

@ -246,6 +246,7 @@ class Binance(Exchange):
raise OperationalException( raise OperationalException(
"Freqtrade only supports isolated futures for leverage trading") "Freqtrade only supports isolated futures for leverage trading")
@retrier
def load_leverage_tiers(self) -> Dict[str, List[Dict]]: def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
if self._config['dry_run']: if self._config['dry_run']:
leverage_tiers_path = ( leverage_tiers_path = (

View File

@ -1858,6 +1858,7 @@ class Exchange:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
@retrier
def load_leverage_tiers(self) -> Dict[str, List[Dict]]: def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
if self.trading_mode == TradingMode.FUTURES and self.exchange_has('fetchLeverageTiers'): if self.trading_mode == TradingMode.FUTURES and self.exchange_has('fetchLeverageTiers'):
try: try:
@ -1874,7 +1875,6 @@ class Exchange:
else: else:
return {} return {}
@retrier
def fill_leverage_tiers(self) -> None: def fill_leverage_tiers(self) -> None:
""" """
Assigns property _leverage_tiers to a dictionary of information about the leverage Assigns property _leverage_tiers to a dictionary of information about the leverage
@ -1888,7 +1888,7 @@ class Exchange:
self._leverage_tiers[pair] = pair_tiers self._leverage_tiers[pair] = pair_tiers
def parse_leverage_tier(self, tier) -> Dict: def parse_leverage_tier(self, tier) -> Dict:
info = tier['info'] info = tier.get('info', {})
return { return {
'min': tier['notionalFloor'], 'min': tier['notionalFloor'],
'max': tier['notionalCap'], 'max': tier['notionalCap'],

View File

@ -1,9 +1,12 @@
import logging import logging
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
import ccxt
from freqtrade.enums import MarginMode, TradingMode 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 import Exchange
from freqtrade.exchange.common import retrier
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -29,6 +32,25 @@ class Okx(Exchange):
(TradingMode.FUTURES, MarginMode.ISOLATED), (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( def _lev_prep(
self, self,
pair: str, pair: str,
@ -40,13 +62,22 @@ class Okx(Exchange):
raise OperationalException( raise OperationalException(
f"{self.name}.margin_mode must be set for {self.trading_mode.value}" f"{self.name}.margin_mode must be set for {self.trading_mode.value}"
) )
self._api.set_leverage( try:
leverage, # TODO-lev: Test me properly (check mgnMode passed)
pair, self._api.set_leverage(
params={ leverage=leverage,
"mgnMode": self.margin_mode.value, symbol=pair,
"posSide": "long" if side == "buy" else "short", 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( def get_max_pair_stake_amount(
self, self,
@ -64,6 +95,7 @@ class Okx(Exchange):
pair_tiers = self._leverage_tiers[pair] pair_tiers = self._leverage_tiers[pair]
return pair_tiers[-1]['max'] / leverage return pair_tiers[-1]['max'] / leverage
@retrier
def load_leverage_tiers(self) -> Dict[str, List[Dict]]: 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 # * This is slow(~45s) on Okex, must make 90-some api calls to load all linear swap markets
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
@ -82,11 +114,9 @@ class Okx(Exchange):
f"Initializing leverage_tiers for {len(symbols)} markets. " f"Initializing leverage_tiers for {len(symbols)} markets. "
"This will take about a minute.") "This will take about a minute.")
for symbol in symbols: for symbol in sorted(symbols):
res = self._api.fetchLeverageTiers(symbol) res = self._api.fetch_leverage_tiers(symbol)
tiers[symbol] = [] tiers[symbol] = res[symbol]
for tier in res[symbol]:
tiers[symbol].append(self.parse_leverage_tier(tier))
logger.info(f"Done initializing {len(symbols)} markets.") logger.info(f"Done initializing {len(symbols)} markets.")
return tiers return tiers

View File

@ -231,9 +231,9 @@ def test_list_markets(mocker, markets_static, capsys):
] ]
start_list_markets(get_args(args), False) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert ("Exchange Bittrex has 10 active markets: " assert ("Exchange Bittrex has 12 active markets: "
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, " "ADA/USDT:USDT, BLK/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, "
"TKN/BTC, XLTCUSDT, XRP/BTC.\n" "LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
in captured.out) in captured.out)
patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) 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 pargs['config'] = None
start_list_markets(pargs, False) start_list_markets(pargs, False)
captured = capsys.readouterr() 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) captured.out)
patch_exchange(mocker, api_mock=api_mock, id="bittrex", mock_markets=markets_static) 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) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert ("Exchange Bittrex has 12 markets: " assert ("Exchange Bittrex has 14 markets: "
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, " "ADA/USDT:USDT, BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, "
"TKN/BTC, XLTCUSDT, XRP/BTC.\n" "LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
in captured.out) in captured.out)
# Test list-pairs subcommand: active pairs # 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) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert ("Exchange Bittrex has 6 active markets with ETH, LTC as base currencies: " assert ("Exchange Bittrex has 7 active markets with ETH, LTC as base currencies: "
"ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" "ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
in captured.out) in captured.out)
# active markets, base=LTC # active markets, base=LTC
@ -323,8 +323,8 @@ def test_list_markets(mocker, markets_static, capsys):
] ]
start_list_markets(get_args(args), False) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert ("Exchange Bittrex has 3 active markets with USDT, USD as quote currencies: " assert ("Exchange Bittrex has 5 active markets with USDT, USD as quote currencies: "
"ETH/USDT, LTC/USD, XLTCUSDT.\n" "ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, LTC/USD, XLTCUSDT.\n"
in captured.out) in captured.out)
# active markets, quote=USDT # active markets, quote=USDT
@ -336,8 +336,8 @@ def test_list_markets(mocker, markets_static, capsys):
] ]
start_list_markets(get_args(args), False) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert ("Exchange Bittrex has 2 active markets with USDT as quote currency: " assert ("Exchange Bittrex has 4 active markets with USDT as quote currency: "
"ETH/USDT, XLTCUSDT.\n" "ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, XLTCUSDT.\n"
in captured.out) in captured.out)
# active markets, base=LTC, quote=USDT # 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) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert ("Exchange Bittrex has 10 active markets:\n" assert ("Exchange Bittrex has 12 active markets:\n"
in captured.out) in captured.out)
# Test tabular output, no markets found # 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) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC",' assert ('["ADA/USDT:USDT","BLK/BTC","ETH/BTC","ETH/USDT","ETH/USDT:USDT",'
'"TKN/BTC","XLTCUSDT","XRP/BTC"]' '"LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC","TKN/BTC","XLTCUSDT","XRP/BTC"]'
in captured.out) in captured.out)
# Test --print-csv # Test --print-csv

View File

@ -891,8 +891,8 @@ def get_markets():
'future': True, 'future': True,
'swap': True, 'swap': True,
'margin': True, 'margin': True,
'linear': False, 'linear': None,
'inverse': True, 'inverse': False,
'type': 'spot', 'type': 'spot',
'contractSize': None, 'contractSize': None,
'taker': 0.0006, '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 # 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! # 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', 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() all_markets = get_markets()
return {m: all_markets[m] for m in static_markets} return {m: all_markets[m] for m in static_markets}

View File

@ -486,7 +486,7 @@ def test_fill_leverage_tiers_binance(default_conf, mocker):
api_mock, api_mock,
"binance", "binance",
"fill_leverage_tiers", "fill_leverage_tiers",
"fetch_leverage_tiers" "fetch_leverage_tiers",
) )

View File

@ -341,9 +341,9 @@ class TestCCXTExchange():
def test_ccxt_get_max_leverage_futures(self, exchange_futures): def test_ccxt_get_max_leverage_futures(self, exchange_futures):
futures, futures_name = exchange_futures futures, futures_name = exchange_futures
# TODO-lev: binance, gateio, and okx test
if futures: if futures:
leverage_in_market_futures = EXCHANGES[futures_name]['leverage_in_market']['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: if leverage_in_market_futures:
futures_pair = EXCHANGES[futures_name].get( futures_pair = EXCHANGES[futures_name].get(
'futures_pair', 'futures_pair',

View File

@ -3003,8 +3003,9 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
# 'XLTCUSDT': 'active': True, not a pair # 'XLTCUSDT': 'active': True, not a pair
# 'XRP/BTC': 'active': False # 'XRP/BTC': 'active': False
([], [], False, False, False, False, ([], [], False, False, False, False,
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT',
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC'], 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', 'ADA/USDT:USDT',
'ETH/USDT:USDT'],
'all markets'), 'all markets'),
([], [], False, False, True, False, ([], [], False, False, True, False,
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', ['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'), 'all markets, only spot pairs'),
([], [], False, True, False, False, ([], [], False, True, False, False,
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', ['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'), 'active markets'),
([], [], True, False, False, False, ([], [], True, False, False, False,
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', ['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'], 'TKN/BTC', 'XRP/BTC'],
'active pairs'), 'active pairs'),
(['ETH', 'LTC'], [], False, False, False, False, (['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'), 'all markets, base=ETH, LTC'),
(['LTC'], [], False, False, False, False, (['LTC'], [], False, False, False, False,
['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT'], ['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'], ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT'],
'spot markets, base=LTC'), 'spot markets, base=LTC'),
([], ['USDT'], False, False, False, False, ([], ['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'), 'all markets, quote=USDT'),
([], ['USDT'], False, False, False, True, ([], ['USDT'], False, False, False, True,
['ETH/USDT', 'LTC/USDT'], ['ADA/USDT:USDT', 'ETH/USDT:USDT'],
'Futures markets, quote=USDT'), 'Futures markets, quote=USDT'),
([], ['USDT', 'USD'], False, False, False, False, ([], ['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'), 'all markets, quote=USDT, USD'),
([], ['USDT', 'USD'], False, False, True, False, ([], ['USDT', 'USD'], False, False, True, False,
['ETH/USDT', 'LTC/USD', 'LTC/USDT'], ['ETH/USDT', 'LTC/USD', 'LTC/USDT'],
@ -4247,7 +4249,7 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers):
mocker, mocker,
default_conf, default_conf,
api_mock, api_mock,
"binance", "ftx",
"load_leverage_tiers", "load_leverage_tiers",
"fetch_leverage_tiers", "fetch_leverage_tiers",
) )

View File

@ -309,7 +309,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets):
exchange.trading_mode = TradingMode.FUTURES exchange.trading_mode = TradingMode.FUTURES
exchange.margin_mode = MarginMode.ISOLATED exchange.margin_mode = MarginMode.ISOLATED
exchange.markets = markets 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': [ 'ADA/USDT:USDT': [
{ {
'tier': 1, 'tier': 1,