cleaned up liquidation price methods

This commit is contained in:
Sam Germain 2022-01-29 02:06:56 -06:00
parent ede9012fcc
commit 143c37d36f
5 changed files with 79 additions and 191 deletions

View File

@ -277,18 +277,15 @@ class Binance(Exchange):
# The lowest notional_floor for any pair in loadLeverageBrackets is always 0 because it
# describes the min amount for a bracket, and the lowest bracket will always go down to 0
def liquidation_price_helper(
def liquidation_price(
self,
open_rate: float, # Entry price of position
is_short: bool,
leverage: float,
mm_ratio: float,
position: float, # Absolute value of position size
trading_mode: TradingMode,
collateral: Collateral,
maintenance_amt: Optional[float] = None, # (Binance)
wallet_balance: Optional[float] = None, # (Binance and Gateio)
wallet_balance: float, # Or margin balance
taker_fee_rate: Optional[float] = None, # (Gateio & Okex)
maintenance_amt: Optional[float] = None, # (Binance)
mm_ex_1: Optional[float] = 0.0, # (Binance) Cross only
upnl_ex_1: Optional[float] = 0.0, # (Binance) Cross only
) -> Optional[float]:
@ -299,19 +296,15 @@ class Binance(Exchange):
:param exchange_name:
:param open_rate: (EP1) Entry price of position
:param is_short: True if the trade is a short, false otherwise
:param leverage: The amount of leverage on the trade
:param mm_ratio: (MMR)
# Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
:param position: Absolute value of position size (in base currency)
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param collateral: Either ISOLATED or CROSS
:param maintenance_amt: (CUM) Maintenance Amount of position
:param wallet_balance: (WB)
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
# * Not required by Binance
:param taker_fee_rate:
:param taker_fee_rate: # * Not required by Binance
:param maintenance_amt:
# * Only required for Cross
:param mm_ex_1: (TMM)
@ -321,31 +314,32 @@ class Binance(Exchange):
Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
Isolated-Margin Mode: 0
"""
if trading_mode == TradingMode.SPOT:
if self.trading_mode == TradingMode.SPOT:
return None
elif (self.collateral is None):
raise OperationalException('Binance.collateral must be set for liquidation_price')
if not collateral:
if (maintenance_amt is None):
raise OperationalException(
"Parameter collateral is required by liquidation_price when trading_mode is "
f"{trading_mode}"
f"Parameter maintenance_amt is required by Binance.liquidation_price"
f"for {self.collateral.value} {self.trading_mode.value}"
)
if (
(wallet_balance is None or maintenance_amt is None or position is None) or
(collateral == Collateral.CROSS and (mm_ex_1 is None or upnl_ex_1 is None))
):
required_params = "wallet_balance, maintenance_amt, position"
if collateral == Collateral.CROSS:
required_params += ", mm_ex_1, upnl_ex_1"
if (self.collateral == Collateral.CROSS and (mm_ex_1 is None or upnl_ex_1 is None)):
raise OperationalException(
f"Parameters {required_params} are required by Binance.liquidation_price"
f"for {collateral.name} {trading_mode.name}"
f"Parameters mm_ex_1 and upnl_ex_1 are required by Binance.liquidation_price"
f"for {self.collateral.value} {self.trading_mode.value}"
)
side_1 = -1 if is_short else 1
position = abs(position)
cross_vars = upnl_ex_1 - mm_ex_1 if collateral == Collateral.CROSS else 0.0 # type: ignore
cross_vars = (
upnl_ex_1 - mm_ex_1 # type: ignore
if self.collateral == Collateral.CROSS else
0.0
)
if trading_mode == TradingMode.FUTURES:
if self.trading_mode == TradingMode.FUTURES:
return (
(
(wallet_balance + cross_vars + maintenance_amt) -
@ -356,4 +350,4 @@ class Binance(Exchange):
)
raise OperationalException(
f"Binance does not support {collateral.value} Mode {trading_mode.value} trading ")
f"Binance does not support {self.collateral.value} {self.trading_mode.value} trading")

View File

@ -2018,135 +2018,62 @@ class Exchange:
self,
open_rate: float, # Entry price of position
is_short: bool,
leverage: float,
mm_ratio: float,
position: float, # Absolute value of position size
trading_mode: TradingMode,
collateral: Optional[Collateral] = Collateral.ISOLATED,
maintenance_amt: Optional[float] = None, # (Binance)
wallet_balance: Optional[float] = None, # (Binance and Gateio)
wallet_balance: float, # Or margin balance
taker_fee_rate: Optional[float] = None, # (Gateio & Okex)
maintenance_amt: Optional[float] = None, # (Binance)
mm_ex_1: Optional[float] = 0.0, # (Binance) Cross only
upnl_ex_1: Optional[float] = 0.0, # (Binance) Cross only
) -> Optional[float]:
"""
PERPETUAL:
gateio: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price
okex: https://www.okex.com/support/hc/en-us/articles/
360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
: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 leverage: The amount of leverage on the trade
:param mm_ratio: (MMR)
Okex: [assets in the position - (liability +interest) * mark price] /
(maintenance margin + liquidation fee)
# * Note: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
:param position: Absolute value of position size (in base currency)
:param mm_ratio:
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param collateral: Either ISOLATED or CROSS
# * Binance
:param maintenance_amt: (CUM) Maintenance Amount of position
# * Binance and Gateio
:param wallet_balance: (WB)
:param wallet_balance: Amount of collateral in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
# * Gateio & Okex
:param taker_fee_rate:
# * Cross only (Binance)
:param mm_ex_1: (TMM)
Cross-Margin Mode: Maintenance Margin of all other contracts, excluding Contract 1
Isolated-Margin Mode: 0
:param upnl_ex_1: (UPNL)
Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
Isolated-Margin Mode: 0
# * Not required by Gateio or OKX
:param maintenance_amt:
:param mm_ex_1:
:param upnl_ex_1:
"""
if trading_mode == TradingMode.SPOT:
if self.trading_mode == TradingMode.SPOT:
return None
elif (self.collateral is None):
raise OperationalException('Binance.collateral must be set for liquidation_price')
if not collateral:
if (not taker_fee_rate):
raise OperationalException(
"Parameter collateral is required by liquidation_price when trading_mode is "
f"{trading_mode}"
f"Parameter taker_fee_rate is required by {self.name}.liquidation_price"
)
return self.liquidation_price_helper(
open_rate=open_rate,
is_short=is_short,
leverage=leverage,
mm_ratio=mm_ratio,
position=position,
trading_mode=trading_mode,
collateral=collateral,
maintenance_amt=maintenance_amt,
wallet_balance=wallet_balance,
taker_fee_rate=taker_fee_rate,
mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1,
)
if self.trading_mode == TradingMode.FUTURES and self.collateral == Collateral.ISOLATED:
# if is_inverse:
# raise OperationalException(
# "Freqtrade does not support inverse contracts at the moment")
def liquidation_price_helper(
self,
open_rate: float, # Entry price of position
is_short: bool,
leverage: float,
mm_ratio: float,
position: float, # Absolute value of position size
wallet_balance: float, # Or margin balance
trading_mode: TradingMode,
collateral: Collateral,
taker_fee_rate: Optional[float] = None, # (Gateio & Okex)
maintenance_amt: Optional[float] = None, # (Binance)
mm_ex_1: Optional[float] = 0.0, # (Binance) Cross only
upnl_ex_1: Optional[float] = 0.0, # (Binance) Cross only
) -> Optional[float]:
"""
PERPETUAL:
gateio: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price
okex: https://www.okex.com/support/hc/en-us/articles/
360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
value = wallet_balance / position
:param exchange_name:
:param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise
:param leverage: The amount of leverage on the trade
:param position: Absolute value of position size (in base currency)
:param mm_ratio:
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param collateral: Either ISOLATED or CROSS
:param wallet_balance: Amount of collateral in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
:param taker_fee_rate:
# * Not required by Gateio or OKX
:param maintenance_amt:
:param mm_ex_1:
:param upnl_ex_1:
"""
if trading_mode == TradingMode.SPOT:
return None
if (not taker_fee_rate):
raise OperationalException(
f"Parameter taker_fee_rate is required by {self.name}.liquidation_price"
)
if trading_mode == TradingMode.FUTURES and collateral == Collateral.ISOLATED:
# if is_inverse:
# raise OperationalException(
# "Freqtrade does not support inverse contracts at the moment")
value = wallet_balance / position
mm_ratio_taker = (mm_ratio + taker_fee_rate)
if is_short:
return (open_rate + value) / (1 + mm_ratio_taker)
else:
return (open_rate - value) / (1 - mm_ratio_taker)
mm_ratio_taker = (mm_ratio + taker_fee_rate)
if is_short:
return (open_rate + value) / (1 + mm_ratio_taker)
else:
raise OperationalException(
f"{self.name} does not support {collateral.value} {trading_mode.value}")
return (open_rate - value) / (1 - mm_ratio_taker)
else:
raise OperationalException(
f"{self.name} does not support {self.collateral.value} {self.trading_mode.value}")
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:

View File

@ -1,8 +1,6 @@
import logging
from typing import Dict, List, Optional, Tuple
from typing import Dict, List, Tuple
from freqtrade.enums import Collateral, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange

View File

@ -626,16 +626,13 @@ class FreqtradeBot(LoggingMixin):
isolated_liq = self.exchange.liquidation_price(
open_rate=open_rate,
is_short=is_short,
leverage=leverage,
trading_mode=self.trading_mode,
collateral=Collateral.ISOLATED,
mm_ex_1=0.0,
upnl_ex_1=0.0,
mm_ratio=mm_ratio,
position=amount,
wallet_balance=(amount * open_rate)/leverage, # TODO: Update for cross
taker_fee_rate=taker_fee_rate,
maintenance_amt=maintenance_amt,
mm_ratio=mm_ratio,
taker_fee_rate=taker_fee_rate
mm_ex_1=0.0,
upnl_ex_1=0.0,
)
else:
isolated_liq = self.exchange.get_liquidation_price(pair)

View File

@ -3626,6 +3626,8 @@ def test_get_liquidation_price(mocker, default_conf):
exchange_has=MagicMock(return_value=True),
)
default_conf['dry_run'] = False
default_conf['trading_mode'] = 'futures'
default_conf['collateral'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf, api_mock)
liq_price = exchange.get_liquidation_price('NEAR/USDT:USDT')
@ -3973,13 +3975,13 @@ def test__amount_to_contracts(
@pytest.mark.parametrize('exchange_name,open_rate,is_short,leverage,trading_mode,collateral', [
# Bittrex
('bittrex', "2.0", False, "3.0", spot, None),
('bittrex', "2.0", False, "1.0", spot, cross),
('bittrex', "2.0", True, "3.0", spot, isolated),
('bittrex', 2.0, False, 3.0, spot, None),
('bittrex', 2.0, False, 1.0, spot, cross),
('bittrex', 2.0, True, 3.0, spot, isolated),
# Binance
('binance', "2.0", False, "3.0", spot, None),
('binance', "2.0", False, "1.0", spot, cross),
('binance', "2.0", True, "3.0", spot, isolated),
('binance', 2.0, False, 3.0, spot, None),
('binance', 2.0, False, 1.0, spot, cross),
('binance', 2.0, True, 3.0, spot, isolated),
])
def test_liquidation_price_is_none(
mocker,
@ -3991,51 +3993,22 @@ def test_liquidation_price_is_none(
trading_mode,
collateral
):
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = collateral
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.liquidation_price(
open_rate,
is_short,
leverage,
trading_mode,
collateral,
1535443.01,
71200.81144,
-56354.57,
135365.00,
3683.979,
0.10,
open_rate=open_rate,
is_short=is_short,
mm_ratio=1535443.01,
position=71200.81144,
wallet_balance=-56354.57,
taker_fee_rate=0.01,
maintenance_amt=3683.979,
mm_ex_1=0.10,
upnl_ex_1=0.0
) is None
@pytest.mark.parametrize('exchange_name,open_rate,is_short,leverage,trading_mode,collateral', [
# Bittrex
('bittrex', "2.0", False, "3.0", margin, cross),
('bittrex', "2.0", False, "3.0", margin, isolated),
('bittrex', "2.0", False, "3.0", futures, cross),
('bittrex', "2.0", False, "3.0", futures, isolated),
# Binance
# Binance supports isolated margin, but freqtrade likely won't for a while on Binance
('binance', "2.0", True, "3.0", margin, isolated),
# Kraken
('kraken', "2.0", True, "1.0", margin, isolated),
('kraken', "2.0", True, "1.0", futures, isolated),
# FTX
('ftx', "2.0", True, "3.0", margin, isolated),
('ftx', "2.0", True, "3.0", futures, isolated),
])
def test_liquidation_price_exception_thrown(
exchange_name,
open_rate,
is_short,
leverage,
trading_mode,
collateral,
result
):
# TODO-lev assert exception is thrown
return # Here to avoid indent error, remove when implemented
@pytest.mark.parametrize(
'exchange_name, is_short, leverage, trading_mode, collateral, wallet_balance, '
'mm_ex_1, upnl_ex_1, maintenance_amt, position, open_rate, '
@ -4054,13 +4027,12 @@ def test_liquidation_price(
mocker, default_conf, exchange_name, open_rate, is_short, leverage, trading_mode,
collateral, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, position, mm_ratio, expected
):
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = collateral
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert isclose(round(exchange.liquidation_price(
open_rate=open_rate,
is_short=is_short,
leverage=leverage,
trading_mode=trading_mode,
collateral=collateral,
wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1,