diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 0f841e2a7..434734ef0 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -155,7 +155,7 @@ CONF_SCHEMA = { 'ignore_roi_if_buy_signal': {'type': 'boolean'}, 'ignore_buying_expired_candle_after': {'type': 'number'}, 'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, - 'collateral_type': {'type': 'string', 'enum': COLLATERAL_TYPES}, + 'collateral': {'type': 'string', 'enum': COLLATERAL_TYPES}, 'backtest_breakdown': { 'type': 'array', 'items': {'type': 'string', 'enum': BACKTEST_BREAKDOWNS} diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6e11f3eb1..82360f429 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -107,8 +107,8 @@ class FreqtradeBot(LoggingMixin): self.trading_mode = TradingMode(self.config.get('trading_mode', 'spot')) self.collateral_type: Optional[Collateral] = None - if 'collateral_type' in self.config: - self.collateral_type = Collateral(self.config['collateral_type']) + if 'collateral' in self.config: + self.collateral_type = Collateral(self.config['collateral']) self._schedule = Scheduler() diff --git a/freqtrade/leverage/liquidation_price.py b/freqtrade/leverage/liquidation_price.py index a5a9a6e56..e52d762eb 100644 --- a/freqtrade/leverage/liquidation_price.py +++ b/freqtrade/leverage/liquidation_price.py @@ -106,8 +106,8 @@ def liquidation_price( trading_mode=trading_mode, collateral=collateral, # type: ignore wallet_balance=wallet_balance, - # mm_ex_1=mm_ex_1, - # upnl_ex_1=upnl_ex_1, + 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, @@ -212,7 +212,6 @@ def binance( :param open_rate: Entry Price of position (one-way mode) :param mm_ratio: Maintenance margin rate of position (one-way mode) """ - # TODO-lev: Additional arguments, fill in formulas wb = wallet_balance tmm_1 = 0.0 if collateral == Collateral.ISOLATED else mm_ex_1 upnl_1 = 0.0 if collateral == Collateral.ISOLATED else upnl_ex_1 @@ -223,7 +222,6 @@ def binance( mmr_b = mm_ratio if trading_mode == TradingMode.MARGIN and collateral == Collateral.CROSS: - # TODO-lev: perform a calculation based on this formula # https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed exception("binance", trading_mode, collateral) elif trading_mode == TradingMode.FUTURES and collateral == Collateral.ISOLATED: @@ -235,11 +233,11 @@ def binance( position * mmr_b - side_1 * position) elif trading_mode == TradingMode.FUTURES and collateral == Collateral.CROSS: - # TODO-lev: perform a calculation based on this formula # https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93 # Liquidation Price of USDⓈ-M Futures Contracts Cross # Isolated margin mode, then TMM=0,UPNL=0 + # * Untested return (wb - tmm_1 + upnl_1 + cum_b - side_1 * position * ep1) / ( position * mmr_b - side_1 * position) @@ -253,18 +251,17 @@ def kraken( leverage: float, trading_mode: TradingMode, collateral: Collateral + # ... ): """ Calculates the liquidation price on Kraken :param trading_mode: spot, margin, futures :param collateral: cross, isolated """ - # TODO-lev: Additional arguments, fill in formulas if collateral == Collateral.CROSS: if trading_mode == TradingMode.MARGIN: exception("kraken", trading_mode, collateral) - # TODO-lev: perform a calculation based on this formula # https://support.kraken.com/hc/en-us/articles/203325763-Margin-Call-Level-and-Margin-Liquidation-Level elif trading_mode == TradingMode.FUTURES: exception("kraken", trading_mode, collateral) @@ -279,6 +276,7 @@ def ftx( leverage: float, trading_mode: TradingMode, collateral: Collateral + # ... ): """ Calculates the liquidation price on FTX @@ -286,7 +284,6 @@ def ftx( :param collateral: cross, isolated """ if collateral == Collateral.CROSS: - # TODO-lev: Additional arguments, fill in formulas exception("ftx", trading_mode, collateral) # If nothing was returned diff --git a/tests/conftest.py b/tests/conftest.py index 207f6ae24..2bacb498e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -823,6 +823,8 @@ def get_markets(): 'margin': True, 'type': 'spot', 'contractSize': None, + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'amount': 8, 'price': 8 @@ -860,6 +862,8 @@ def get_markets(): 'margin': True, 'type': 'spot', 'contractSize': None, + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'amount': 8, 'price': 8 @@ -892,6 +896,8 @@ def get_markets(): 'active': True, 'spot': True, 'type': 'spot', + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'price': 8, 'amount': 8, @@ -923,6 +929,8 @@ def get_markets(): 'active': True, 'spot': True, 'type': 'spot', + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'price': 8, 'amount': 8, @@ -955,6 +963,8 @@ def get_markets(): 'spot': True, 'type': 'spot', 'contractSize': None, + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'price': 8, 'amount': 8, @@ -1023,6 +1033,8 @@ def get_markets(): 'spot': False, 'type': 'swap', 'contractSize': 0.01, + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'amount': 8, 'price': 8 diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gateio.py index cf3e44d71..a648b229a 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gateio.py @@ -37,6 +37,8 @@ def test_validate_order_types_gateio(default_conf, mocker): ("DOGE/USDT:USDT", None), ]) def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker, pair, mm_ratio): + api_mock = MagicMock() + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio") mocker.patch( 'freqtrade.exchange.Exchange.markets', PropertyMock( @@ -71,6 +73,4 @@ def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker, pair, mm_rat } ) ) - api_mock = MagicMock() - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio") assert exchange.get_maintenance_ratio_and_amt(pair) == [mm_ratio, None] diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 373ffb215..3651ba7b7 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -707,21 +707,45 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) CandleType.SPOT) in refresh_mock.call_args[0][0] -@pytest.mark.parametrize("trading_mode", [ - 'spot', - # TODO-lev: Enable other modes - # 'margin', 'futures' -] -) -@pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short,trading_mode,exchange_name,margin_mode,liq_price", [ + (False, 'spot', 'binance', '', None), + (True, 'spot', 'binance', '', None), + (False, 'spot', 'gateio', '', None), + (True, 'spot', 'gateio', '', None), + (True, 'futures', 'binance', 'isolated', 13.217821782178218), + (False, 'futures', 'binance', 'isolated', 6.717171717171718), + (True, 'futures', 'gateio', 'isolated', 13.198706526760379), + (False, 'futures', 'gateio', 'isolated', 6.735367414292449), + # TODO-lev: Okex + # (False, 'spot', 'okex', 'isolated', ...), + # (True, 'futures', 'okex', 'isolated', ...), +]) def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, - limit_order_open, is_short, trading_mode) -> None: + limit_order_open, is_short, trading_mode, + exchange_name, margin_mode, liq_price) -> None: + ''' + exchange_name = binance, is_short = true + (wb + cum_b - side_1 * position * ep1) / (position * mmr_b - side_1 * position) + ((2 + 0.01) - ((-1) * 0.6 * 10)) / ((0.6 * 0.01) - ((-1) * 0.6)) = 13.217821782178218 + exchange_name = binance, is_short = false + (wb + cum_b - side_1 * position * ep1) / (position * mmr_b - side_1 * position) + (2 + 0.01 - 1 * 0.6 * 10) / (0.6 * 0.01 - 1 * 0.6) = 6.717171717171718 + + exchange_name = gateio, is_short = true + (open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate)) + (10 + (6 / 0.6)) / (1 + (0.01 + 0.0002)) + 13.198706526760379 + + exchange_name = gateio, is_short = false + (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate)) + (10 - (2 / 0.6)) / (1 - (0.01 + 0.0002)) = 6.735367414292449 + ''' open_order = limit_order_open[enter_side(is_short)] order = limit_order[enter_side(is_short)] default_conf_usdt['trading_mode'] = trading_mode - leverage = 1.0 if trading_mode == 'spot' else 3.0 - default_conf_usdt['collateral'] = 'cross' + leverage = 1.0 if trading_mode == 'spot' else 5.0 + default_conf_usdt['collateral'] = margin_mode patch_RPCManager(mocker) patch_exchange(mocker) freqtrade = FreqtradeBot(default_conf_usdt) @@ -886,14 +910,24 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, assert trade.open_rate_requested == 10 # In case of custom entry price not float type + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + name=exchange_name, + get_maintenance_ratio_and_amt=MagicMock(return_value=[0.01, 0.01]) + ) order['status'] = 'open' order['id'] = '5568' freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) trade = Trade.query.all()[8] + # Trade(id=9, pair=ETH/USDT, amount=0.20000000, is_short=False, + # leverage=1.0, open_rate=10.00000000, open_since=...) + # Trade(id=9, pair=ETH/USDT, amount=0.60000000, is_short=True, + # leverage=3.0, open_rate=10.00000000, open_since=...) trade.is_short = is_short assert trade assert trade.open_rate_requested == 10 + assert trade.isolated_liq == liq_price @pytest.mark.parametrize("is_short", [False, True])