mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-03 18:43:04 +00:00
Merge pull request #11655 from freqtrade/fix/11650
Fix max-stake-amount with high leverage
This commit is contained in:
@@ -961,7 +961,7 @@ class Exchange:
|
||||
return 1 / pow(10, precision)
|
||||
|
||||
def get_min_pair_stake_amount(
|
||||
self, pair: str, price: float, stoploss: float, leverage: float | None = 1.0
|
||||
self, pair: str, price: float, stoploss: float, leverage: float = 1.0
|
||||
) -> float | None:
|
||||
return self._get_stake_amount_limit(pair, price, stoploss, "min", leverage)
|
||||
|
||||
@@ -980,7 +980,7 @@ class Exchange:
|
||||
price: float,
|
||||
stoploss: float,
|
||||
limit: Literal["min", "max"],
|
||||
leverage: float | None = 1.0,
|
||||
leverage: float = 1.0,
|
||||
) -> float | None:
|
||||
isMin = limit == "min"
|
||||
|
||||
@@ -989,6 +989,8 @@ class Exchange:
|
||||
except KeyError:
|
||||
raise ValueError(f"Can't get market information for symbol {pair}")
|
||||
|
||||
stake_limits = []
|
||||
limits = market["limits"]
|
||||
if isMin:
|
||||
# reserve some percent defined in config (5% default) + stoploss
|
||||
margin_reserve: float = 1.0 + self._config.get(
|
||||
@@ -998,11 +1000,12 @@ class Exchange:
|
||||
# it should not be more than 50%
|
||||
stoploss_reserve = max(min(stoploss_reserve, 1.5), 1)
|
||||
else:
|
||||
# is_max
|
||||
margin_reserve = 1.0
|
||||
stoploss_reserve = 1.0
|
||||
if max_from_tiers := self._get_max_notional_from_tiers(pair, leverage=leverage):
|
||||
stake_limits.append(max_from_tiers)
|
||||
|
||||
stake_limits = []
|
||||
limits = market["limits"]
|
||||
if limits["cost"][limit] is not None:
|
||||
stake_limits.append(
|
||||
self._contracts_to_amount(pair, limits["cost"][limit]) * stoploss_reserve
|
||||
@@ -3361,42 +3364,22 @@ class Exchange:
|
||||
pair_tiers = self._leverage_tiers[pair]
|
||||
|
||||
if stake_amount == 0:
|
||||
return self._leverage_tiers[pair][0]["maxLeverage"] # Max lev for lowest amount
|
||||
return pair_tiers[0]["maxLeverage"] # Max lev for lowest amount
|
||||
|
||||
for tier_index in range(len(pair_tiers)):
|
||||
tier = pair_tiers[tier_index]
|
||||
lev = tier["maxLeverage"]
|
||||
# Find the appropriate tier based on stake_amount
|
||||
prior_max_lev = None
|
||||
for tier in pair_tiers:
|
||||
min_stake = tier["minNotional"] / (prior_max_lev or tier["maxLeverage"])
|
||||
max_stake = tier["maxNotional"] / tier["maxLeverage"]
|
||||
prior_max_lev = tier["maxLeverage"]
|
||||
# Adjust notional by leverage to do a proper comparison
|
||||
if min_stake <= stake_amount <= max_stake:
|
||||
return tier["maxLeverage"]
|
||||
|
||||
if tier_index < len(pair_tiers) - 1:
|
||||
next_tier = pair_tiers[tier_index + 1]
|
||||
next_floor = next_tier["minNotional"] / next_tier["maxLeverage"]
|
||||
if next_floor > stake_amount: # Next tier min too high for stake amount
|
||||
return min((tier["maxNotional"] / stake_amount), lev)
|
||||
#
|
||||
# With the two leverage tiers 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 tier
|
||||
if stake_amount > tier["maxNotional"]:
|
||||
# If stake is > than max tradeable amount
|
||||
raise InvalidOrderException(f"Amount {stake_amount} too high for {pair}")
|
||||
else:
|
||||
return tier["maxLeverage"]
|
||||
# else: # if on the last tier
|
||||
if stake_amount > max_stake:
|
||||
# If stake is > than max tradeable amount
|
||||
raise InvalidOrderException(f"Amount {stake_amount} too high for {pair}")
|
||||
|
||||
raise OperationalException(
|
||||
"Looped through all tiers without finding a max leverage. Should never be reached"
|
||||
@@ -3411,6 +3394,23 @@ class Exchange:
|
||||
else:
|
||||
return 1.0
|
||||
|
||||
def _get_max_notional_from_tiers(self, pair: str, leverage: float) -> float | None:
|
||||
"""
|
||||
get max_notional from leverage_tiers
|
||||
:param pair: The base/quote currency pair being traded
|
||||
:param leverage: The leverage to be used
|
||||
:return: The maximum notional value for the given leverage or None if not found
|
||||
"""
|
||||
if self.trading_mode != TradingMode.FUTURES:
|
||||
return None
|
||||
if pair not in self._leverage_tiers:
|
||||
return None
|
||||
pair_tiers = self._leverage_tiers[pair]
|
||||
for tier in reversed(pair_tiers):
|
||||
if leverage <= tier["maxLeverage"]:
|
||||
return tier["maxNotional"]
|
||||
return None
|
||||
|
||||
@retrier
|
||||
def _set_leverage(
|
||||
self,
|
||||
|
||||
@@ -760,12 +760,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
current_exit_profit = trade.calc_profit_ratio(current_exit_rate)
|
||||
|
||||
min_entry_stake = self.exchange.get_min_pair_stake_amount(
|
||||
trade.pair, current_entry_rate, 0.0
|
||||
trade.pair, current_entry_rate, 0.0, trade.leverage
|
||||
)
|
||||
min_exit_stake = self.exchange.get_min_pair_stake_amount(
|
||||
trade.pair, current_exit_rate, self.strategy.stoploss
|
||||
trade.pair, current_exit_rate, self.strategy.stoploss, trade.leverage
|
||||
)
|
||||
max_entry_stake = self.exchange.get_max_pair_stake_amount(
|
||||
trade.pair, current_entry_rate, trade.leverage
|
||||
)
|
||||
max_entry_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_entry_rate)
|
||||
stake_available = self.wallets.get_available_stake_amount()
|
||||
logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
|
||||
stake_amount, order_tag = self.strategy._adjust_trade_position_internal(
|
||||
|
||||
@@ -5599,11 +5599,13 @@ def test_liquidation_price_is_none(
|
||||
def test_get_max_pair_stake_amount(
|
||||
mocker,
|
||||
default_conf,
|
||||
leverage_tiers,
|
||||
):
|
||||
api_mock = MagicMock()
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
default_conf["trading_mode"] = "futures"
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange._leverage_tiers = leverage_tiers
|
||||
markets = {
|
||||
"XRP/USDT:USDT": {
|
||||
"limits": {
|
||||
@@ -5667,11 +5669,23 @@ def test_get_max_pair_stake_amount(
|
||||
"contractSize": 0.01,
|
||||
"spot": False,
|
||||
},
|
||||
"ZEC/USDT:USDT": {
|
||||
"limits": {
|
||||
"amount": {"min": 0.001, "max": None},
|
||||
"cost": {"min": 5, "max": None},
|
||||
},
|
||||
"contractSize": 1,
|
||||
"spot": False,
|
||||
},
|
||||
}
|
||||
|
||||
mocker.patch(f"{EXMS}.markets", markets)
|
||||
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0) == 20000
|
||||
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0, 5) == 4000
|
||||
# limit leverage tiers
|
||||
assert exchange.get_max_pair_stake_amount("ZEC/USDT:USDT", 2.0, 5) == 100_000
|
||||
assert exchange.get_max_pair_stake_amount("ZEC/USDT:USDT", 2.0, 50) == 1000
|
||||
|
||||
assert exchange.get_max_pair_stake_amount("LTC/USDT:USDT", 2.0) == float("inf")
|
||||
assert exchange.get_max_pair_stake_amount("ETH/USDT:USDT", 2.0) == 200
|
||||
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):
|
||||
assert exchange.get_max_leverage("XRP/USDT:USDT", 1.0) == 20.0
|
||||
assert exchange.get_max_leverage("BNB/USDT:USDT", 100.0) == 75.0
|
||||
assert exchange.get_max_leverage("BTC/USDT:USDT", 170.30) == 125.0
|
||||
assert pytest.approx(exchange.get_max_leverage("XRP/USDT:USDT", 99999.9)) == 5.000005
|
||||
assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 33.333333333333333
|
||||
assert pytest.approx(exchange.get_max_leverage("XRP/USDT:USDT", 99999.9)) == 5
|
||||
assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 25
|
||||
assert exchange.get_max_leverage("BTC/USDT:USDT", 300000000) == 2.0
|
||||
assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier
|
||||
|
||||
|
||||
Reference in New Issue
Block a user