From ba02605d770d6f7aef5810a712285d3d3ac6f916 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 5 Nov 2021 19:27:06 -0600 Subject: [PATCH] Isolated liq branch passes all tests and has the general structure that it is supposed to, but is patchy, and doesnt get the correct maintenance amt and maintenance margin rate yet --- freqtrade/exchange/exchange.py | 10 ++++- freqtrade/freqtradebot.py | 31 +++++++++----- freqtrade/leverage/liquidation_price.py | 52 +++++++++++++++++------- freqtrade/persistence/models.py | 14 ++++--- tests/exchange/test_exchange.py | 18 ++++++++ tests/leverage/test_liquidation_price.py | 22 +++++----- 6 files changed, 103 insertions(+), 44 deletions(-) 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)