diff --git a/freqtrade/leverage/liquidation_price.py b/freqtrade/leverage/liquidation_price.py index 2519bab52..5a14716e1 100644 --- a/freqtrade/leverage/liquidation_price.py +++ b/freqtrade/leverage/liquidation_price.py @@ -6,18 +6,74 @@ from freqtrade.exceptions import OperationalException def liquidation_price( exchange_name: str, - open_rate: float, + open_rate: float, # (b) Entry price of position is_short: bool, leverage: float, trading_mode: TradingMode, collateral: Optional[Collateral], - wallet_balance: Optional[float], - mm_ex_1: Optional[float], - upnl_ex_1: Optional[float], - maintenance_amt: Optional[float], - position: Optional[float], - mm_rate: Optional[float] + # Binance + collateral_amount: Optional[float] = None, # (bg) + mm_ex_1: Optional[float] = None, # (b) + upnl_ex_1: Optional[float] = None, # (b) + maintenance_amt: Optional[float] = None, # (b) (cum_b) + position: Optional[float] = None, # (b) Absolute value of position size + mm_rate: Optional[float] = None, # (b) + # Gateio & Okex + mm_ratio: Optional[float] = None, # (go) + taker_fee_rate: Optional[float] = None, # (go) + # Gateio + base_size: Optional[float] = None, # (g) + # Okex + liability: Optional[float] = None, # (o) + interest: Optional[float] = None, # (o) + position_assets: Optional[float] = None, # (o) ) -> Optional[float]: + ''' + exchange_name + is_short + leverage + trading_mode + collateral + # + open_rate - b + collateral_amount - bg + In Cross margin mode, WB is crossWalletBalance + In Isolated margin mode, WB is isolatedWalletBalance of the isolated position, + 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 the isolated mode. + Under the isolated mode, each isolated position will have different liquidation prices depending + on the margin allocated to the positions. + mm_ex_1 - b + Maintenance Margin of all other contracts, excluding Contract 1 + If it is an isolated margin mode, then TMM=0,UPNL=0 + upnl_ex_1 - b + Unrealized PNL of all other contracts, excluding Contract 1 + If it is an isolated margin mode, then UPNL=0 + maintenance_amt (cumb) - b + Maintenance Amount of position + position - b + Absolute value of position size + mm_rate - b + Maintenance margin rate of position + # Gateio & okex + mm_ratio - go + - [assets in the position - (liability +interest) * mark price] / (maintenance margin + liquidation fee) (okex) + taker_fee_rate - go + # Gateio + base_size - g + The size of the position in base currency + # Okex + liability - o + Initial liabilities + deducted interest + • Long positions: Liability is calculated in quote currency. + • Short positions: Liability is calculated in trading currency. + interest - o + Interest that has not been deducted yet. + position_assets - o + # * I think this is the same as collateral_amount + Total position assets – on-hold by pending order + ''' if trading_mode == TradingMode.SPOT: return None @@ -29,7 +85,7 @@ def liquidation_price( if exchange_name.lower() == "binance": if ( - wallet_balance is None or + collateral_amount is None or mm_ex_1 is None or upnl_ex_1 is None or maintenance_amt is None or @@ -48,7 +104,7 @@ def liquidation_price( leverage=leverage, trading_mode=trading_mode, collateral=collateral, # type: ignore - wallet_balance=wallet_balance, + wallet_balance=collateral_amount, mm_ex_1=mm_ex_1, upnl_ex_1=upnl_ex_1, maintenance_amt=maintenance_amt, # type: ignore @@ -59,6 +115,51 @@ def liquidation_price( return kraken(open_rate, is_short, leverage, trading_mode, collateral) elif exchange_name.lower() == "ftx": return ftx(open_rate, is_short, leverage, trading_mode, collateral) + elif exchange_name.lower() == "gateio": + if ( + not collateral_amount or + not base_size or + not mm_ratio or + not taker_fee_rate + ): + raise OperationalException( + f"{exchange_name} {collateral} {trading_mode} requires parameters " + f"collateral_amount, contract_size, num_contracts, mm_ratio and taker_fee" + ) + else: + return gateio( + open_rate=open_rate, + is_short=is_short, + trading_mode=trading_mode, + collateral=collateral, + collateral_amount=collateral_amount, + base_size=base_size, + mm_ratio=mm_ratio, + taker_fee_rate=taker_fee_rate + ) + elif exchange_name.lower() == "okex": + if ( + not mm_ratio or + not liability or + not interest or + not taker_fee_rate or + not position_assets + ): + raise OperationalException( + f"{exchange_name} {collateral} {trading_mode} requires parameters " + f"mm_ratio, liability, interest, taker_fee_rate, position_assets" + ) + 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, + ) raise OperationalException( f"liquidation_price is not implemented for {exchange_name}" ) @@ -94,29 +195,21 @@ def binance( mm_rate: float, ): """ - Calculates the liquidation price on Binance - :param is_short: true or false - :param leverage: leverage in float - :param trading_mode: spot, margin, futures - :param collateral: cross, isolated - - :param wallet_balance: Wallet Balance is crossWalletBalance in Cross-Margin Mode. - Wallet Balance is isolatedWalletBalance in Isolated Margin Mode - - :param mm_ex_1: Maintenance Margin of all other contracts, - excluding Contract 1. If it is an isolated margin mode, then TMM=0 - - :param upnl_ex_1: Unrealized PNL of all other contracts, excluding Contract 1. - If it is an isolated margin mode, then UPNL=0 - - :param maintenance_amt: Maintenance Amount of position (one-way mode) - - :param position: Absolute value of position size (one-way mode) - - :param open_rate: Entry Price of position (one-way mode) - - :param mm_rate: Maintenance margin rate of position (one-way mode) - + Calculates the liquidation price on Binance + :param is_short: true or false + :param leverage: leverage in float + :param trading_mode: spot, margin, futures + :param collateral: cross, isolated + :param wallet_balance: Wallet Balance is crossWalletBalance in Cross-Margin Mode. + Wallet Balance is isolatedWalletBalance in Isolated Margin Mode + :param mm_ex_1: Maintenance Margin of all other contracts, + excluding Contract 1. If it is an isolated margin mode, then TMM=0 + :param upnl_ex_1: Unrealized PNL of all other contracts, excluding Contract 1. + If it is an isolated margin mode, then UPNL=0 + :param maintenance_amt: Maintenance Amount of position (one-way mode) + :param position: Absolute value of position size (one-way mode) + :param open_rate: Entry Price of position (one-way mode) + :param mm_rate: Maintenance margin rate of position (one-way mode) """ # TODO-lev: Additional arguments, fill in formulas wb = wallet_balance @@ -161,10 +254,9 @@ def kraken( collateral: Collateral ): """ - Calculates the liquidation price on Kraken - :param name: Name of the exchange - :param trading_mode: spot, margin, futures - :param collateral: cross, isolated + Calculates the liquidation price on Kraken + :param trading_mode: spot, margin, futures + :param collateral: cross, isolated """ # TODO-lev: Additional arguments, fill in formulas @@ -188,10 +280,9 @@ def ftx( collateral: Collateral ): """ - Calculates the liquidation price on FTX - :param name: Name of the exchange - :param trading_mode: spot, margin, futures - :param collateral: cross, isolated + Calculates the liquidation price on FTX + :param trading_mode: spot, margin, futures + :param collateral: cross, isolated """ if collateral == Collateral.CROSS: # TODO-lev: Additional arguments, fill in formulas @@ -201,18 +292,99 @@ def ftx( exception("ftx", trading_mode, collateral) -if __name__ == '__main__': - print(liquidation_price( - "binance", - 32481.980, - False, - 1, - TradingMode.FUTURES, - Collateral.ISOLATED, - 1535443.01, - 356512.508, - 0.0, - 16300.000, - 109.488, - 0.025 - )) +def gateio( + open_rate: float, + is_short: bool, + trading_mode: TradingMode, + collateral: Collateral, + collateral_amount: float, + base_size: float, + mm_ratio: float, + taker_fee_rate: float, + is_inverse: bool = False +): + """ + Calculates the liquidation price on Gate.io + :param open_rate: Entry Price of position + :param is_short: True for short trades + :param trading_mode: spot, margin, futures + :param collateral: cross, isolated + :param collateral_amount: Also called margin + :param base_size: size of position in base currency + contract_size / num_contracts + contract_size: How much one contract is worth + num_contracts: Also called position + :param mm_ratio: Viewed in contract details + :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 '+' + Position refers to the number of contracts. + Maintenance Margin Ratio and Contract Multiplier can be viewed in the Contract Details. + + https://www.gate.io/help/futures/perpetual/22160/calculation-of-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 = collateral_amount / base_size + + 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, + collateral: Collateral, + liability: float, + interest: float, + mm_ratio: float, + taker_fee_rate: float, + position_assets: float +): + ''' + https://www.okex.com/support/hc/en-us/articles/ + 360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin + + Initial liabilities + deducted interest + Long positions: Liability is calculated in quote currency. + Short positions: Liability is calculated in trading currency. + interest: Interest that has not been deducted yet. + Margin ratio + Long: [position_assets - (liability + interest) / mark_price] / (maintenance_margin + fees) + Short: [position_assets - (liability + interest) * mark_price] / (maintenance_margin + fees) + ''' + 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) + +# if __name__ == '__main__': +# print(liquidation_price( +# "binance", +# 32481.980, +# False, +# 1, +# TradingMode.FUTURES, +# Collateral.ISOLATED, +# 1535443.01, +# 356512.508, +# 0.0, +# 16300.000, +# 109.488, +# 0.025 +# )) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index abe6d0237..91f458cb6 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -16,8 +16,7 @@ from sqlalchemy.sql.schema import UniqueConstraint from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES from freqtrade.enums import Collateral, SellType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException -from freqtrade.leverage import interest -from freqtrade.leverage import liquidation_price +from freqtrade.leverage import interest, liquidation_price from freqtrade.misc import safe_value_fallback from freqtrade.persistence.migrations import check_migrate