diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ac3edad89..07bc0ae61 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1987,7 +1987,7 @@ class Exchange: def get_liquidation_price(self, pair: str): ''' Set's the margin mode on the exchange to cross or isolated for a specific pair - :param symbol: base/quote currency pair (e.g. "ADA/USDT") + :param pair: base/quote currency pair (e.g. "ADA/USDT") ''' if self._config['dry_run'] or not self.exchange_has("fetchPositions"): # Some exchanges only support one collateral type @@ -2003,6 +2003,14 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + @retrier + def get_mm_amt_rate(self, pair: str, amount: float): + ''' + :return: The maintenance amount, and maintenance margin rate + ''' + # TODO-lev: return the real amounts + return 0, 0.4 + 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/freqtradebot.py b/freqtrade/freqtradebot.py index 113c78a15..93eb27bb4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -21,6 +21,7 @@ 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 @@ -609,24 +610,32 @@ class FreqtradeBot(LoggingMixin): interest_rate = 0.0 isolated_liq = None - # TODO-lev: Uncomment once liq and interest merged in # if TradingMode == TradingMode.MARGIN: # interest_rate = self.exchange.get_interest_rate( # pair=pair, # open_rate=open_rate, # is_short=is_short # ) + maintenance_amt, mm_rate = self.exchange.get_mm_amt_rate(pair, amount) - # if self.collateral_type == Collateral.ISOLATED: - - # isolated_liq = liquidation_price( - # exchange_name=self.exchange.name, - # trading_mode=self.trading_mode, - # open_rate=open_rate, - # amount=amount, - # leverage=leverage, - # is_short=is_short - # ) + if self.collateral_type == Collateral.ISOLATED: + if self.config['dry_run']: + isolated_liq = liquidation_price( + exchange_name=self.exchange.name, + 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, + position=amount * open_rate, + wallet_balance=amount/leverage, # TODO-lev: Is this correct? + maintenance_amt=maintenance_amt, + mm_rate=mm_rate, + ) + else: + isolated_liq = self.exchange.get_liquidation_price(pair) return interest_rate, isolated_liq diff --git a/freqtrade/leverage/liquidation_price.py b/freqtrade/leverage/liquidation_price.py index 7a796773b..2519bab52 100644 --- a/freqtrade/leverage/liquidation_price.py +++ b/freqtrade/leverage/liquidation_price.py @@ -16,7 +16,6 @@ def liquidation_price( upnl_ex_1: Optional[float], maintenance_amt: Optional[float], position: Optional[float], - entry_price: Optional[float], mm_rate: Optional[float] ) -> Optional[float]: if trading_mode == TradingMode.SPOT: @@ -29,17 +28,33 @@ def liquidation_price( ) if exchange_name.lower() == "binance": - if None in [wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, position, entry_price, - mm_rate]: + if ( + wallet_balance is None or + mm_ex_1 is None or + upnl_ex_1 is None or + maintenance_amt is None or + position is None or + mm_rate is None + ): raise OperationalException( f"Parameters wallet_balance, mm_ex_1, upnl_ex_1, " - f"maintenance_amt, position, entry_price, mm_rate " + f"maintenance_amt, position, mm_rate " f"is required by liquidation_price when exchange is {exchange_name.lower()}") # Suppress incompatible type "Optional[float]"; expected "float" as the check exists above. - return binance(open_rate, is_short, leverage, trading_mode, collateral, # type: ignore - wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, # type: ignore - position, entry_price, mm_rate) # type: ignore + 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, + upnl_ex_1=upnl_ex_1, + maintenance_amt=maintenance_amt, # type: ignore + position=position, + mm_rate=mm_rate, + ) # type: ignore elif exchange_name.lower() == "kraken": return kraken(open_rate, is_short, leverage, trading_mode, collateral) elif exchange_name.lower() == "ftx": @@ -76,12 +91,10 @@ def binance( upnl_ex_1: float, maintenance_amt: float, position: float, - entry_price: float, mm_rate: float, ): """ Calculates the liquidation price on Binance - :param open_rate: open_rate :param is_short: true or false :param leverage: leverage in float :param trading_mode: spot, margin, futures @@ -100,7 +113,7 @@ def binance( :param position: Absolute value of position size (one-way mode) - :param entry_price: Entry Price of position (one-way mode) + :param open_rate: Entry Price of position (one-way mode) :param mm_rate: Maintenance margin rate of position (one-way mode) @@ -112,7 +125,7 @@ def binance( cum_b = maintenance_amt side_1 = -1 if is_short else 1 position = abs(position) - ep1 = entry_price + ep1 = open_rate mmr_b = mm_rate if trading_mode == TradingMode.MARGIN and collateral == Collateral.CROSS: @@ -189,6 +202,17 @@ def ftx( if __name__ == '__main__': - print(liquidation_price("binance", 0.0, False, 1, TradingMode.FUTURES, Collateral.ISOLATED, - 1535443.01, 356512.508, - 0.0, 16300.000, 109.488, 32481.980, 0.025)) + 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 f3b68a924..abe6d0237 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -365,9 +365,11 @@ class LocalTrade(): def set_isolated_liq( self, - isolated_liq: Optional[float], - wallet_balance: Optional[float], - current_price: Optional[float] + isolated_liq: Optional[float] = None, + wallet_balance: Optional[float] = None, + current_price: Optional[float] = None, + maintenance_amt: Optional[float] = None, + mm_rate: Optional[float] = None, ): """ Method you should use to set self.liquidation price. @@ -389,9 +391,9 @@ class LocalTrade(): mm_ex_1=0.0, upnl_ex_1=0.0, position=self.amount * current_price, - wallet_balance=wallet_balance, - # TODO-lev: maintenance_amt, - # TODO-lev: mm_rate, + wallet_balance=self.amount / self.leverage, # TODO-lev: Is this correct? + maintenance_amt=maintenance_amt, + mm_rate=mm_rate, ) if isolated_liq is None: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 5836aa4e4..0ac7a6130 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3582,6 +3582,24 @@ def test_calculate_funding_fees( ) == kraken_fee +def test_get_liquidation_price(mocker, default_conf): + + api_mock = MagicMock() + api_mock.fetch_positions = MagicMock() + type(api_mock).has = PropertyMock(return_value={'fetchPositions': True}) + default_conf['dry_run'] = False + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "binance", + "get_liquidation_price", + "fetch_positions", + pair="XRP/USDT" + ) + + @pytest.mark.parametrize('exchange,rate_start,rate_end,d1,d2,amount,expected_fees', [ ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999), ('binance', 0, 2, "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999), diff --git a/tests/leverage/test_liquidation_price.py b/tests/leverage/test_liquidation_price.py index e1fba4c4f..300c316d9 100644 --- a/tests/leverage/test_liquidation_price.py +++ b/tests/leverage/test_liquidation_price.py @@ -54,7 +54,6 @@ def test_liquidation_price_is_none( -56354.57, 135365.00, 3683.979, - 1456.84, 0.10, ) is None @@ -89,23 +88,23 @@ def test_liquidation_price_exception_thrown( @pytest.mark.parametrize( - 'exchange_name, open_rate, is_short, leverage, trading_mode, collateral, wallet_balance, ' - 'mm_ex_1, upnl_ex_1, maintenance_amt, position, entry_price, ' + 'exchange_name, is_short, leverage, trading_mode, collateral, wallet_balance, ' + 'mm_ex_1, upnl_ex_1, maintenance_amt, position, open_rate, ' 'mm_rate, expected', [ - ("binance", 0.0, False, 1, TradingMode.FUTURES, Collateral.ISOLATED, 1535443.01, 0.0, + ("binance", False, 1, TradingMode.FUTURES, Collateral.ISOLATED, 1535443.01, 0.0, 0.0, 135365.00, 3683.979, 1456.84, 0.10, 1114.78), - ("binance", 0.0, False, 1, TradingMode.FUTURES, Collateral.ISOLATED, 1535443.01, 0.0, + ("binance", False, 1, TradingMode.FUTURES, Collateral.ISOLATED, 1535443.01, 0.0, 0.0, 16300.000, 109.488, 32481.980, 0.025, 18778.73), - ("binance", 0.0, False, 1, TradingMode.FUTURES, Collateral.CROSS, 1535443.01, 71200.81144, + ("binance", False, 1, TradingMode.FUTURES, Collateral.CROSS, 1535443.01, 71200.81144, -56354.57, 135365.00, 3683.979, 1456.84, 0.10, 1153.26), - ("binance", 0.0, False, 1, TradingMode.FUTURES, Collateral.CROSS, 1535443.01, 356512.508, + ("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, entry_price, mm_rate, - expected): +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_rate, expected +): assert isclose(round(liquidation_price( exchange_name=exchange_name, open_rate=open_rate, @@ -118,6 +117,5 @@ def test_liquidation_price(exchange_name, open_rate, is_short, leverage, trading upnl_ex_1=upnl_ex_1, maintenance_amt=maintenance_amt, position=position, - entry_price=entry_price, mm_rate=mm_rate ), 2), expected)