redid get_max_leverage

This commit is contained in:
Sam Germain 2022-02-05 22:36:28 -06:00
parent 720a86778e
commit a99cf2eeed
8 changed files with 17440 additions and 1409 deletions

View File

@ -153,27 +153,24 @@ class Binance(Exchange):
try: try:
if self._config['dry_run']: if self._config['dry_run']:
leverage_brackets_path = ( leverage_brackets_path = (
Path(__file__).parent / 'binance_leverage_brackets.json' Path(__file__).parent / 'binance_leverage_tiers.json'
) )
with open(leverage_brackets_path) as json_file: with open(leverage_brackets_path) as json_file:
leverage_brackets = json.load(json_file) leverage_brackets = json.load(json_file)
else: else:
leverage_brackets = self._api.load_leverage_brackets() leverage_brackets = self._api.fetch_leverage_tiers()
for pair, brkts in leverage_brackets.items(): for pair, tiers in leverage_brackets.items():
[amt, old_ratio] = [0.0, 0.0]
brackets = [] brackets = []
for [notional_floor, mm_ratio] in brkts: for tier in tiers:
amt = ( info = tier['info']
(float(notional_floor) * (float(mm_ratio) - float(old_ratio))) brackets.append({
+ amt 'min': tier['notionalFloor'],
) if old_ratio else 0.0 'max': tier['notionalCap'],
old_ratio = mm_ratio 'mmr': tier['maintenanceMarginRatio'],
brackets.append(( 'lev': tier['maxLeverage'],
float(notional_floor), 'maintAmt': float(info['cum']) if 'cum' in info else None,
float(mm_ratio), })
amt,
))
self._leverage_brackets[pair] = brackets self._leverage_brackets[pair] = brackets
except ccxt.DDoSProtection as e: except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e raise DDosProtection(e) from e
@ -189,29 +186,69 @@ class Binance(Exchange):
:param pair: The base/quote currency pair being traded :param pair: The base/quote currency pair being traded
:stake_amount: The total value of the traders margin_mode in quote currency :stake_amount: The total value of the traders margin_mode in quote currency
""" """
if stake_amount is None:
raise OperationalException('binance.get_max_leverage requires argument stake_amount') if self.trading_mode == TradingMode.SPOT:
if pair not in self._leverage_brackets:
return 1.0 return 1.0
pair_brackets = self._leverage_brackets[pair]
num_brackets = len(pair_brackets) if self._api.has['fetchLeverageTiers']:
min_amount = 0.0
for bracket_num in range(num_brackets): # Checks and edge cases
[notional_floor, mm_ratio, _] = pair_brackets[bracket_num] if stake_amount is None:
lev = 1.0 raise OperationalException(
if mm_ratio != 0: 'binance.get_max_leverage requires argument stake_amount')
lev = 1.0/mm_ratio if pair not in self._leverage_brackets: # Not a leveraged market
return 1.0
if stake_amount == 0:
return self._leverage_brackets[pair][0]['lev'] # Max lev for lowest amount
pair_brackets = self._leverage_brackets[pair]
num_brackets = len(pair_brackets)
for bracket_index in range(num_brackets):
bracket = pair_brackets[bracket_index]
lev = bracket['lev']
if bracket_index < num_brackets - 1:
next_bracket = pair_brackets[bracket_index+1]
next_floor = next_bracket['min'] / next_bracket['lev']
if next_floor > stake_amount: # Next bracket min too high for stake amount
return min((bracket['max'] / stake_amount), lev)
#
# With the two leverage brackets below,
# - a stake amount of 150 would mean a max leverage of (10000 / 150) = 66.66
# - stakes below 133.33 = max_lev of 75
# - stakes between 133.33-200 = max_lev of 10000/stake = 50.01-74.99
# - stakes from 200 + 1000 = max_lev of 50
#
# {
# "min": 0, # stake = 0.0
# "max": 10000, # max_stake@75 = 10000/75 = 133.33333333333334
# "lev": 75,
# },
# {
# "min": 10000, # stake = 200.0
# "max": 50000, # max_stake@50 = 50000/50 = 1000.0
# "lev": 50,
# }
#
else: # if on the last bracket
if stake_amount > bracket['max']: # If stake is > than max tradeable amount
raise InvalidOrderException(f'Amount {stake_amount} too high for {pair}')
else:
return bracket['lev']
raise OperationalException(
'Looped through all tiers without finding a max leverage. Should never be reached'
)
else: # Search markets.limits for max lev
market = self.markets[pair]
if market['limits']['leverage']['max'] is not None:
return market['limits']['leverage']['max']
else: else:
logger.warning(f"mm_ratio for {pair} with notional floor {notional_floor} is 0") return 1.0 # Default if max leverage cannot be found
if bracket_num+1 != num_brackets: # If not on last bracket
[min_amount, _, __] = pair_brackets[bracket_num+1] # Get min_amount of next bracket
else:
return lev
nominal_value = stake_amount * lev
# Bracket is good if the leveraged trade value doesnt exceed min_amount of next bracket
if nominal_value < min_amount:
return lev
return 1.0 # default leverage
@retrier @retrier
def _set_leverage( def _set_leverage(

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -91,7 +91,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[Tuple[float, float, Optional(float)]]] = {} self._leverage_brackets: 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)
@ -2193,7 +2193,7 @@ class Exchange:
if nominal_value >= notional_floor: if nominal_value >= notional_floor:
return (mm_ratio, amt) return (mm_ratio, amt)
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 loadLeverageBrackets 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 bracket, and the lowest bracket will always go down to 0
else: else:
info = self.markets[pair]['info'] info = self.markets[pair]['info']

File diff suppressed because it is too large Load Diff

View File

@ -3486,9 +3486,9 @@ def test_set_margin_mode(mocker, default_conf, margin_mode):
("binance", TradingMode.FUTURES, MarginMode.ISOLATED, False), ("binance", TradingMode.FUTURES, MarginMode.ISOLATED, False),
("gateio", TradingMode.FUTURES, MarginMode.ISOLATED, False), ("gateio", TradingMode.FUTURES, MarginMode.ISOLATED, False),
("okex", TradingMode.FUTURES, MarginMode.ISOLATED, False),
# * Remove once implemented # * Remove once implemented
("okx", TradingMode.FUTURES, MarginMode.ISOLATED, True),
("binance", TradingMode.MARGIN, MarginMode.CROSS, True), ("binance", TradingMode.MARGIN, MarginMode.CROSS, True),
("binance", TradingMode.FUTURES, MarginMode.CROSS, True), ("binance", TradingMode.FUTURES, MarginMode.CROSS, True),
("kraken", TradingMode.MARGIN, MarginMode.CROSS, True), ("kraken", TradingMode.MARGIN, MarginMode.CROSS, True),
@ -3499,7 +3499,6 @@ def test_set_margin_mode(mocker, default_conf, margin_mode):
("gateio", TradingMode.FUTURES, MarginMode.CROSS, True), ("gateio", TradingMode.FUTURES, MarginMode.CROSS, True),
# * Uncomment once implemented # * Uncomment once implemented
# ("okx", TradingMode.FUTURES, MarginMode.ISOLATED, False),
# ("binance", TradingMode.MARGIN, MarginMode.CROSS, False), # ("binance", TradingMode.MARGIN, MarginMode.CROSS, False),
# ("binance", TradingMode.FUTURES, MarginMode.CROSS, False), # ("binance", TradingMode.FUTURES, MarginMode.CROSS, False),
# ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False), # ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False),

View File

@ -24,25 +24,25 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re
def generate_result_metrics(): def generate_result_metrics():
return { return {
'trade_count': 1, 'trade_count': 1,
'total_trades': 1, 'total_trades': 1,
'avg_profit': 0.1, 'avg_profit': 0.1,
'total_profit': 0.001, 'total_profit': 0.001,
'profit': 0.01, 'profit': 0.01,
'duration': 20.0, 'duration': 20.0,
'wins': 1, 'wins': 1,
'draws': 0, 'draws': 0,
'losses': 0, 'losses': 0,
'profit_mean': 0.01, 'profit_mean': 0.01,
'profit_total_abs': 0.001, 'profit_total_abs': 0.001,
'profit_total': 0.01, 'profit_total': 0.01,
'holding_avg': timedelta(minutes=20), 'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001, 'max_drawdown': 0.001,
'max_drawdown_abs': 0.001, 'max_drawdown_abs': 0.001,
'loss': 0.001, 'loss': 0.001,
'is_initial_point': 0.001, 'is_initial_point': 0.001,
'is_best': 1, 'is_best': 1,
} }
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@ -852,6 +852,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
'spaces': ['all'] 'spaces': ['all']
}) })
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)

View File

@ -5085,6 +5085,7 @@ def test_update_funding_fees(
create_order=enter_mm, create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1), get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee, get_fee=fee,
get_maintenance_ratio_and_amt=MagicMock(return_value=(0.01, 0.01)),
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)