357 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			357 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from typing import Optional
 | ||
| 
 | ||
| from freqtrade.enums import Collateral, TradingMode
 | ||
| from freqtrade.exceptions import OperationalException
 | ||
| 
 | ||
| 
 | ||
| def liquidation_price(
 | ||
|     exchange_name: str,
 | ||
|     open_rate: float,   # Entry price of position
 | ||
|     is_short: bool,
 | ||
|     leverage: float,
 | ||
|     trading_mode: TradingMode,
 | ||
|     mm_ratio: float,
 | ||
|     collateral: Optional[Collateral] = Collateral.ISOLATED,
 | ||
|     maintenance_amt: Optional[float] = None,  # (Binance)
 | ||
|     position: Optional[float] = None,  # (Binance and Gateio) Absolute value of position size
 | ||
|     wallet_balance: Optional[float] = None,  # (Binance and Gateio)
 | ||
|     taker_fee_rate: Optional[float] = None,  # (Gateio & Okex)
 | ||
|     liability: Optional[float] = None,  # (Okex)
 | ||
|     interest: Optional[float] = None,  # (Okex)
 | ||
|     position_assets: Optional[float] = None,  # * (Okex) Might be same as position
 | ||
|     mm_ex_1: Optional[float] = 0.0,  # (Binance) Cross only
 | ||
|     upnl_ex_1: Optional[float] = 0.0,  # (Binance) Cross only
 | ||
| ) -> Optional[float]:
 | ||
|     """
 | ||
|     :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 trading_mode: SPOT, MARGIN, FUTURES, etc.
 | ||
|     :param position: Absolute value of position size (in base currency)
 | ||
|     :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 collateral: Either ISOLATED or CROSS
 | ||
| 
 | ||
|     # * Binance
 | ||
|     :param maintenance_amt: (CUM) Maintenance Amount of position
 | ||
| 
 | ||
|     # * Binance and Gateio
 | ||
|     :param wallet_balance: (WB)
 | ||
|         Cross-Margin Mode: crossWalletBalance
 | ||
|         Isolated-Margin Mode: isolatedWalletBalance
 | ||
|     :param position: Absolute value of position size (in base currency)
 | ||
| 
 | ||
|     # * Gateio & Okex
 | ||
|     :param taker_fee_rate:
 | ||
| 
 | ||
|     # * Okex
 | ||
|     :param liability:
 | ||
|         Initial liabilities + deducted interest
 | ||
|             • Long positions: Liability is calculated in quote currency.
 | ||
|             • Short positions: Liability is calculated in trading currency.
 | ||
|     :param interest:
 | ||
|         Interest that has not been deducted yet.
 | ||
|     :param position_assets:
 | ||
|         Total position assets – on-hold by pending order
 | ||
| 
 | ||
|     # * 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
 | ||
|     """
 | ||
|     if trading_mode == TradingMode.SPOT:
 | ||
|         return None
 | ||
| 
 | ||
|     if not collateral:
 | ||
|         raise OperationalException(
 | ||
|             "Parameter collateral is required by liquidation_price when trading_mode is "
 | ||
|             f"{trading_mode}"
 | ||
|         )
 | ||
| 
 | ||
|     if exchange_name.lower() == "binance":
 | ||
|         if (wallet_balance is None or maintenance_amt is None or position is None):
 | ||
|             # mm_ex_1 is None or # * Cross only
 | ||
|             # upnl_ex_1 is None or # * Cross only
 | ||
|             raise OperationalException(
 | ||
|                 f"Parameters wallet_balance, maintenance_amt, position"
 | ||
|                 f"are required by liquidation_price when exchange is {exchange_name.lower()}"
 | ||
|             )
 | ||
|         # Suppress incompatible type "Optional[...]"; expected "..." as the check exists above.
 | ||
|         return binance(
 | ||
|             open_rate=open_rate,
 | ||
|             is_short=is_short,
 | ||
|             leverage=leverage,
 | ||
|             trading_mode=trading_mode,
 | ||
|             collateral=collateral,  # type: ignore
 | ||
|             wallet_balance=wallet_balance,
 | ||
|             mm_ex_1=mm_ex_1,  # type: ignore
 | ||
|             upnl_ex_1=upnl_ex_1,  # type: ignore
 | ||
|             maintenance_amt=maintenance_amt,  # type: ignore
 | ||
|             position=position,
 | ||
|             mm_ratio=mm_ratio,
 | ||
|         )
 | ||
|     elif exchange_name.lower() == "gateio":
 | ||
|         if (not wallet_balance or not position or not taker_fee_rate):
 | ||
|             raise OperationalException(
 | ||
|                 f"Parameters wallet_balance, position, taker_fee_rate"
 | ||
|                 f"are required by liquidation_price when exchange is {exchange_name.lower()}"
 | ||
|             )
 | ||
|         else:
 | ||
|             return gateio(
 | ||
|                 open_rate=open_rate,
 | ||
|                 is_short=is_short,
 | ||
|                 trading_mode=trading_mode,
 | ||
|                 collateral=collateral,
 | ||
|                 wallet_balance=wallet_balance,
 | ||
|                 position=position,
 | ||
|                 mm_ratio=mm_ratio,
 | ||
|                 taker_fee_rate=taker_fee_rate
 | ||
|             )
 | ||
|     elif exchange_name.lower() == "okex":
 | ||
|         if (not liability or not interest or not taker_fee_rate or not position_assets):
 | ||
|             raise OperationalException(
 | ||
|                 f"Parameters liability, interest, taker_fee_rate, position_assets"
 | ||
|                 f"are required by liquidation_price when exchange is {exchange_name.lower()}"
 | ||
|             )
 | ||
|         else:
 | ||
|             return okex(
 | ||
|                 is_short=is_short,
 | ||
|                 trading_mode=trading_mode,
 | ||
|                 collateral=collateral,
 | ||
|                 mm_ratio=mm_ratio,
 | ||
|                 liability=liability,
 | ||
|                 interest=interest,
 | ||
|                 taker_fee_rate=taker_fee_rate,
 | ||
|                 position_assets=position_assets,
 | ||
|             )
 | ||
|     elif exchange_name.lower() == "ftx":
 | ||
|         return ftx(open_rate, is_short, leverage, trading_mode, collateral)
 | ||
|     elif exchange_name.lower() == "kraken":
 | ||
|         return kraken(open_rate, is_short, leverage, trading_mode, collateral)
 | ||
|     raise OperationalException(f"liquidation_price is not implemented for {exchange_name}")
 | ||
| 
 | ||
| 
 | ||
| def exception(
 | ||
|     exchange: str,
 | ||
|     trading_mode: TradingMode,
 | ||
|     collateral: Collateral,
 | ||
| ):
 | ||
|     """
 | ||
|     Raises an exception if exchange used doesn't support desired leverage mode
 | ||
|     :param exchange: Name of the exchange
 | ||
|     :param trading_mode: spot, margin, futures
 | ||
|     :param collateral: cross, isolated
 | ||
|     """
 | ||
| 
 | ||
|     raise OperationalException(
 | ||
|         f"{exchange} does not support {collateral.value} Mode {trading_mode.value} trading ")
 | ||
| 
 | ||
| 
 | ||
| def binance(
 | ||
|     open_rate: float,
 | ||
|     is_short: bool,
 | ||
|     leverage: float,
 | ||
|     trading_mode: TradingMode,
 | ||
|     mm_ratio: float,
 | ||
|     collateral: Collateral,
 | ||
|     maintenance_amt: float,
 | ||
|     wallet_balance: float,
 | ||
|     position: float,
 | ||
|     mm_ex_1: float,
 | ||
|     upnl_ex_1: float,
 | ||
| ):
 | ||
|     """
 | ||
|     MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
 | ||
|     PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
 | ||
| 
 | ||
|     :param open_rate: (EP1) Entry Price of position (one-way mode)
 | ||
|     :param is_short: true or false
 | ||
|     :param leverage: leverage in float
 | ||
|     :param trading_mode: SPOT, MARGIN, FUTURES
 | ||
|     :param mm_ratio: (MMR) Maintenance margin rate of position (one-way mode)
 | ||
|     :param collateral: CROSS, ISOLATED
 | ||
|     :param maintenance_amt: (CUM) Maintenance Amount of position (one-way mode)
 | ||
|     :param position: Absolute value of position size (one-way mode)
 | ||
|     :param wallet_balance: (WB)
 | ||
|         Cross-Margin Mode: crossWalletBalance
 | ||
|         Isolated-Margin Mode: isolatedWalletBalance
 | ||
|             TMM=0, UPNL=0, substitute the position quantity, MMR, cum into the formula to calculate.
 | ||
|             Under the cross margin mode, the same ticker/symbol,
 | ||
|             both long and short position share the same liquidation price except in isolated mode.
 | ||
|             Under the isolated mode, each isolated position will have different liquidation prices
 | ||
|             depending on the margin allocated to the positions.
 | ||
|     :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
 | ||
|     """
 | ||
|     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
 | ||
| 
 | ||
|     if trading_mode == TradingMode.MARGIN and collateral == Collateral.CROSS:
 | ||
|         # ! Not Implemented
 | ||
|         exception("binance", trading_mode, collateral)
 | ||
|     if trading_mode == TradingMode.FUTURES:
 | ||
|         return (wallet_balance + cross_vars + maintenance_amt - (side_1 * position * open_rate)) / (
 | ||
|             (position * mm_ratio) - (side_1 * position))
 | ||
| 
 | ||
|     exception("binance", trading_mode, collateral)
 | ||
| 
 | ||
| 
 | ||
| def gateio(
 | ||
|     open_rate: float,
 | ||
|     is_short: bool,
 | ||
|     trading_mode: TradingMode,
 | ||
|     mm_ratio: float,
 | ||
|     collateral: Collateral,
 | ||
|     position: float,
 | ||
|     wallet_balance: float,
 | ||
|     taker_fee_rate: float,
 | ||
|     is_inverse: bool = False
 | ||
| ):
 | ||
|     """
 | ||
|     PERPETUAL: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price
 | ||
| 
 | ||
|     :param open_rate: Entry Price of position
 | ||
|     :param is_short: True for short trades
 | ||
|     :param trading_mode: SPOT, MARGIN, FUTURES
 | ||
|     :param mm_ratio: Viewed in contract details
 | ||
|     :param collateral: CROSS, ISOLATED
 | ||
|     :param position: size of position in base currency
 | ||
|         contract_size / num_contracts
 | ||
|         contract_size: How much one contract is worth
 | ||
|         num_contracts: Also called position
 | ||
|     :param wallet_balance: Also called margin
 | ||
|     :param taker_fee_rate:
 | ||
|     :param is_inverse: True if settle currency matches base currency
 | ||
| 
 | ||
|     ( Opening Price ± Margin/Contract Multiplier/Position ) / [ 1 ± ( MMR + Taker Fee)]
 | ||
|     '±' in the formula refers to the direction of the contract,
 | ||
|         go long refers to '-'
 | ||
|         go short refers to '+'
 | ||
| 
 | ||
|     """
 | ||
| 
 | ||
|     if trading_mode == TradingMode.FUTURES and collateral == Collateral.ISOLATED:
 | ||
|         if is_inverse:
 | ||
|             # ! Not implemented
 | ||
|             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)
 | ||
|     else:
 | ||
|         exception("gatio", trading_mode, collateral)
 | ||
| 
 | ||
| 
 | ||
| def okex(
 | ||
|     is_short: bool,
 | ||
|     trading_mode: TradingMode,
 | ||
|     mm_ratio: float,
 | ||
|     collateral: Collateral,
 | ||
|     taker_fee_rate: float,
 | ||
|     liability: float,
 | ||
|     interest: float,
 | ||
|     position_assets: float
 | ||
| ):
 | ||
|     '''
 | ||
|     PERPETUAL: https://www.okex.com/support/hc/en-us/articles/
 | ||
|     360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
 | ||
| 
 | ||
|     :param is_short: True if the position is short, false otherwise
 | ||
|     :param trading_mode: SPOT, MARGIN, FUTURES
 | ||
|     :param mm_ratio:
 | ||
|         long: [position_assets - (liability + interest) / mark_price] / (maintenance_margin + fees)
 | ||
|         short: [position_assets - (liability + interest) * mark_price] / (maintenance_margin + fees)
 | ||
|     :param collateral: CROSS, ISOLATED
 | ||
|     :param taker_fee_rate:
 | ||
|     :param liability: Initial liabilities + deducted interest
 | ||
|         long: Liability is calculated in quote currency
 | ||
|         short: Liability is calculated in trading currency
 | ||
|     :param interest: Interest that has not been deducted yet
 | ||
|     :param position_assets: Total position assets - on-hold by pending order
 | ||
| 
 | ||
|     Total: The number of positive assets on the position (including margin).
 | ||
|         long: with trading currency as position asset.
 | ||
|         short: with quote currency as position asset.
 | ||
| 
 | ||
|     Est. liquidation price
 | ||
|         long: (liability + interest)* (1 + maintenance margin ratio) *
 | ||
|             (1 + taker fee rate) / position assets
 | ||
|         short: (liability + interest)* (1 + maintenance margin ratio) *
 | ||
|             (1 + taker fee rate)
 | ||
| 
 | ||
|     '''
 | ||
|     if trading_mode == TradingMode.FUTURES and collateral == Collateral.ISOLATED:
 | ||
|         if is_short:
 | ||
|             return (liability + interest) * (1 + mm_ratio) * (1 + taker_fee_rate)
 | ||
|         else:
 | ||
|             return (liability + interest) * (1 + mm_ratio) * (1 + taker_fee_rate) / position_assets
 | ||
|     else:
 | ||
|         exception("okex", trading_mode, collateral)
 | ||
| 
 | ||
| 
 | ||
| def ftx(
 | ||
|     open_rate: float,
 | ||
|     is_short: bool,
 | ||
|     leverage: float,
 | ||
|     trading_mode: TradingMode,
 | ||
|     collateral: Collateral
 | ||
|     # ...
 | ||
| ):
 | ||
|     """
 | ||
|     # ! Not Implemented
 | ||
|     Calculates the liquidation price on FTX
 | ||
|     :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 trading_mode: SPOT, MARGIN, FUTURES, etc.
 | ||
|     :param collateral: Either ISOLATED or CROSS
 | ||
|     """
 | ||
|     if collateral == Collateral.CROSS:
 | ||
|         exception("ftx", trading_mode, collateral)
 | ||
| 
 | ||
|     # If nothing was returned
 | ||
|     exception("ftx", trading_mode, collateral)
 | ||
| 
 | ||
| 
 | ||
| def kraken(
 | ||
|     open_rate: float,
 | ||
|     is_short: bool,
 | ||
|     leverage: float,
 | ||
|     trading_mode: TradingMode,
 | ||
|     collateral: Collateral
 | ||
|     # ...
 | ||
| ):
 | ||
|     """
 | ||
|     # ! Not Implemented
 | ||
|     MARGIN: https://support.kraken.com/hc/en-us/articles/203325763-Margin-Call-Level-and-Margin-Liquidation-Level
 | ||
| 
 | ||
|     :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 trading_mode: SPOT, MARGIN, FUTURES, etc.
 | ||
|     :param collateral: Either ISOLATED or CROSS
 | ||
|     """
 | ||
| 
 | ||
|     if collateral == Collateral.CROSS:
 | ||
|         if trading_mode == TradingMode.MARGIN:
 | ||
|             exception("kraken", trading_mode, collateral)
 | ||
|         elif trading_mode == TradingMode.FUTURES:
 | ||
|             exception("kraken", trading_mode, collateral)
 | ||
| 
 | ||
|     # If nothing was returned
 | ||
|     exception("kraken", trading_mode, collateral)
 |