diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 631546f49..e277c0428 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2015,6 +2015,114 @@ class Exchange: # TODO-lev: return the real amounts return (0, 0.4) + def liquidation_price( + self, + 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}" + ) + + return self.liquidation_price_helper( + open_rate, + is_short, + leverage, + trading_mode, + mm_ratio, + collateral, + maintenance_amt, + position, + wallet_balance, + taker_fee_rate, + liability, + interest, + position_assets, + mm_ex_1, + upnl_ex_1, + ) + + def liquidation_price_helper( + self, + open_rate: float, + is_short: bool, + leverage: float, + trading_mode: TradingMode, + mm_ratio: float, + collateral: Collateral, + maintenance_amt: Optional[float] = None, + position: Optional[float] = None, + wallet_balance: Optional[float] = None, + taker_fee_rate: Optional[float] = None, + liability: Optional[float] = None, + interest: Optional[float] = None, + position_assets: Optional[float] = None, + mm_ex_1: Optional[float] = 0.0, + upnl_ex_1: Optional[float] = 0.0, + ) -> Optional[float]: + raise OperationalException(f"liquidation_price is not implemented for {self.name}") + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index c62b6222d..e2dbf35c7 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -51,3 +51,78 @@ class Gateio(Exchange): """ info = self.markets[pair]['info'] return (float(info['maintenance_rate']), None) + + def liquidation_price_helper( + self, + open_rate: float, # Entry price of position + is_short: bool, + leverage: float, + trading_mode: TradingMode, + mm_ratio: float, + collateral: Collateral, + 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]: + """ + PERPETUAL: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price + + :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 trading_mode: SPOT, MARGIN, FUTURES, etc. + :param position: Absolute value of position size (in base currency) + :param mm_ratio: + :param collateral: Either ISOLATED or CROSS + :param maintenance_amt: # * Not required by Gateio + :param wallet_balance: + Cross-Margin Mode: crossWalletBalance + Isolated-Margin Mode: isolatedWalletBalance + :param position: Absolute value of position size (in base currency) + :param taker_fee_rate: + + # * Not required by Gateio + :param liability: + :param interest: + :param position_assets: + :param mm_ex_1: + :param upnl_ex_1: + """ + 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 (not wallet_balance or not position or not taker_fee_rate): + raise OperationalException( + "Parameters wallet_balance, position, taker_fee_rate" + "are required by Gateio.liquidation_price" + ) + + 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: + raise OperationalException( + f"Gateio does not support {collateral.value} Mode {trading_mode.value} trading ") diff --git a/freqtrade/exchange/okex.py b/freqtrade/exchange/okex.py index 7e9348f0c..f847f1180 100644 --- a/freqtrade/exchange/okex.py +++ b/freqtrade/exchange/okex.py @@ -1,7 +1,8 @@ import logging -from typing import Dict, List, Tuple +from typing import Dict, List, Optional, Tuple from freqtrade.enums import Collateral, TradingMode +from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange @@ -26,3 +27,67 @@ class Okex(Exchange): # (TradingMode.FUTURES, Collateral.CROSS), # (TradingMode.FUTURES, Collateral.ISOLATED) ] + + def liquidation_price_helper( + self, + open_rate: float, # Entry price of position + is_short: bool, + leverage: float, + trading_mode: TradingMode, + mm_ratio: float, + collateral: Collateral, + maintenance_amt: Optional[float] = None, # Not required + position: Optional[float] = None, # Not required + wallet_balance: Optional[float] = None, # Not required + taker_fee_rate: Optional[float] = None, # * required + liability: Optional[float] = None, # * required + interest: Optional[float] = None, # * required + position_assets: Optional[float] = None, # * required (Might be same as position) + mm_ex_1: Optional[float] = 0.0, # Not required + upnl_ex_1: Optional[float] = 0.0, # Not required + ) -> Optional[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 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: + Okex: [assets in the position - (liability +interest) * mark price] / + (maintenance margin + liquidation fee) + :param collateral: Either ISOLATED or CROSS + :param maintenance_amt: # * Not required by Okex + :param wallet_balance: # * Not required by Okex + :param position: # * Not required by Okex + :param taker_fee_rate: + :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 + :param mm_ex_1: # * Not required by Okex + :param upnl_ex_1: # * Not required by Okex + """ + + if (not liability or not interest or not taker_fee_rate or not position_assets): + raise OperationalException( + "Parameters liability, interest, taker_fee_rate, position_assets" + "are required by Okex.liquidation_price" + ) + + 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: + raise OperationalException( + f"Okex does not support {collateral.value} Mode {trading_mode.value} trading") diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 26bcb456c..2911b8ea4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -21,7 +21,6 @@ from freqtrade.enums import (Collateral, RPCMessageType, RunMode, SellType, Sign from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.leverage import liquidation_price from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.mixins import LoggingMixin from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db @@ -624,8 +623,7 @@ class FreqtradeBot(LoggingMixin): amount ) taker_fee_rate = self.exchange.markets[pair]['taker'] - isolated_liq = liquidation_price( - exchange_name=self.exchange.name, + isolated_liq = self.exchange.liquidation_price( open_rate=open_rate, is_short=is_short, leverage=leverage, diff --git a/freqtrade/leverage/__init__.py b/freqtrade/leverage/__init__.py index 0bb2dd0be..ae78f4722 100644 --- a/freqtrade/leverage/__init__.py +++ b/freqtrade/leverage/__init__.py @@ -1,3 +1,2 @@ # flake8: noqa: F401 from freqtrade.leverage.interest import interest -from freqtrade.leverage.liquidation_price import liquidation_price diff --git a/freqtrade/leverage/liquidation_price.py b/freqtrade/leverage/liquidation_price.py deleted file mode 100644 index c80367a37..000000000 --- a/freqtrade/leverage/liquidation_price.py +++ /dev/null @@ -1,357 +0,0 @@ -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) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index ad4a513b4..884afb11d 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -14,9 +14,9 @@ from sqlalchemy.pool import StaticPool 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.enums import SellType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException -from freqtrade.leverage import interest, liquidation_price +from freqtrade.leverage import interest from freqtrade.misc import safe_value_fallback from freqtrade.persistence.migrations import check_migrate @@ -364,52 +364,12 @@ class LocalTrade(): def set_isolated_liq( self, - isolated_liq: Optional[float] = None, - wallet_balance: Optional[float] = None, - current_price: Optional[float] = None, - maintenance_amt: Optional[float] = None, - mm_ratio: Optional[float] = None, + isolated_liq: float, ): """ Method you should use to set self.liquidation price. Assures stop_loss is not passed the liquidation price """ - if not isolated_liq: - if not wallet_balance or not current_price: - raise OperationalException( - "wallet balance must be passed to LocalTrade.set_isolated_liq when param" - "isolated_liq is None" - ) - if ( - mm_ratio is None or - wallet_balance is None or - current_price is None or - maintenance_amt is None - ): - raise OperationalException( - 'mm_ratio, wallet_balance, current_price and maintenance_amt ' - 'required in set_isolated_liq when isolated_liq is None' - ) - isolated_liq = liquidation_price( - exchange_name=self.exchange, - open_rate=self.open_rate, - is_short=self.is_short, - leverage=self.leverage, - trading_mode=self.trading_mode, - collateral=Collateral.ISOLATED, - mm_ex_1=0.0, - upnl_ex_1=0.0, - position=self.amount * current_price, - wallet_balance=self.amount / self.leverage, # TODO: Update for cross - maintenance_amt=maintenance_amt, - mm_ratio=mm_ratio, - - ) - if isolated_liq is None: - raise OperationalException( - "leverage/isolated_liq returned None. This exception should never happen" - ) - if self.stop_loss is not None: if self.is_short: self.stop_loss = min(self.stop_loss, isolated_liq) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 6e0c02e7c..22e92c9fd 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -26,6 +26,12 @@ from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has # Make sure to always keep one exchange here which is NOT subclassed!! EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx', 'gateio'] +spot = TradingMode.SPOT +margin = TradingMode.MARGIN +futures = TradingMode.FUTURES + +cross = Collateral.CROSS +isolated = Collateral.ISOLATED def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, @@ -3965,3 +3971,102 @@ def test__amount_to_contracts( assert result_size == param_size result_amount = exchange._contracts_to_amount(pair, param_size) assert result_amount == param_amount + + +@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), + # 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), +]) +def test_liquidation_price_is_none( + mocker, + default_conf, + exchange_name, + open_rate, + is_short, + leverage, + trading_mode, + 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, + ) 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, ' + 'mm_ratio, expected', + [ + ("binance", False, 1, futures, isolated, 1535443.01, 0.0, + 0.0, 135365.00, 3683.979, 1456.84, 0.10, 1114.78), + ("binance", False, 1, futures, isolated, 1535443.01, 0.0, + 0.0, 16300.000, 109.488, 32481.980, 0.025, 18778.73), + ("binance", False, 1, futures, cross, 1535443.01, 71200.81144, + -56354.57, 135365.00, 3683.979, 1456.84, 0.10, 1153.26), + ("binance", False, 1, futures, cross, 1535443.01, 356512.508, + -448192.89, 16300.000, 109.488, 32481.980, 0.025, 26316.89) + ]) +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 +): + 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, + maintenance_amt=maintenance_amt, + position=position, + mm_ratio=mm_ratio + ), 2), expected) diff --git a/tests/leverage/test_liquidation_price.py b/tests/leverage/test_liquidation_price.py deleted file mode 100644 index 4707a5680..000000000 --- a/tests/leverage/test_liquidation_price.py +++ /dev/null @@ -1,121 +0,0 @@ -from math import isclose - -import pytest - -from freqtrade.enums import Collateral, TradingMode -from freqtrade.leverage import liquidation_price - - -# from freqtrade.exceptions import OperationalException - -spot = TradingMode.SPOT -margin = TradingMode.MARGIN -futures = TradingMode.FUTURES - -cross = Collateral.CROSS -isolated = Collateral.ISOLATED - - -@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), - # 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), - # Kraken - ('kraken', "2.0", False, "3.0", spot, None), - ('kraken', "2.0", True, "3.0", spot, cross), - ('kraken', "2.0", False, "1.0", spot, isolated), - # FTX - ('ftx', "2.0", True, "3.0", spot, None), - ('ftx', "2.0", False, "3.0", spot, cross), - ('ftx', "2.0", False, "3.0", spot, isolated), -]) -def test_liquidation_price_is_none( - exchange_name, - open_rate, - is_short, - leverage, - trading_mode, - collateral -): - assert liquidation_price( - exchange_name, - open_rate, - is_short, - leverage, - trading_mode, - collateral, - 1535443.01, - 71200.81144, - -56354.57, - 135365.00, - 3683.979, - 0.10, - ) 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", False, "1.0", margin, isolated), - ('kraken', "2.0", False, "1.0", futures, isolated), - # FTX - ('ftx', "2.0", False, "3.0", margin, isolated), - ('ftx', "2.0", False, "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, ' - 'mm_ratio, expected', - [ - ("binance", False, 1, TradingMode.FUTURES, Collateral.ISOLATED, 1535443.01, 0.0, - 0.0, 135365.00, 3683.979, 1456.84, 0.10, 1114.78), - ("binance", False, 1, TradingMode.FUTURES, Collateral.ISOLATED, 1535443.01, 0.0, - 0.0, 16300.000, 109.488, 32481.980, 0.025, 18778.73), - ("binance", False, 1, TradingMode.FUTURES, Collateral.CROSS, 1535443.01, 71200.81144, - -56354.57, 135365.00, 3683.979, 1456.84, 0.10, 1153.26), - ("binance", False, 1, TradingMode.FUTURES, Collateral.CROSS, 1535443.01, 356512.508, - -448192.89, 16300.000, 109.488, 32481.980, 0.025, 26316.89) - ]) -def test_liquidation_price( - exchange_name, open_rate, is_short, leverage, trading_mode, collateral, wallet_balance, - mm_ex_1, upnl_ex_1, maintenance_amt, position, mm_ratio, expected -): - assert isclose(round(liquidation_price( - exchange_name=exchange_name, - 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, - maintenance_amt=maintenance_amt, - position=position, - mm_ratio=mm_ratio - ), 2), expected) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index affc86692..7c266b156 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -734,7 +734,6 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) ((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071 - exchange_name = gateio, is_short = true (open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate)) (10 + (2 / 1)) / (1 + (0.01 + 0.0006)) = 11.87413417771621