Fix liquidation price tier calculation

closes #7294
This commit is contained in:
Matthias 2022-08-26 20:04:36 +02:00
parent 753d1b2aad
commit 01126c43f7
5 changed files with 40 additions and 26 deletions

View File

@ -137,23 +137,27 @@ class Binance(Exchange):
pair: str, pair: str,
open_rate: float, # Entry price of position open_rate: float, # Entry price of position
is_short: bool, is_short: bool,
position: float, # Absolute value of position size amount: float,
stake_amount: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Important: Must be fetching data from cached values as this is used by backtesting!
MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93 PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
:param exchange_name: :param exchange_name:
:param open_rate: (EP1) Entry price of position :param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise :param is_short: True if the trade is a short, false otherwise
:param position: Absolute value of position size (in base currency) :param amount: Absolute value of position size incl. leverage (in base currency)
:param wallet_balance: (WB) :param stake_amount: Stake amount - Collateral in settle currency.
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance Isolated-Margin Mode: isolatedWalletBalance
:param maintenance_amt:
# * Only required for Cross # * Only required for Cross
:param mm_ex_1: (TMM) :param mm_ex_1: (TMM)
@ -165,12 +169,11 @@ class Binance(Exchange):
""" """
side_1 = -1 if is_short else 1 side_1 = -1 if is_short else 1
position = abs(position)
cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0 cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0
# mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100% # mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
# maintenance_amt: (CUM) Maintenance Amount of position # maintenance_amt: (CUM) Maintenance Amount of position
mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, position) mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, stake_amount)
if (maintenance_amt is None): if (maintenance_amt is None):
raise OperationalException( raise OperationalException(
@ -182,9 +185,9 @@ class Binance(Exchange):
return ( return (
( (
(wallet_balance + cross_vars + maintenance_amt) - (wallet_balance + cross_vars + maintenance_amt) -
(side_1 * position * open_rate) (side_1 * amount * open_rate)
) / ( ) / (
(position * mm_ratio) - (side_1 * position) (amount * mm_ratio) - (side_1 * amount)
) )
) )
else: else:

View File

@ -2437,6 +2437,7 @@ class Exchange:
pair: str, pair: str,
open_rate: float, open_rate: float,
amount: float, # quote currency, includes leverage amount: float, # quote currency, includes leverage
stake_amount: float,
leverage: float, leverage: float,
is_short: bool is_short: bool
) -> Optional[float]: ) -> Optional[float]:
@ -2446,13 +2447,13 @@ class Exchange:
elif ( elif (
self.trading_mode == TradingMode.FUTURES self.trading_mode == TradingMode.FUTURES
): ):
wallet_balance = (amount * open_rate) / leverage
isolated_liq = self.get_or_calculate_liquidation_price( isolated_liq = self.get_or_calculate_liquidation_price(
pair=pair, pair=pair,
open_rate=open_rate, open_rate=open_rate,
is_short=is_short, is_short=is_short,
position=amount, amount=amount,
wallet_balance=wallet_balance, stake_amount=stake_amount,
wallet_balance=stake_amount, # In isolated mode, stake-amount = wallet size
mm_ex_1=0.0, mm_ex_1=0.0,
upnl_ex_1=0.0, upnl_ex_1=0.0,
) )
@ -2627,14 +2628,14 @@ class Exchange:
# Dry-run # Dry-run
open_rate: float, # Entry price of position open_rate: float, # Entry price of position
is_short: bool, is_short: bool,
position: float, # Absolute value of position size amount: float, # Absolute value of position size
stake_amount: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Set's the margin mode on the exchange to cross or isolated for a specific pair Set's the margin mode on the exchange to cross or isolated for a specific pair
:param pair: base/quote currency pair (e.g. "ADA/USDT")
""" """
if self.trading_mode == TradingMode.SPOT: if self.trading_mode == TradingMode.SPOT:
return None return None
@ -2648,7 +2649,8 @@ class Exchange:
pair=pair, pair=pair,
open_rate=open_rate, open_rate=open_rate,
is_short=is_short, is_short=is_short,
position=position, amount=amount,
stake_amount=stake_amount,
wallet_balance=wallet_balance, wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1, mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1 upnl_ex_1=upnl_ex_1
@ -2677,22 +2679,24 @@ class Exchange:
pair: str, pair: str,
open_rate: float, # Entry price of position open_rate: float, # Entry price of position
is_short: bool, is_short: bool,
position: float, # Absolute value of position size amount: float,
stake_amount: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL: PERPETUAL:
gateio: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price gateio: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price
okex: https://www.okex.com/support/hc/en-us/articles/ okex: https://www.okex.com/support/hc/en-us/articles/
360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin 360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
Important: Must be fetching data from cached values as this is used by backtesting!
:param exchange_name: :param exchange_name:
:param open_rate: Entry price of position :param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise :param is_short: True if the trade is a short, false otherwise
:param position: Absolute value of position size incl. leverage (in base currency) :param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency.
:param trading_mode: SPOT, MARGIN, FUTURES, etc. :param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS :param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade :param wallet_balance: Amount of margin_mode in the wallet being used to trade
@ -2706,7 +2710,7 @@ class Exchange:
market = self.markets[pair] market = self.markets[pair]
taker_fee_rate = market['taker'] taker_fee_rate = market['taker']
mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, position) mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, stake_amount)
if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED: if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED:
@ -2714,7 +2718,7 @@ class Exchange:
raise OperationalException( raise OperationalException(
"Freqtrade does not yet support inverse contracts") "Freqtrade does not yet support inverse contracts")
value = wallet_balance / position value = wallet_balance / amount
mm_ratio_taker = (mm_ratio + taker_fee_rate) mm_ratio_taker = (mm_ratio + taker_fee_rate)
if is_short: if is_short:

View File

@ -1734,6 +1734,7 @@ class FreqtradeBot(LoggingMixin):
leverage=trade.leverage, leverage=trade.leverage,
pair=trade.pair, pair=trade.pair,
amount=trade.amount, amount=trade.amount,
stake_amount=trade.stake_amount,
open_rate=trade.open_rate, open_rate=trade.open_rate,
is_short=trade.is_short is_short=trade.is_short
)) ))

View File

@ -876,6 +876,7 @@ class Backtesting:
pair=pair, pair=pair,
open_rate=propose_rate, open_rate=propose_rate,
amount=amount, amount=amount,
stake_amount=trade.stake_amount,
leverage=leverage, leverage=leverage,
is_short=is_short, is_short=is_short,
)) ))

View File

@ -4132,7 +4132,8 @@ def test_get_or_calculate_liquidation_price(mocker, default_conf):
pair='NEAR/USDT:USDT', pair='NEAR/USDT:USDT',
open_rate=18.884, open_rate=18.884,
is_short=False, is_short=False,
position=0.8, amount=0.8,
stake_amount=18.884 * 0.8,
wallet_balance=0.8, wallet_balance=0.8,
) )
assert liq_price == 17.47 assert liq_price == 17.47
@ -4143,7 +4144,8 @@ def test_get_or_calculate_liquidation_price(mocker, default_conf):
pair='NEAR/USDT:USDT', pair='NEAR/USDT:USDT',
open_rate=18.884, open_rate=18.884,
is_short=False, is_short=False,
position=0.8, amount=0.8,
stake_amount=18.884 * 0.8,
wallet_balance=0.8, wallet_balance=0.8,
) )
assert liq_price == 17.540699999999998 assert liq_price == 17.540699999999998
@ -4543,7 +4545,8 @@ def test_liquidation_price_is_none(
pair='DOGE/USDT', pair='DOGE/USDT',
open_rate=open_rate, open_rate=open_rate,
is_short=is_short, is_short=is_short,
position=71200.81144, amount=71200.81144,
stake_amount=open_rate * 71200.81144,
wallet_balance=-56354.57, wallet_balance=-56354.57,
mm_ex_1=0.10, mm_ex_1=0.10,
upnl_ex_1=0.0 upnl_ex_1=0.0
@ -4552,7 +4555,7 @@ def test_liquidation_price_is_none(
@pytest.mark.parametrize( @pytest.mark.parametrize(
'exchange_name, is_short, trading_mode, margin_mode, wallet_balance, ' 'exchange_name, is_short, trading_mode, margin_mode, wallet_balance, '
'mm_ex_1, upnl_ex_1, maintenance_amt, position, open_rate, ' 'mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, '
'mm_ratio, expected', 'mm_ratio, expected',
[ [
("binance", False, 'futures', 'isolated', 1535443.01, 0.0, ("binance", False, 'futures', 'isolated', 1535443.01, 0.0,
@ -4566,7 +4569,7 @@ def test_liquidation_price_is_none(
]) ])
def test_liquidation_price( def test_liquidation_price(
mocker, default_conf, exchange_name, open_rate, is_short, trading_mode, mocker, default_conf, exchange_name, open_rate, is_short, trading_mode,
margin_mode, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, position, mm_ratio, expected margin_mode, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, amount, mm_ratio, expected
): ):
default_conf['trading_mode'] = trading_mode default_conf['trading_mode'] = trading_mode
default_conf['margin_mode'] = margin_mode default_conf['margin_mode'] = margin_mode
@ -4580,7 +4583,8 @@ def test_liquidation_price(
wallet_balance=wallet_balance, wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1, mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1, upnl_ex_1=upnl_ex_1,
position=position, amount=amount,
stake_amount=open_rate * amount,
), 2), expected) ), 2), expected)
@ -5111,6 +5115,7 @@ def test_get_liquidation_price(
pair='ETH/USDT:USDT', pair='ETH/USDT:USDT',
open_rate=open_rate, open_rate=open_rate,
amount=amount, amount=amount,
stake_amount=amount * open_rate / leverage,
leverage=leverage, leverage=leverage,
is_short=is_short, is_short=is_short,
) )