From 995be90f91a21c351a5c22264135694f3e11d62b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Jul 2022 20:35:23 +0200 Subject: [PATCH 01/11] Liquidation should be a separate exit type --- freqtrade/enums/exittype.py | 1 + freqtrade/optimize/backtesting.py | 14 ++++++++++---- freqtrade/persistence/trade_model.py | 12 ++---------- freqtrade/plugins/protections/stoploss_guard.py | 2 +- freqtrade/strategy/interface.py | 13 ++++++++++++- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/freqtrade/enums/exittype.py b/freqtrade/enums/exittype.py index b2c5b62ea..1e15e70cd 100644 --- a/freqtrade/enums/exittype.py +++ b/freqtrade/enums/exittype.py @@ -9,6 +9,7 @@ class ExitType(Enum): STOP_LOSS = "stop_loss" STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange" TRAILING_STOP_LOSS = "trailing_stop_loss" + LIQUIDATION = "liquidation" EXIT_SIGNAL = "exit_signal" FORCE_EXIT = "force_exit" EMERGENCY_EXIT = "emergency_exit" diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 4d16dc0f1..598ce6710 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -381,7 +381,8 @@ class Backtesting: Get close rate for backtesting result """ # Special handling if high or low hit STOP_LOSS or ROI - if exit.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): + if exit.exit_type in ( + ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS, ExitType.LIQUIDATION): return self._get_close_rate_for_stoploss(row, trade, exit, trade_dur) elif exit.exit_type == (ExitType.ROI): return self._get_close_rate_for_roi(row, trade, exit, trade_dur) @@ -396,11 +397,16 @@ class Backtesting: is_short = trade.is_short or False leverage = trade.leverage or 1.0 side_1 = -1 if is_short else 1 + if exit.exit_type == ExitType.LIQUIDATION and trade.liquidation_price: + stoploss_value = trade.liquidation_price + else: + stoploss_value = trade.stop_loss + if is_short: - if trade.stop_loss < row[LOW_IDX]: + if stoploss_value < row[LOW_IDX]: return row[OPEN_IDX] else: - if trade.stop_loss > row[HIGH_IDX]: + if stoploss_value > row[HIGH_IDX]: return row[OPEN_IDX] # Special case: trailing triggers within same candle as trade opened. Assume most @@ -433,7 +439,7 @@ class Backtesting: return max(row[LOW_IDX], stop_rate) # Set close_rate to stoploss - return trade.stop_loss + return stoploss_value def _get_close_rate_for_roi(self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int) -> float: diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 5f302de71..2ff65d9d0 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -511,17 +511,9 @@ class LocalTrade(): Method you should use to set self.stop_loss. Assures stop_loss is not passed the liquidation price """ - if self.liquidation_price is not None: - if self.is_short: - sl = min(stop_loss, self.liquidation_price) - else: - sl = max(stop_loss, self.liquidation_price) - else: - sl = stop_loss - if not self.stop_loss: - self.initial_stop_loss = sl - self.stop_loss = sl + self.initial_stop_loss = stop_loss + self.stop_loss = stop_loss self.stop_loss_pct = -1 * abs(percent) self.stoploss_last_update = datetime.utcnow() diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index abc90a685..e80d13e9d 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -49,7 +49,7 @@ class StoplossGuard(IProtection): trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) trades = [trade for trade in trades1 if (str(trade.exit_reason) in ( ExitType.TRAILING_STOP_LOSS.value, ExitType.STOP_LOSS.value, - ExitType.STOPLOSS_ON_EXCHANGE.value) + ExitType.STOPLOSS_ON_EXCHANGE.value, ExitType.LIQUIDATION.value) and trade.close_profit and trade.close_profit < self._profit_limit)] if self._only_per_side: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index c60817c99..f50721583 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -963,7 +963,7 @@ class IStrategy(ABC, HyperStrategyMixin): # ROI # Trailing stoploss - if stoplossflag.exit_type == ExitType.STOP_LOSS: + if stoplossflag.exit_type in (ExitType.STOP_LOSS, ExitType.LIQUIDATION): logger.debug(f"{trade.pair} - Stoploss hit. exit_type={stoplossflag.exit_type}") exits.append(stoplossflag) @@ -1035,6 +1035,17 @@ class IStrategy(ABC, HyperStrategyMixin): sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short) sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short) + liq_higher_long = (trade.liquidation_price + and trade.liquidation_price >= (low or current_rate) + and not trade.is_short) + liq_lower_short = (trade.liquidation_price + and trade.liquidation_price <= (high or current_rate) + and trade.is_short) + + if (liq_higher_long or liq_lower_short): + logger.debug(f"{trade.pair} - Liquidation price hit. exit_type=ExitType.LIQUIDATION") + return ExitCheckTuple(exit_type=ExitType.LIQUIDATION) + # evaluate if the stoploss was hit if stoploss is not on exchange # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to # regular stoploss handling. From 8711b7d99f224b3d0ee26553e32774dd247d605a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Jul 2022 20:37:10 +0200 Subject: [PATCH 02/11] Liquidations cannot be rejected. --- freqtrade/freqtradebot.py | 5 +++-- freqtrade/optimize/backtesting.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 43608cae7..d58c05d7f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1463,11 +1463,12 @@ class FreqtradeBot(LoggingMixin): amount = self._safe_exit_amount(trade.pair, trade.amount) time_in_force = self.strategy.order_time_in_force['exit'] - if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( + if (exit_check.exit_type != ExitType.LIQUIDATION and not strategy_safe_wrapper( + self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, time_in_force=time_in_force, exit_reason=exit_reason, sell_reason=exit_reason, # sellreason -> compatibility - current_time=datetime.now(timezone.utc)): + current_time=datetime.now(timezone.utc))): logger.info(f"User denied exit for {trade.pair}.") return False diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 598ce6710..6bbace185 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -598,7 +598,8 @@ class Backtesting: # Confirm trade exit: time_in_force = self.strategy.order_time_in_force['exit'] - if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( + if (exit_.exit_type != ExitType.LIQUIDATION and not strategy_safe_wrapper( + self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, # type: ignore[arg-type] order_type='limit', @@ -607,7 +608,7 @@ class Backtesting: time_in_force=time_in_force, sell_reason=exit_reason, # deprecated exit_reason=exit_reason, - current_time=exit_candle_time): + current_time=exit_candle_time)): return None trade.exit_reason = exit_reason From f57ecb18615dc2e75fc73d0da78540ea0ebd6804 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Jul 2022 06:55:42 +0200 Subject: [PATCH 03/11] Simplify adjust_stop test --- tests/test_persistence.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 838c4c22a..4703075eb 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1537,26 +1537,26 @@ def test_adjust_stop_loss(fee): # Get percent of profit with a custom rate (Higher than open rate) trade.adjust_stop_loss(1.3, -0.1) - assert round(trade.stop_loss, 8) == 1.17 + assert pytest.approx(trade.stop_loss) == 1.17 assert trade.stop_loss_pct == -0.1 assert trade.initial_stop_loss == 0.95 assert trade.initial_stop_loss_pct == -0.05 # current rate lower again ... should not change trade.adjust_stop_loss(1.2, 0.1) - assert round(trade.stop_loss, 8) == 1.17 + assert pytest.approx(trade.stop_loss) == 1.17 assert trade.initial_stop_loss == 0.95 assert trade.initial_stop_loss_pct == -0.05 # current rate higher... should raise stoploss trade.adjust_stop_loss(1.4, 0.1) - assert round(trade.stop_loss, 8) == 1.26 + assert pytest.approx(trade.stop_loss) == 1.26 assert trade.initial_stop_loss == 0.95 assert trade.initial_stop_loss_pct == -0.05 # Initial is true but stop_loss set - so doesn't do anything trade.adjust_stop_loss(1.7, 0.1, True) - assert round(trade.stop_loss, 8) == 1.26 + assert pytest.approx(trade.stop_loss) == 1.26 assert trade.initial_stop_loss == 0.95 assert trade.initial_stop_loss_pct == -0.05 assert trade.stop_loss_pct == -0.1 From 9852733ef7f7284765b8092eb6cd27edd2b7c0e8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Jul 2022 06:58:59 +0200 Subject: [PATCH 04/11] Improve tests to align with modified logic --- freqtrade/persistence/trade_model.py | 6 ------ tests/test_persistence.py | 17 ++++++++++------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 2ff65d9d0..919750886 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -535,14 +535,8 @@ class LocalTrade(): leverage = self.leverage or 1.0 if self.is_short: new_loss = float(current_price * (1 + abs(stoploss / leverage))) - # If trading with leverage, don't set the stoploss below the liquidation price - if self.liquidation_price: - new_loss = min(self.liquidation_price, new_loss) else: new_loss = float(current_price * (1 - abs(stoploss / leverage))) - # If trading with leverage, don't set the stoploss below the liquidation price - if self.liquidation_price: - new_loss = max(self.liquidation_price, new_loss) # no stop loss assigned yet if self.initial_stop_loss_pct is None or refresh: diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 4703075eb..5476e1d50 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -133,13 +133,14 @@ def test_set_stop_loss_isolated_liq(fee): trade.set_isolated_liq(0.11) trade._set_stop_loss(0.1, 0) assert trade.liquidation_price == 0.11 - assert trade.stop_loss == 0.11 + # Stoploss does not change from liquidation price + assert trade.stop_loss == 0.1 assert trade.initial_stop_loss == 0.1 # lower stop doesn't move stoploss trade._set_stop_loss(0.1, 0) assert trade.liquidation_price == 0.11 - assert trade.stop_loss == 0.11 + assert trade.stop_loss == 0.1 assert trade.initial_stop_loss == 0.1 trade.stop_loss = None @@ -174,13 +175,14 @@ def test_set_stop_loss_isolated_liq(fee): trade.set_isolated_liq(0.07) trade._set_stop_loss(0.1, (1.0 / 8.0)) assert trade.liquidation_price == 0.07 - assert trade.stop_loss == 0.07 + # Stoploss does not change from liquidation price + assert trade.stop_loss == 0.1 assert trade.initial_stop_loss == 0.08 # Stop doesn't move stop higher trade._set_stop_loss(0.1, (1.0 / 9.0)) assert trade.liquidation_price == 0.07 - assert trade.stop_loss == 0.07 + assert trade.stop_loss == 0.1 assert trade.initial_stop_loss == 0.08 @@ -1609,9 +1611,10 @@ def test_adjust_stop_loss_short(fee): assert trade.initial_stop_loss == 1.05 assert trade.initial_stop_loss_pct == -0.05 assert trade.stop_loss_pct == -0.1 + # Liquidation price is lower than stoploss - so liquidation would trigger first. trade.set_isolated_liq(0.63) trade.adjust_stop_loss(0.59, -0.1) - assert trade.stop_loss == 0.63 + assert trade.stop_loss == 0.649 assert trade.liquidation_price == 0.63 @@ -2011,8 +2014,8 @@ def test_stoploss_reinitialization_short(default_conf, fee): # Stoploss can't go above liquidation price trade_adj.set_isolated_liq(0.985) trade.adjust_stop_loss(0.9799, -0.05) - assert trade_adj.stop_loss == 0.985 - assert trade_adj.stop_loss == 0.985 + assert trade_adj.stop_loss == 0.989699 + assert trade_adj.liquidation_price == 0.985 def test_update_fee(fee): From ff4cc5d3165b53058c1b80f8f94bb16df2345269 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Jul 2022 07:14:25 +0200 Subject: [PATCH 05/11] Revamp liquidation test to actually make sense --- tests/test_persistence.py | 79 +++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 5476e1d50..5dbd6b86b 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -120,70 +120,83 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.stop_loss is None assert trade.initial_stop_loss is None - trade._set_stop_loss(0.1, (1.0 / 9.0)) + trade.adjust_stop_loss(2.0, 0.2, True) assert trade.liquidation_price == 0.09 - assert trade.stop_loss == 0.1 - assert trade.initial_stop_loss == 0.1 + assert trade.stop_loss == 1.8 + assert trade.initial_stop_loss == 1.8 trade.set_isolated_liq(0.08) assert trade.liquidation_price == 0.08 - assert trade.stop_loss == 0.1 - assert trade.initial_stop_loss == 0.1 + assert trade.stop_loss == 1.8 + assert trade.initial_stop_loss == 1.8 trade.set_isolated_liq(0.11) - trade._set_stop_loss(0.1, 0) + trade.adjust_stop_loss(2.0, 0.2) assert trade.liquidation_price == 0.11 # Stoploss does not change from liquidation price - assert trade.stop_loss == 0.1 - assert trade.initial_stop_loss == 0.1 + assert trade.stop_loss == 1.8 + assert trade.initial_stop_loss == 1.8 # lower stop doesn't move stoploss - trade._set_stop_loss(0.1, 0) + trade.adjust_stop_loss(1.8, 0.2) assert trade.liquidation_price == 0.11 - assert trade.stop_loss == 0.1 - assert trade.initial_stop_loss == 0.1 + assert trade.stop_loss == 1.8 + assert trade.initial_stop_loss == 1.8 + + # higher stop does move stoploss + trade.adjust_stop_loss(2.1, 0.1) + assert trade.liquidation_price == 0.11 + assert pytest.approx(trade.stop_loss) == 1.994999 + assert trade.initial_stop_loss == 1.8 trade.stop_loss = None trade.liquidation_price = None trade.initial_stop_loss = None + trade.initial_stop_loss_pct = None - trade._set_stop_loss(0.07, 0) + trade.adjust_stop_loss(2.0, 0.1, True) assert trade.liquidation_price is None - assert trade.stop_loss == 0.07 - assert trade.initial_stop_loss == 0.07 + assert trade.stop_loss == 1.9 + assert trade.initial_stop_loss == 1.9 trade.is_short = True trade.recalc_open_trade_value() trade.stop_loss = None trade.initial_stop_loss = None + trade.initial_stop_loss_pct = None - trade.set_isolated_liq(0.09) - assert trade.liquidation_price == 0.09 + trade.set_isolated_liq(3.09) + assert trade.liquidation_price == 3.09 assert trade.stop_loss is None assert trade.initial_stop_loss is None - trade._set_stop_loss(0.08, (1.0 / 9.0)) - assert trade.liquidation_price == 0.09 - assert trade.stop_loss == 0.08 - assert trade.initial_stop_loss == 0.08 + trade.adjust_stop_loss(2.0, 0.2) + assert trade.liquidation_price == 3.09 + assert trade.stop_loss == 2.2 + assert trade.initial_stop_loss == 2.2 - trade.set_isolated_liq(0.1) - assert trade.liquidation_price == 0.1 - assert trade.stop_loss == 0.08 - assert trade.initial_stop_loss == 0.08 + trade.set_isolated_liq(3.1) + assert trade.liquidation_price == 3.1 + assert trade.stop_loss == 2.2 + assert trade.initial_stop_loss == 2.2 - trade.set_isolated_liq(0.07) - trade._set_stop_loss(0.1, (1.0 / 8.0)) - assert trade.liquidation_price == 0.07 + trade.set_isolated_liq(3.8) + assert trade.liquidation_price == 3.8 # Stoploss does not change from liquidation price - assert trade.stop_loss == 0.1 - assert trade.initial_stop_loss == 0.08 + assert trade.stop_loss == 2.2 + assert trade.initial_stop_loss == 2.2 # Stop doesn't move stop higher - trade._set_stop_loss(0.1, (1.0 / 9.0)) - assert trade.liquidation_price == 0.07 - assert trade.stop_loss == 0.1 - assert trade.initial_stop_loss == 0.08 + trade.adjust_stop_loss(2.0, 0.3) + assert trade.liquidation_price == 3.8 + assert trade.stop_loss == 2.2 + assert trade.initial_stop_loss == 2.2 + + # Stoploss does move lower + trade.adjust_stop_loss(1.8, 0.1) + assert trade.liquidation_price == 3.8 + assert pytest.approx(trade.stop_loss) == 1.89 + assert trade.initial_stop_loss == 2.2 @pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [ From 15752ce3c2d4b2131e9141c3aa496dda25c5d9d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Jul 2022 07:15:01 +0200 Subject: [PATCH 06/11] Rename set_stoploss method to be fully private --- freqtrade/persistence/trade_model.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 919750886..1b8bcc42f 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -506,10 +506,9 @@ class LocalTrade(): return self.liquidation_price = liquidation_price - def _set_stop_loss(self, stop_loss: float, percent: float): + def __set_stop_loss(self, stop_loss: float, percent: float): """ - Method you should use to set self.stop_loss. - Assures stop_loss is not passed the liquidation price + Method used internally to set self.stop_loss. """ if not self.stop_loss: self.initial_stop_loss = stop_loss @@ -540,7 +539,7 @@ class LocalTrade(): # no stop loss assigned yet if self.initial_stop_loss_pct is None or refresh: - self._set_stop_loss(new_loss, stoploss) + self.__set_stop_loss(new_loss, stoploss) self.initial_stop_loss = new_loss self.initial_stop_loss_pct = -1 * abs(stoploss) @@ -555,7 +554,7 @@ class LocalTrade(): # ? decreasing the minimum stoploss if (higher_stop and not self.is_short) or (lower_stop and self.is_short): logger.debug(f"{self.pair} - Adjusting stoploss...") - self._set_stop_loss(new_loss, stoploss) + self.__set_stop_loss(new_loss, stoploss) else: logger.debug(f"{self.pair} - Keeping current stoploss...") From 4da96bc511c1a01ee717f304d60696141797d200 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Jul 2022 07:27:24 +0200 Subject: [PATCH 07/11] Update docs --- docs/strategy-callbacks.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index f584bd1bb..59d221bfc 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -623,6 +623,7 @@ class AwesomeStrategy(IStrategy): !!! Warning `confirm_trade_exit()` can prevent stoploss exits, causing significant losses as this would ignore stoploss exits. + `confirm_trade_exit()` will not be called for Liquidations - as liquidations are forced by the exchange, and therefore cannot be rejected. ## Adjust trade position From 845cecd38fe0acfed72d99eddccc5aea37234cfc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Jul 2022 08:12:48 +0200 Subject: [PATCH 08/11] Add stoploss or liquidation property --- freqtrade/persistence/trade_model.py | 10 ++++++++++ freqtrade/strategy/interface.py | 7 ------- tests/test_persistence.py | 10 +++++++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 1b8bcc42f..244ca79cd 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -302,6 +302,16 @@ class LocalTrade(): # Futures properties funding_fees: Optional[float] = None + @property + def stoploss_or_liquidation(self) -> float: + if self.liquidation_price: + if self.is_short: + return min(self.stop_loss, self.liquidation_price) + else: + return max(self.stop_loss, self.liquidation_price) + + return self.stop_loss + @property def buy_tag(self) -> Optional[str]: """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index f50721583..824f31258 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -1063,13 +1063,6 @@ class IStrategy(ABC, HyperStrategyMixin): f"stoploss is {trade.stop_loss:.6f}, " f"initial stoploss was at {trade.initial_stop_loss:.6f}, " f"trade opened at {trade.open_rate:.6f}") - new_stoploss = ( - trade.stop_loss + trade.initial_stop_loss - if trade.is_short else - trade.stop_loss - trade.initial_stop_loss - ) - logger.debug(f"{trade.pair} - Trailing stop saved " - f"{new_stoploss:.6f}") return ExitCheckTuple(exit_type=exit_type) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 5dbd6b86b..3eca035c9 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -148,6 +148,7 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.liquidation_price == 0.11 assert pytest.approx(trade.stop_loss) == 1.994999 assert trade.initial_stop_loss == 1.8 + assert trade.stoploss_or_liquidation == trade.stop_loss trade.stop_loss = None trade.liquidation_price = None @@ -158,6 +159,7 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.liquidation_price is None assert trade.stop_loss == 1.9 assert trade.initial_stop_loss == 1.9 + assert trade.stoploss_or_liquidation == 1.9 trade.is_short = True trade.recalc_open_trade_value() @@ -174,11 +176,13 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.liquidation_price == 3.09 assert trade.stop_loss == 2.2 assert trade.initial_stop_loss == 2.2 + assert trade.stoploss_or_liquidation == 2.2 trade.set_isolated_liq(3.1) assert trade.liquidation_price == 3.1 assert trade.stop_loss == 2.2 assert trade.initial_stop_loss == 2.2 + assert trade.stoploss_or_liquidation == 2.2 trade.set_isolated_liq(3.8) assert trade.liquidation_price == 3.8 @@ -193,10 +197,14 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.initial_stop_loss == 2.2 # Stoploss does move lower + trade.set_isolated_liq(1.5) trade.adjust_stop_loss(1.8, 0.1) - assert trade.liquidation_price == 3.8 + assert trade.liquidation_price == 1.5 assert pytest.approx(trade.stop_loss) == 1.89 assert trade.initial_stop_loss == 2.2 + assert trade.stoploss_or_liquidation == 1.5 + + @pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [ From dba7a7257dec1371c1bea14c099dbca06474e3ff Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Jul 2022 09:18:15 +0200 Subject: [PATCH 09/11] Use stop_or_liquidation instead of stop_loss --- freqtrade/freqtradebot.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d58c05d7f..7440212c3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1015,7 +1015,7 @@ class FreqtradeBot(LoggingMixin): trade.stoploss_order_id = None logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.warning('Exiting the trade forcefully') - self.execute_trade_exit(trade, trade.stop_loss, exit_check=ExitCheckTuple( + self.execute_trade_exit(trade, stop_price, exit_check=ExitCheckTuple( exit_type=ExitType.EMERGENCY_EXIT)) except ExchangeError: @@ -1114,7 +1114,7 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stop_loss) + stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stoploss_or_liquidation) if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side): # we check if the update is necessary @@ -1132,7 +1132,7 @@ class FreqtradeBot(LoggingMixin): f"for pair {trade.pair}") # Create new stoploss order - if not self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss): + if not self.create_stoploss_order(trade=trade, stop_price=stoploss_norm): logger.warning(f"Could not create trailing stoploss order " f"for pair {trade.pair}.") @@ -1431,14 +1431,15 @@ class FreqtradeBot(LoggingMixin): ) exit_type = 'exit' exit_reason = exit_tag or exit_check.exit_reason - if exit_check.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): + if exit_check.exit_type in ( + ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS, ExitType.LIQUIDATION): exit_type = 'stoploss' # if stoploss is on exchange and we are on dry_run mode, # we consider the sell price stop price if (self.config['dry_run'] and exit_type == 'stoploss' and self.strategy.order_types['stoploss_on_exchange']): - limit = trade.stop_loss + limit = trade.stoploss_or_liquidation # set custom_exit_price if available proposed_limit_rate = limit From d046f0cc5e766ec24f5c88fd967b44ea88698481 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Jul 2022 09:19:48 +0200 Subject: [PATCH 10/11] Improve method wording for liquidation price setter --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/optimize/backtesting.py | 2 +- freqtrade/persistence/trade_model.py | 2 +- tests/test_persistence.py | 22 ++++++++++------------ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7440212c3..3490b58d6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1085,7 +1085,7 @@ class FreqtradeBot(LoggingMixin): if (trade.is_open and stoploss_order and stoploss_order['status'] in ('canceled', 'cancelled')): - if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss): + if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation): return False else: trade.stoploss_order_id = None @@ -1662,7 +1662,7 @@ class FreqtradeBot(LoggingMixin): trade = self.cancel_stoploss_on_exchange(trade) # TODO: Margin will need to use interest_rate as well. # interest_rate = self.exchange.get_interest_rate() - trade.set_isolated_liq(self.exchange.get_liquidation_price( + trade.set_liquidation_price(self.exchange.get_liquidation_price( leverage=trade.leverage, pair=trade.pair, amount=trade.amount, diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6bbace185..2c6cfb0e9 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -814,7 +814,7 @@ class Backtesting: trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) - trade.set_isolated_liq(self.exchange.get_liquidation_price( + trade.set_liquidation_price(self.exchange.get_liquidation_price( pair=pair, open_rate=propose_rate, amount=amount, diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 244ca79cd..44e148a0c 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -507,7 +507,7 @@ class LocalTrade(): self.max_rate = max(current_price, self.max_rate or self.open_rate) self.min_rate = min(current_price_low, self.min_rate or self.open_rate) - def set_isolated_liq(self, liquidation_price: Optional[float]): + def set_liquidation_price(self, liquidation_price: Optional[float]): """ Method you should use to set self.liquidation price. Assures stop_loss is not passed the liquidation price diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 3eca035c9..0c1fc01a5 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -99,7 +99,7 @@ def test_enter_exit_side(fee, is_short): @pytest.mark.usefixtures("init_persistence") -def test_set_stop_loss_isolated_liq(fee): +def test_set_stop_loss_liquidation(fee): trade = Trade( id=2, pair='ADA/USDT', @@ -115,7 +115,7 @@ def test_set_stop_loss_isolated_liq(fee): leverage=2.0, trading_mode=margin ) - trade.set_isolated_liq(0.09) + trade.set_liquidation_price(0.09) assert trade.liquidation_price == 0.09 assert trade.stop_loss is None assert trade.initial_stop_loss is None @@ -125,12 +125,12 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.stop_loss == 1.8 assert trade.initial_stop_loss == 1.8 - trade.set_isolated_liq(0.08) + trade.set_liquidation_price(0.08) assert trade.liquidation_price == 0.08 assert trade.stop_loss == 1.8 assert trade.initial_stop_loss == 1.8 - trade.set_isolated_liq(0.11) + trade.set_liquidation_price(0.11) trade.adjust_stop_loss(2.0, 0.2) assert trade.liquidation_price == 0.11 # Stoploss does not change from liquidation price @@ -167,7 +167,7 @@ def test_set_stop_loss_isolated_liq(fee): trade.initial_stop_loss = None trade.initial_stop_loss_pct = None - trade.set_isolated_liq(3.09) + trade.set_liquidation_price(3.09) assert trade.liquidation_price == 3.09 assert trade.stop_loss is None assert trade.initial_stop_loss is None @@ -178,13 +178,13 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.initial_stop_loss == 2.2 assert trade.stoploss_or_liquidation == 2.2 - trade.set_isolated_liq(3.1) + trade.set_liquidation_price(3.1) assert trade.liquidation_price == 3.1 assert trade.stop_loss == 2.2 assert trade.initial_stop_loss == 2.2 assert trade.stoploss_or_liquidation == 2.2 - trade.set_isolated_liq(3.8) + trade.set_liquidation_price(3.8) assert trade.liquidation_price == 3.8 # Stoploss does not change from liquidation price assert trade.stop_loss == 2.2 @@ -197,7 +197,7 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.initial_stop_loss == 2.2 # Stoploss does move lower - trade.set_isolated_liq(1.5) + trade.set_liquidation_price(1.5) trade.adjust_stop_loss(1.8, 0.1) assert trade.liquidation_price == 1.5 assert pytest.approx(trade.stop_loss) == 1.89 @@ -205,8 +205,6 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.stoploss_or_liquidation == 1.5 - - @pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [ ("binance", False, 3, 10, 0.0005, round(0.0008333333333333334, 8), margin), ("binance", True, 3, 10, 0.0005, 0.000625, margin), @@ -1633,7 +1631,7 @@ def test_adjust_stop_loss_short(fee): assert trade.initial_stop_loss_pct == -0.05 assert trade.stop_loss_pct == -0.1 # Liquidation price is lower than stoploss - so liquidation would trigger first. - trade.set_isolated_liq(0.63) + trade.set_liquidation_price(0.63) trade.adjust_stop_loss(0.59, -0.1) assert trade.stop_loss == 0.649 assert trade.liquidation_price == 0.63 @@ -2033,7 +2031,7 @@ def test_stoploss_reinitialization_short(default_conf, fee): assert trade_adj.initial_stop_loss == 1.01 assert trade_adj.initial_stop_loss_pct == -0.05 # Stoploss can't go above liquidation price - trade_adj.set_isolated_liq(0.985) + trade_adj.set_liquidation_price(0.985) trade.adjust_stop_loss(0.9799, -0.05) assert trade_adj.stop_loss == 0.989699 assert trade_adj.liquidation_price == 0.985 From dc82675f00e5ed1b006eeb1490a3f14147cde979 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Jul 2022 17:28:16 +0200 Subject: [PATCH 11/11] Add Test for liquidation in stop-loss-reached --- tests/strategy/test_interface.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index f6996a7a2..4257b2cf9 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -408,28 +408,31 @@ def test_min_roi_reached3(default_conf, fee) -> None: @pytest.mark.parametrize( - 'profit,adjusted,expected,trailing,custom,profit2,adjusted2,expected2,custom_stop', [ + 'profit,adjusted,expected,liq,trailing,custom,profit2,adjusted2,expected2,custom_stop', [ # Profit, adjusted stoploss(absolute), profit for 2nd call, enable trailing, # enable custom stoploss, expected after 1st call, expected after 2nd call - (0.2, 0.9, ExitType.NONE, False, False, 0.3, 0.9, ExitType.NONE, None), - (0.2, 0.9, ExitType.NONE, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None), - (0.2, 1.14, ExitType.NONE, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS, None), - (0.01, 0.96, ExitType.NONE, True, False, 0.05, 1, ExitType.NONE, None), - (0.05, 1, ExitType.NONE, True, False, -0.01, 1, ExitType.TRAILING_STOP_LOSS, None), + (0.2, 0.9, ExitType.NONE, None, False, False, 0.3, 0.9, ExitType.NONE, None), + (0.2, 0.9, ExitType.NONE, None, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None), + (0.2, 0.9, ExitType.NONE, 0.8, False, False, -0.2, 0.9, ExitType.LIQUIDATION, None), + (0.2, 1.14, ExitType.NONE, None, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS, + None), + (0.01, 0.96, ExitType.NONE, None, True, False, 0.05, 1, ExitType.NONE, None), + (0.05, 1, ExitType.NONE, None, True, False, -0.01, 1, ExitType.TRAILING_STOP_LOSS, None), # Default custom case - trails with 10% - (0.05, 0.95, ExitType.NONE, False, True, -0.02, 0.95, ExitType.NONE, None), - (0.05, 0.95, ExitType.NONE, False, True, -0.06, 0.95, ExitType.TRAILING_STOP_LOSS, None), - (0.05, 1, ExitType.NONE, False, True, -0.06, 1, ExitType.TRAILING_STOP_LOSS, + (0.05, 0.95, ExitType.NONE, None, False, True, -0.02, 0.95, ExitType.NONE, None), + (0.05, 0.95, ExitType.NONE, None, False, True, -0.06, 0.95, ExitType.TRAILING_STOP_LOSS, + None), + (0.05, 1, ExitType.NONE, None, False, True, -0.06, 1, ExitType.TRAILING_STOP_LOSS, lambda **kwargs: -0.05), - (0.05, 1, ExitType.NONE, False, True, 0.09, 1.04, ExitType.NONE, + (0.05, 1, ExitType.NONE, None, False, True, 0.09, 1.04, ExitType.NONE, lambda **kwargs: -0.05), - (0.05, 0.95, ExitType.NONE, False, True, 0.09, 0.98, ExitType.NONE, + (0.05, 0.95, ExitType.NONE, None, False, True, 0.09, 0.98, ExitType.NONE, lambda current_profit, **kwargs: -0.1 if current_profit < 0.6 else -(current_profit * 2)), # Error case - static stoploss in place - (0.05, 0.9, ExitType.NONE, False, True, 0.09, 0.9, ExitType.NONE, + (0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE, lambda **kwargs: None), ]) -def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom, +def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom, profit2, adjusted2, expected2, custom_stop) -> None: strategy = StrategyResolver.load_strategy(default_conf) @@ -442,6 +445,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili fee_close=fee.return_value, exchange='binance', open_rate=1, + liquidation_price=liq, ) trade.adjust_min_max_rates(trade.open_rate, trade.open_rate) strategy.trailing_stop = trailing