mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-01-29 02:10:24 +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)
|
return 1 / pow(10, precision)
|
||||||
|
|
||||||
def get_min_pair_stake_amount(
|
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:
|
) -> float | None:
|
||||||
return self._get_stake_amount_limit(pair, price, stoploss, "min", leverage)
|
return self._get_stake_amount_limit(pair, price, stoploss, "min", leverage)
|
||||||
|
|
||||||
@@ -980,7 +980,7 @@ class Exchange:
|
|||||||
price: float,
|
price: float,
|
||||||
stoploss: float,
|
stoploss: float,
|
||||||
limit: Literal["min", "max"],
|
limit: Literal["min", "max"],
|
||||||
leverage: float | None = 1.0,
|
leverage: float = 1.0,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
isMin = limit == "min"
|
isMin = limit == "min"
|
||||||
|
|
||||||
@@ -989,6 +989,8 @@ class Exchange:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(f"Can't get market information for symbol {pair}")
|
raise ValueError(f"Can't get market information for symbol {pair}")
|
||||||
|
|
||||||
|
stake_limits = []
|
||||||
|
limits = market["limits"]
|
||||||
if isMin:
|
if isMin:
|
||||||
# reserve some percent defined in config (5% default) + stoploss
|
# reserve some percent defined in config (5% default) + stoploss
|
||||||
margin_reserve: float = 1.0 + self._config.get(
|
margin_reserve: float = 1.0 + self._config.get(
|
||||||
@@ -998,11 +1000,12 @@ class Exchange:
|
|||||||
# it should not be more than 50%
|
# it should not be more than 50%
|
||||||
stoploss_reserve = max(min(stoploss_reserve, 1.5), 1)
|
stoploss_reserve = max(min(stoploss_reserve, 1.5), 1)
|
||||||
else:
|
else:
|
||||||
|
# is_max
|
||||||
margin_reserve = 1.0
|
margin_reserve = 1.0
|
||||||
stoploss_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:
|
if limits["cost"][limit] is not None:
|
||||||
stake_limits.append(
|
stake_limits.append(
|
||||||
self._contracts_to_amount(pair, limits["cost"][limit]) * stoploss_reserve
|
self._contracts_to_amount(pair, limits["cost"][limit]) * stoploss_reserve
|
||||||
@@ -3361,42 +3364,22 @@ class Exchange:
|
|||||||
pair_tiers = self._leverage_tiers[pair]
|
pair_tiers = self._leverage_tiers[pair]
|
||||||
|
|
||||||
if stake_amount == 0:
|
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)):
|
# Find the appropriate tier based on stake_amount
|
||||||
tier = pair_tiers[tier_index]
|
prior_max_lev = None
|
||||||
lev = tier["maxLeverage"]
|
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:
|
# else: # if on the last tier
|
||||||
next_tier = pair_tiers[tier_index + 1]
|
if stake_amount > max_stake:
|
||||||
next_floor = next_tier["minNotional"] / next_tier["maxLeverage"]
|
# If stake is > than max tradeable amount
|
||||||
if next_floor > stake_amount: # Next tier min too high for stake amount
|
raise InvalidOrderException(f"Amount {stake_amount} too high for {pair}")
|
||||||
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"]
|
|
||||||
|
|
||||||
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"
|
||||||
@@ -3411,6 +3394,23 @@ class Exchange:
|
|||||||
else:
|
else:
|
||||||
return 1.0
|
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
|
@retrier
|
||||||
def _set_leverage(
|
def _set_leverage(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -760,12 +760,14 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
current_exit_profit = trade.calc_profit_ratio(current_exit_rate)
|
current_exit_profit = trade.calc_profit_ratio(current_exit_rate)
|
||||||
|
|
||||||
min_entry_stake = self.exchange.get_min_pair_stake_amount(
|
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(
|
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()
|
stake_available = self.wallets.get_available_stake_amount()
|
||||||
logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
|
logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
|
||||||
stake_amount, order_tag = self.strategy._adjust_trade_position_internal(
|
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(
|
def test_get_max_pair_stake_amount(
|
||||||
mocker,
|
mocker,
|
||||||
default_conf,
|
default_conf,
|
||||||
|
leverage_tiers,
|
||||||
):
|
):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
default_conf["margin_mode"] = "isolated"
|
default_conf["margin_mode"] = "isolated"
|
||||||
default_conf["trading_mode"] = "futures"
|
default_conf["trading_mode"] = "futures"
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
exchange._leverage_tiers = leverage_tiers
|
||||||
markets = {
|
markets = {
|
||||||
"XRP/USDT:USDT": {
|
"XRP/USDT:USDT": {
|
||||||
"limits": {
|
"limits": {
|
||||||
@@ -5667,11 +5669,23 @@ def test_get_max_pair_stake_amount(
|
|||||||
"contractSize": 0.01,
|
"contractSize": 0.01,
|
||||||
"spot": False,
|
"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)
|
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) == 20000
|
||||||
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0, 5) == 4000
|
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("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("ETH/USDT:USDT", 2.0) == 200
|
||||||
assert exchange.get_max_pair_stake_amount("DOGE/USDT:USDT", 2.0) == 500
|
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("XRP/USDT:USDT", 1.0) == 20.0
|
||||||
assert exchange.get_max_leverage("BNB/USDT:USDT", 100.0) == 75.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 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("XRP/USDT:USDT", 99999.9)) == 5
|
||||||
assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 33.333333333333333
|
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", 300000000) == 2.0
|
||||||
assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier
|
assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user