Skip to content

Commit 0c6ed58

Browse files
authored
Merge pull request freqtrade#11655 from freqtrade/fix/11650
Fix max-stake-amount with high leverage
2 parents 45dc667 + 1e1c9a2 commit 0c6ed58

File tree

3 files changed

+61
-45
lines changed

3 files changed

+61
-45
lines changed

freqtrade/exchange/exchange.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,7 @@ def price_get_one_pip(self, pair: str, price: float) -> float:
961961
return 1 / pow(10, precision)
962962

963963
def get_min_pair_stake_amount(
964-
self, pair: str, price: float, stoploss: float, leverage: float | None = 1.0
964+
self, pair: str, price: float, stoploss: float, leverage: float = 1.0
965965
) -> float | None:
966966
return self._get_stake_amount_limit(pair, price, stoploss, "min", leverage)
967967

@@ -980,7 +980,7 @@ def _get_stake_amount_limit(
980980
price: float,
981981
stoploss: float,
982982
limit: Literal["min", "max"],
983-
leverage: float | None = 1.0,
983+
leverage: float = 1.0,
984984
) -> float | None:
985985
isMin = limit == "min"
986986

@@ -989,6 +989,8 @@ def _get_stake_amount_limit(
989989
except KeyError:
990990
raise ValueError(f"Can't get market information for symbol {pair}")
991991

992+
stake_limits = []
993+
limits = market["limits"]
992994
if isMin:
993995
# reserve some percent defined in config (5% default) + stoploss
994996
margin_reserve: float = 1.0 + self._config.get(
@@ -998,11 +1000,12 @@ def _get_stake_amount_limit(
9981000
# it should not be more than 50%
9991001
stoploss_reserve = max(min(stoploss_reserve, 1.5), 1)
10001002
else:
1003+
# is_max
10011004
margin_reserve = 1.0
10021005
stoploss_reserve = 1.0
1006+
if max_from_tiers := self._get_max_notional_from_tiers(pair, leverage=leverage):
1007+
stake_limits.append(max_from_tiers)
10031008

1004-
stake_limits = []
1005-
limits = market["limits"]
10061009
if limits["cost"][limit] is not None:
10071010
stake_limits.append(
10081011
self._contracts_to_amount(pair, limits["cost"][limit]) * stoploss_reserve
@@ -3361,42 +3364,22 @@ def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
33613364
pair_tiers = self._leverage_tiers[pair]
33623365

33633366
if stake_amount == 0:
3364-
return self._leverage_tiers[pair][0]["maxLeverage"] # Max lev for lowest amount
3365-
3366-
for tier_index in range(len(pair_tiers)):
3367-
tier = pair_tiers[tier_index]
3368-
lev = tier["maxLeverage"]
3369-
3370-
if tier_index < len(pair_tiers) - 1:
3371-
next_tier = pair_tiers[tier_index + 1]
3372-
next_floor = next_tier["minNotional"] / next_tier["maxLeverage"]
3373-
if next_floor > stake_amount: # Next tier min too high for stake amount
3374-
return min((tier["maxNotional"] / stake_amount), lev)
3375-
#
3376-
# With the two leverage tiers below,
3377-
# - a stake amount of 150 would mean a max leverage of (10000 / 150) = 66.66
3378-
# - stakes below 133.33 = max_lev of 75
3379-
# - stakes between 133.33-200 = max_lev of 10000/stake = 50.01-74.99
3380-
# - stakes from 200 + 1000 = max_lev of 50
3381-
#
3382-
# {
3383-
# "min": 0, # stake = 0.0
3384-
# "max": 10000, # max_stake@75 = 10000/75 = 133.33333333333334
3385-
# "lev": 75,
3386-
# },
3387-
# {
3388-
# "min": 10000, # stake = 200.0
3389-
# "max": 50000, # max_stake@50 = 50000/50 = 1000.0
3390-
# "lev": 50,
3391-
# }
3392-
#
3393-
3394-
else: # if on the last tier
3395-
if stake_amount > tier["maxNotional"]:
3396-
# If stake is > than max tradeable amount
3397-
raise InvalidOrderException(f"Amount {stake_amount} too high for {pair}")
3398-
else:
3399-
return tier["maxLeverage"]
3367+
return pair_tiers[0]["maxLeverage"] # Max lev for lowest amount
3368+
3369+
# Find the appropriate tier based on stake_amount
3370+
prior_max_lev = None
3371+
for tier in pair_tiers:
3372+
min_stake = tier["minNotional"] / (prior_max_lev or tier["maxLeverage"])
3373+
max_stake = tier["maxNotional"] / tier["maxLeverage"]
3374+
prior_max_lev = tier["maxLeverage"]
3375+
# Adjust notional by leverage to do a proper comparison
3376+
if min_stake <= stake_amount <= max_stake:
3377+
return tier["maxLeverage"]
3378+
3379+
# else: # if on the last tier
3380+
if stake_amount > max_stake:
3381+
# If stake is > than max tradeable amount
3382+
raise InvalidOrderException(f"Amount {stake_amount} too high for {pair}")
34003383

34013384
raise OperationalException(
34023385
"Looped through all tiers without finding a max leverage. Should never be reached"
@@ -3411,6 +3394,23 @@ def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
34113394
else:
34123395
return 1.0
34133396

3397+
def _get_max_notional_from_tiers(self, pair: str, leverage: float) -> float | None:
3398+
"""
3399+
get max_notional from leverage_tiers
3400+
:param pair: The base/quote currency pair being traded
3401+
:param leverage: The leverage to be used
3402+
:return: The maximum notional value for the given leverage or None if not found
3403+
"""
3404+
if self.trading_mode != TradingMode.FUTURES:
3405+
return None
3406+
if pair not in self._leverage_tiers:
3407+
return None
3408+
pair_tiers = self._leverage_tiers[pair]
3409+
for tier in reversed(pair_tiers):
3410+
if leverage <= tier["maxLeverage"]:
3411+
return tier["maxNotional"]
3412+
return None
3413+
34143414
@retrier
34153415
def _set_leverage(
34163416
self,

freqtrade/freqtradebot.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,12 +760,14 @@ def check_and_call_adjust_trade_position(self, trade: Trade):
760760
current_exit_profit = trade.calc_profit_ratio(current_exit_rate)
761761

762762
min_entry_stake = self.exchange.get_min_pair_stake_amount(
763-
trade.pair, current_entry_rate, 0.0
763+
trade.pair, current_entry_rate, 0.0, trade.leverage
764764
)
765765
min_exit_stake = self.exchange.get_min_pair_stake_amount(
766-
trade.pair, current_exit_rate, self.strategy.stoploss
766+
trade.pair, current_exit_rate, self.strategy.stoploss, trade.leverage
767+
)
768+
max_entry_stake = self.exchange.get_max_pair_stake_amount(
769+
trade.pair, current_entry_rate, trade.leverage
767770
)
768-
max_entry_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_entry_rate)
769771
stake_available = self.wallets.get_available_stake_amount()
770772
logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
771773
stake_amount, order_tag = self.strategy._adjust_trade_position_internal(

tests/exchange/test_exchange.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5599,11 +5599,13 @@ def test_liquidation_price_is_none(
55995599
def test_get_max_pair_stake_amount(
56005600
mocker,
56015601
default_conf,
5602+
leverage_tiers,
56025603
):
56035604
api_mock = MagicMock()
56045605
default_conf["margin_mode"] = "isolated"
56055606
default_conf["trading_mode"] = "futures"
56065607
exchange = get_patched_exchange(mocker, default_conf, api_mock)
5608+
exchange._leverage_tiers = leverage_tiers
56075609
markets = {
56085610
"XRP/USDT:USDT": {
56095611
"limits": {
@@ -5667,11 +5669,23 @@ def test_get_max_pair_stake_amount(
56675669
"contractSize": 0.01,
56685670
"spot": False,
56695671
},
5672+
"ZEC/USDT:USDT": {
5673+
"limits": {
5674+
"amount": {"min": 0.001, "max": None},
5675+
"cost": {"min": 5, "max": None},
5676+
},
5677+
"contractSize": 1,
5678+
"spot": False,
5679+
},
56705680
}
56715681

56725682
mocker.patch(f"{EXMS}.markets", markets)
56735683
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0) == 20000
56745684
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0, 5) == 4000
5685+
# limit leverage tiers
5686+
assert exchange.get_max_pair_stake_amount("ZEC/USDT:USDT", 2.0, 5) == 100_000
5687+
assert exchange.get_max_pair_stake_amount("ZEC/USDT:USDT", 2.0, 50) == 1000
5688+
56755689
assert exchange.get_max_pair_stake_amount("LTC/USDT:USDT", 2.0) == float("inf")
56765690
assert exchange.get_max_pair_stake_amount("ETH/USDT:USDT", 2.0) == 200
56775691
assert exchange.get_max_pair_stake_amount("DOGE/USDT:USDT", 2.0) == 500
@@ -5902,8 +5916,8 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
59025916
assert exchange.get_max_leverage("XRP/USDT:USDT", 1.0) == 20.0
59035917
assert exchange.get_max_leverage("BNB/USDT:USDT", 100.0) == 75.0
59045918
assert exchange.get_max_leverage("BTC/USDT:USDT", 170.30) == 125.0
5905-
assert pytest.approx(exchange.get_max_leverage("XRP/USDT:USDT", 99999.9)) == 5.000005
5906-
assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 33.333333333333333
5919+
assert pytest.approx(exchange.get_max_leverage("XRP/USDT:USDT", 99999.9)) == 5
5920+
assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 25
59075921
assert exchange.get_max_leverage("BTC/USDT:USDT", 300000000) == 2.0
59085922
assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier
59095923

0 commit comments

Comments
 (0)