replaced "leverage" with "tiers"

This commit is contained in:
Sam Germain 2022-02-07 02:01:00 -06:00
parent 6b9915bc73
commit 42e36f44f8
4 changed files with 72 additions and 73 deletions

View File

@ -246,15 +246,22 @@ class Binance(Exchange):
raise OperationalException( raise OperationalException(
"Freqtrade only supports isolated futures for leverage trading") "Freqtrade only supports isolated futures for leverage trading")
def load_leverage_brackets(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_brackets_path = ( leverage_tiers_path = (
Path(__file__).parent / 'binance_leverage_tiers.json' Path(__file__).parent / 'binance_leverage_tiers.json'
) )
leverage_brackets = {} with open(leverage_tiers_path) as json_file:
with open(leverage_brackets_path) as json_file: leverage_tiers = json.load(json_file)
leverage_brackets = json.load(json_file) return leverage_tiers
return leverage_brackets
else: else:
leverage_brackets = self._api.fetch_leverage_tiers() try:
return leverage_brackets leverage_tiers = self._api.fetch_leverage_tiers()
return leverage_tiers
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not fetch leverage amounts due to'
f'{e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e

View File

@ -92,7 +92,7 @@ class Exchange:
self._api: ccxt.Exchange = None self._api: ccxt.Exchange = None
self._api_async: ccxt_async.Exchange = None self._api_async: ccxt_async.Exchange = None
self._markets: Dict = {} self._markets: Dict = {}
self._leverage_brackets: Dict[str, List[Dict]] = {} self._leverage_tiers: Dict[str, List[Dict]] = {}
self.loop = asyncio.new_event_loop() self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
@ -185,7 +185,7 @@ class Exchange:
"markets_refresh_interval", 60) * 60 "markets_refresh_interval", 60) * 60
if self.trading_mode != TradingMode.SPOT: if self.trading_mode != TradingMode.SPOT:
self.fill_leverage_brackets() self.fill_leverage_tiers()
def __del__(self): def __del__(self):
""" """
@ -461,7 +461,7 @@ class Exchange:
# Also reload async markets to avoid issues with newly listed pairs # Also reload async markets to avoid issues with newly listed pairs
self._load_async_markets(reload=True) self._load_async_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = arrow.utcnow().int_timestamp
self.fill_leverage_brackets() self.fill_leverage_tiers()
except ccxt.BaseError: except ccxt.BaseError:
logger.exception("Could not reload markets.") logger.exception("Could not reload markets.")
@ -1856,15 +1856,15 @@ class Exchange:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
def load_leverage_brackets(self) -> Dict[str, List[Dict]]: def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
return self._api.fetch_leverage_tiers() return self._api.fetch_leverage_tiers()
@retrier @retrier
def fill_leverage_brackets(self) -> None: def fill_leverage_tiers(self) -> None:
""" """
Assigns property _leverage_brackets to a dictionary of information about the leverage Assigns property _leverage_tiers to a dictionary of information about the leverage
allowed on each pair allowed on each pair
After exectution, self._leverage_brackets = { After exectution, self._leverage_tiers = {
"pair_name": [ "pair_name": [
[notional_floor, maintenenace_margin_ratio, maintenance_amt], [notional_floor, maintenenace_margin_ratio, maintenance_amt],
... ...
@ -1882,20 +1882,12 @@ class Exchange:
""" """
if self._api.has['fetchLeverageTiers']: if self._api.has['fetchLeverageTiers']:
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
leverage_brackets = self.load_leverage_brackets() leverage_tiers = self.load_leverage_tiers()
try: for pair, tiers in leverage_tiers.items():
for pair, tiers in leverage_brackets.items(): tiers = []
brackets = [] for tier in tiers:
for tier in tiers: tiers.append(self.parse_leverage_tier(tier))
brackets.append(self.parse_leverage_tier(tier)) self._leverage_tiers[pair] = tiers
self._leverage_brackets[pair] = brackets
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not fetch leverage amounts due to'
f'{e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def parse_leverage_tier(self, tier) -> Dict: def parse_leverage_tier(self, tier) -> Dict:
info = tier['info'] info = tier['info']
@ -1923,30 +1915,30 @@ class Exchange:
if stake_amount is None: if stake_amount is None:
raise OperationalException( raise OperationalException(
'binance.get_max_leverage requires argument stake_amount') 'binance.get_max_leverage requires argument stake_amount')
if pair not in self._leverage_brackets: if pair not in self._leverage_tiers:
brackets = self.get_leverage_tiers_for_pair(pair) tiers = self.get_leverage_tiers_for_pair(pair)
if not brackets: # Not a leveraged market if not tiers: # Not a leveraged market
return 1.0 return 1.0
else: else:
self._leverage_brackets[pair] = brackets self._leverage_tiers[pair] = tiers
if stake_amount == 0: if stake_amount == 0:
return self._leverage_brackets[pair][0]['lev'] # Max lev for lowest amount return self._leverage_tiers[pair][0]['lev'] # Max lev for lowest amount
pair_brackets = self._leverage_brackets[pair] pair_tiers = self._leverage_tiers[pair]
num_brackets = len(pair_brackets) num_tiers = len(pair_tiers)
for bracket_index in range(num_brackets): for tier_index in range(num_tiers):
bracket = pair_brackets[bracket_index] tier = pair_tiers[tier_index]
lev = bracket['lev'] lev = tier['lev']
if bracket_index < num_brackets - 1: if tier_index < num_tiers - 1:
next_bracket = pair_brackets[bracket_index+1] next_tier = pair_tiers[tier_index+1]
next_floor = next_bracket['min'] / next_bracket['lev'] next_floor = next_tier['min'] / next_tier['lev']
if next_floor > stake_amount: # Next bracket min too high for stake amount if next_floor > stake_amount: # Next tier min too high for stake amount
return min((bracket['max'] / stake_amount), lev) return min((tier['max'] / stake_amount), lev)
# #
# With the two leverage brackets below, # With the two leverage tiers below,
# - a stake amount of 150 would mean a max leverage of (10000 / 150) = 66.66 # - a stake amount of 150 would mean a max leverage of (10000 / 150) = 66.66
# - stakes below 133.33 = max_lev of 75 # - stakes below 133.33 = max_lev of 75
# - stakes between 133.33-200 = max_lev of 10000/stake = 50.01-74.99 # - stakes between 133.33-200 = max_lev of 10000/stake = 50.01-74.99
@ -1964,11 +1956,11 @@ class Exchange:
# } # }
# #
else: # if on the last bracket else: # if on the last tier
if stake_amount > bracket['max']: # If stake is > than max tradeable amount if stake_amount > tier['max']: # If stake is > than max tradeable amount
raise InvalidOrderException(f'Amount {stake_amount} too high for {pair}') raise InvalidOrderException(f'Amount {stake_amount} too high for {pair}')
else: else:
return bracket['lev'] return tier['lev']
raise OperationalException( raise OperationalException(
'Looped through all tiers without finding a max leverage. Should never be reached' 'Looped through all tiers without finding a max leverage. Should never be reached'
@ -2260,9 +2252,9 @@ class Exchange:
"Freqtrade only supports isolated futures for leverage trading") "Freqtrade only supports isolated futures for leverage trading")
def get_leverage_tiers_for_pair(self, pair: str): def get_leverage_tiers_for_pair(self, pair: str):
# When exchanges can load all their leverage brackets at once in the constructor # 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 # then this method does nothing, it should only be implemented when the leverage
# brackets requires per symbol fetching to avoid excess api calls # tiers requires per symbol fetching to avoid excess api calls
if not self._ft_has['can_fetch_multiple_tiers']: if not self._ft_has['can_fetch_multiple_tiers']:
try: try:
return self._api.fetch_leverage_tiers(pair) return self._api.fetch_leverage_tiers(pair)
@ -2287,22 +2279,22 @@ class Exchange:
f"nominal value is required for {self.name}.get_maintenance_ratio_and_amt" f"nominal value is required for {self.name}.get_maintenance_ratio_and_amt"
) )
if self._api.has['fetchLeverageTiers']: if self._api.has['fetchLeverageTiers']:
if pair not in self._leverage_brackets: if pair not in self._leverage_tiers:
# Used when fetchLeverageTiers cannot fetch all symbols at once # Used when fetchLeverageTiers cannot fetch all symbols at once
tiers = self.get_leverage_tiers_for_pair(pair) tiers = self.get_leverage_tiers_for_pair(pair)
if not bool(tiers): if not bool(tiers):
raise InvalidOrderException(f"Cannot calculate liquidation price for {pair}") raise InvalidOrderException(f"Cannot calculate liquidation price for {pair}")
else: else:
self._leverage_brackets[pair] = [] self._leverage_tiers[pair] = []
for tier in tiers[pair]: for tier in tiers[pair]:
self._leverage_brackets[pair].append(self.parse_leverage_tier(tier)) self._leverage_tiers[pair].append(self.parse_leverage_tier(tier))
pair_brackets = self._leverage_brackets[pair] pair_tiers = self._leverage_tiers[pair]
for bracket in reversed(pair_brackets): for tier in reversed(pair_tiers):
if nominal_value >= bracket['min']: if nominal_value >= tier['min']:
return (bracket['mmr'], bracket['maintAmt']) return (tier['mmr'], tier['maintAmt'])
raise OperationalException("nominal value can not be lower than 0") raise OperationalException("nominal value can not be lower than 0")
# The lowest notional_floor for any pair in fetch_leverage_tiers is always 0 because it # The lowest notional_floor for any pair in fetch_leverage_tiers is always 0 because it
# describes the min amt for a bracket, and the lowest bracket will always go down to 0 # describes the min amt for a tier, and the lowest tier will always go down to 0
else: else:
info = self.markets[pair]['info'] info = self.markets[pair]['info']
mmr_key = self._ft_has['mmr_key'] mmr_key = self._ft_has['mmr_key']

View File

@ -174,7 +174,7 @@ def test_get_max_leverage_binance(default_conf, mocker):
default_conf['margin_mode'] = 'isolated' default_conf['margin_mode'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._leverage_brackets = { exchange._leverage_tiers = {
'BNB/BUSD': [ 'BNB/BUSD': [
{ {
"min": 0, # stake(before leverage) = 0 "min": 0, # stake(before leverage) = 0
@ -364,9 +364,9 @@ def test_get_max_leverage_binance(default_conf, mocker):
assert isclose(exchange.get_max_leverage("BNB/BUSD", 99999.9), 5.000005) assert isclose(exchange.get_max_leverage("BNB/BUSD", 99999.9), 5.000005)
assert isclose(exchange.get_max_leverage("BNB/USDT", 1500), 33.333333333333333) assert isclose(exchange.get_max_leverage("BNB/USDT", 1500), 33.333333333333333)
assert exchange.get_max_leverage("BTC/USDT", 300000000) == 2.0 assert exchange.get_max_leverage("BTC/USDT", 300000000) == 2.0
assert exchange.get_max_leverage("BTC/USDT", 600000000) == 1.0 # Last bracket assert exchange.get_max_leverage("BTC/USDT", 600000000) == 1.0 # Last tier
assert exchange.get_max_leverage("ETC/USDT", 200) == 1.0 # Pair not in leverage_brackets assert exchange.get_max_leverage("ETC/USDT", 200) == 1.0 # Pair not in leverage_tiers
assert exchange.get_max_leverage("BTC/USDT", 0.0) == 125.0 # No stake amount assert exchange.get_max_leverage("BTC/USDT", 0.0) == 125.0 # No stake amount
with pytest.raises( with pytest.raises(
InvalidOrderException, InvalidOrderException,
@ -375,7 +375,7 @@ def test_get_max_leverage_binance(default_conf, mocker):
exchange.get_max_leverage("BTC/USDT", 1000000000.01) exchange.get_max_leverage("BTC/USDT", 1000000000.01)
def test_fill_leverage_brackets_binance(default_conf, mocker): def test_fill_leverage_tiers_binance(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_leverage_tiers = MagicMock(return_value={ api_mock.fetch_leverage_tiers = MagicMock(return_value={
'ADA/BUSD': [ 'ADA/BUSD': [
@ -583,9 +583,9 @@ def test_fill_leverage_brackets_binance(default_conf, mocker):
default_conf['trading_mode'] = TradingMode.FUTURES default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED default_conf['margin_mode'] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
exchange.fill_leverage_brackets() exchange.fill_leverage_tiers()
assert exchange._leverage_brackets == { assert exchange._leverage_tiers == {
'ADA/BUSD': [ 'ADA/BUSD': [
{ {
"min": 0, "min": 0,
@ -684,7 +684,7 @@ def test_fill_leverage_brackets_binance(default_conf, mocker):
} }
api_mock = MagicMock() api_mock = MagicMock()
api_mock.load_leverage_brackets = MagicMock() api_mock.load_leverage_tiers = MagicMock()
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
ccxt_exceptionhandlers( ccxt_exceptionhandlers(
@ -692,19 +692,19 @@ def test_fill_leverage_brackets_binance(default_conf, mocker):
default_conf, default_conf,
api_mock, api_mock,
"binance", "binance",
"fill_leverage_brackets", "fill_leverage_tiers",
"fetch_leverage_tiers" "fetch_leverage_tiers"
) )
def test_fill_leverage_brackets_binance_dryrun(default_conf, mocker): def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
default_conf['trading_mode'] = TradingMode.FUTURES default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED default_conf['margin_mode'] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
exchange.fill_leverage_brackets() exchange.fill_leverage_tiers()
leverage_brackets = { leverage_tiers = {
"1000SHIB/USDT": [ "1000SHIB/USDT": [
{ {
'min': 0, 'min': 0,
@ -904,8 +904,8 @@ def test_fill_leverage_brackets_binance_dryrun(default_conf, mocker):
] ]
} }
for key, value in leverage_brackets.items(): for key, value in leverage_tiers.items():
assert exchange._leverage_brackets[key] == value assert exchange._leverage_tiers[key] == value
def test__set_leverage_binance(mocker, default_conf): def test__set_leverage_binance(mocker, default_conf):
@ -996,7 +996,7 @@ def test_get_maintenance_ratio_and_amt_binance(
amt, amt,
): ):
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._leverage_brackets = { exchange._leverage_tiers = {
'BNB/BUSD': [ 'BNB/BUSD': [
{ {
"min": 0, # stake(before leverage) = 0 "min": 0, # stake(before leverage) = 0

View File

@ -143,7 +143,7 @@ def exchange_futures(request, exchange_conf, class_mocker):
class_mocker.patch( class_mocker.patch(
'freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') 'freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode')
class_mocker.patch( class_mocker.patch(
'freqtrade.exchange.binance.Binance.fill_leverage_brackets') 'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)