From 31745a9dc2214cdc2760be6955fc2fb487baa6cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 31 Dec 2022 11:19:27 +0100 Subject: [PATCH] bybit: Initial implementation liquidation calculation --- freqtrade/exchange/bybit.py | 63 +++++++++++++++++++++++++++++++++ tests/exchange/test_exchange.py | 12 ++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 35d5ef1d2..cb8faa7a8 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -3,6 +3,7 @@ import logging from typing import Any, Dict, List, Optional, Tuple from freqtrade.enums import MarginMode, TradingMode +from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange from freqtrade.exchange.exchange_utils import timeframe_to_msecs @@ -80,3 +81,65 @@ class Bybit(Exchange): # Convert funding rate to candle pattern data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data] return data + + def dry_run_liquidation_price( + self, + pair: str, + open_rate: float, # Entry price of position + is_short: bool, + amount: float, + stake_amount: float, + leverage: float, + wallet_balance: float, # Or margin balance + mm_ex_1: float = 0.0, # (Binance) Cross only + upnl_ex_1: float = 0.0, # (Binance) Cross only + ) -> Optional[float]: + """ + Important: Must be fetching data from cached values as this is used by backtesting! + PERPETUAL: + bybit: + https://www.bybithelp.com/HelpCenterKnowledge/bybitHC_Article?language=en_US&id=000001067 + + Long: + Liquidation Price = ( + Entry Price * (1 - Initial Margin Rate + Maintenance Margin Rate) + - Extra Margin Added/ Contract) + Short: + Liquidation Price = ( + Entry Price * (1 + Initial Margin Rate - Maintenance Margin Rate) + + Extra Margin Added/ Contract) + + Implementation Note: Extra margin is currently not used. + + :param pair: Pair to calculate liquidation price for + :param open_rate: Entry price of position + :param is_short: True if the trade is a short, false otherwise + :param amount: Absolute value of position size incl. leverage (in base currency) + :param stake_amount: Stake amount - Collateral in settle currency. + :param leverage: Leverage used for this position. + :param trading_mode: SPOT, MARGIN, FUTURES, etc. + :param margin_mode: Either ISOLATED or CROSS + :param wallet_balance: Amount of margin_mode in the wallet being used to trade + Cross-Margin Mode: crossWalletBalance + Isolated-Margin Mode: isolatedWalletBalance + """ + + market = self.markets[pair] + mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, stake_amount) + + if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED: + + if market['inverse']: + raise OperationalException( + "Freqtrade does not yet support inverse contracts") + initial_margin_rate = 1 / leverage + + # See docstring - ignores extra margin! + if is_short: + return open_rate * (1 + initial_margin_rate - mm_ratio) + else: + return open_rate * (1 - initial_margin_rate + mm_ratio) + + else: + raise OperationalException( + "Freqtrade only supports isolated futures for leverage trading") diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 36990afc4..61e99a5ea 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -5115,7 +5115,7 @@ def test_get_liquidation_price1(mocker, default_conf): ) -@pytest.mark.parametrize('liquidation_buffer', [0.0, 0.05]) +@pytest.mark.parametrize('liquidation_buffer', [0.0]) @pytest.mark.parametrize( "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [ (False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), @@ -5144,6 +5144,16 @@ def test_get_liquidation_price1(mocker, default_conf): (False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207), (False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), (False, 'futures', 'okx', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), + # bybit, long + (False, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 0.1), + (False, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 6.7666666), + (False, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 8.1), + (False, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 9.1), + # bybit, short + (True, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 19.9), + (True, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 13.233333), + (True, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 11.9), + (True, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 10.9), ] ) def test_get_liquidation_price(