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:
if self._config['dry_run']:
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:
leverage_brackets = json.load(json_file)
else:
leverage_brackets = self._api.load_leverage_brackets()
leverage_brackets = self._api.fetch_leverage_tiers()
for pair, brkts in leverage_brackets.items():
[amt, old_ratio] = [0.0, 0.0]
for pair, tiers in leverage_brackets.items():
brackets = []
for [notional_floor, mm_ratio] in brkts:
amt = (
(float(notional_floor) * (float(mm_ratio) - float(old_ratio)))
+ amt
) if old_ratio else 0.0
old_ratio = mm_ratio
brackets.append((
float(notional_floor),
float(mm_ratio),
amt,
))
for tier in tiers:
info = tier['info']
brackets.append({
'min': tier['notionalFloor'],
'max': tier['notionalCap'],
'mmr': tier['maintenanceMarginRatio'],
'lev': tier['maxLeverage'],
'maintAmt': float(info['cum']) if 'cum' in info else None,
})
self._leverage_brackets[pair] = brackets
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
@ -189,29 +186,69 @@ class Binance(Exchange):
:param pair: The base/quote currency pair being traded
: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 pair not in self._leverage_brackets:
if self.trading_mode == TradingMode.SPOT:
return 1.0
pair_brackets = self._leverage_brackets[pair]
num_brackets = len(pair_brackets)
min_amount = 0.0
for bracket_num in range(num_brackets):
[notional_floor, mm_ratio, _] = pair_brackets[bracket_num]
lev = 1.0
if mm_ratio != 0:
lev = 1.0/mm_ratio
if self._api.has['fetchLeverageTiers']:
# Checks and edge cases
if stake_amount is None:
raise OperationalException(
'binance.get_max_leverage requires argument stake_amount')
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:
logger.warning(f"mm_ratio for {pair} with notional floor {notional_floor} is 0")
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
return 1.0 # Default if max leverage cannot be found
@retrier
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_async: ccxt_async.Exchange = None
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()
asyncio.set_event_loop(self.loop)
@ -2193,7 +2193,7 @@ class Exchange:
if nominal_value >= notional_floor:
return (mm_ratio, amt)
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
else:
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),
("gateio", TradingMode.FUTURES, MarginMode.ISOLATED, False),
("okex", TradingMode.FUTURES, MarginMode.ISOLATED, False),
# * Remove once implemented
("okx", TradingMode.FUTURES, MarginMode.ISOLATED, True),
("binance", TradingMode.MARGIN, MarginMode.CROSS, True),
("binance", TradingMode.FUTURES, 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),
# * Uncomment once implemented
# ("okx", TradingMode.FUTURES, MarginMode.ISOLATED, False),
# ("binance", TradingMode.MARGIN, MarginMode.CROSS, False),
# ("binance", TradingMode.FUTURES, 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():
return {
'trade_count': 1,
'total_trades': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 0.01,
'duration': 20.0,
'wins': 1,
'draws': 0,
'losses': 0,
'profit_mean': 0.01,
'profit_total_abs': 0.001,
'profit_total': 0.01,
'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001,
'max_drawdown_abs': 0.001,
'loss': 0.001,
'is_initial_point': 0.001,
'is_best': 1,
}
'trade_count': 1,
'total_trades': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 0.01,
'duration': 20.0,
'wins': 1,
'draws': 0,
'losses': 0,
'profit_mean': 0.01,
'profit_total_abs': 0.001,
'profit_total': 0.01,
'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001,
'max_drawdown_abs': 0.001,
'loss': 0.001,
'is_initial_point': 0.001,
'is_best': 1,
}
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']
})
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)

View File

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