diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 9b75cb50d..56c790d55 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -46,6 +46,9 @@ class CooldownPeriod(IProtection): # Ignore type error as we know we only get closed trades. trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) + + self.set_unlock_at_as_stop_duration() + until = self.calculate_lock_end([trade], self._stop_duration) return ProtectionReturn( diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index a3ddcfe33..c6cdd3665 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -47,18 +47,7 @@ class IProtection(LoggingMixin, ABC): else: self._lookback_period = int(protection_config.get("lookback_period", 60)) - if "unlock_at" in protection_config: - now_time = datetime.now(timezone.utc) - unlock_at = datetime.strptime(protection_config["unlock_at"], "%H:%M").replace( - day=now_time.day, year=now_time.year, month=now_time.month - ) - - if unlock_at.time() < now_time.time(): - unlock_at = unlock_at.replace(day=now_time.day + 1) - - unlock_at = unlock_at.replace(tzinfo=timezone.utc) - self._stop_duration = self.calculate_timespan(now_time, unlock_at) - self.unlock_at = unlock_at + self.set_unlock_at_as_stop_duration() LoggingMixin.__init__(self, logger) @@ -101,6 +90,36 @@ class IProtection(LoggingMixin, ABC): return self.unlock_at.strftime("%H:%M") return None + def set_unlock_at_as_stop_duration(self) -> None: + """ + Calculates the stop_duration based on the unlock_at protection config value and sets it. + """ + if "unlock_at" in self._protection_config: + self._stop_duration = self.calculate_unlock_at() + return None + + logger.warning( + "Couldn't update the stop duration, because unlock_at is not set in the " + "protection config." + ) + + def calculate_unlock_at(self) -> int: + """ + Calculate and update the stop duration based on the unlock at config. + """ + + now_time = datetime.now(timezone.utc) + unlock_at = datetime.strptime( + str(self._protection_config.get("unlock_at_config")), "%H:%M" + ).replace(day=now_time.day, year=now_time.year, month=now_time.month) + + if unlock_at.time() < now_time.time(): + unlock_at = unlock_at.replace(day=now_time.day + 1) + + self.unlock_at = unlock_at.replace(tzinfo=timezone.utc) + result = IProtection.calculate_timespan(now_time, self.unlock_at) + return result + @abstractmethod def short_desc(self) -> str: """ diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 5904ca276..6024fe894 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -73,6 +73,8 @@ class LowProfitPairs(IProtection): f"within {self._lookback_period} minutes.", logger.info, ) + + self.set_unlock_at_as_stop_duration() until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index fcecdc3d0..3f97d418e 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -73,6 +73,8 @@ class MaxDrawdown(IProtection): f" within {self.lookback_period_str}.", logger.info, ) + + self.set_unlock_at_as_stop_duration() until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 42b04fba7..329b6b772 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -81,7 +81,10 @@ class StoplossGuard(IProtection): f"stoplosses within {self._lookback_period} minutes.", logger.info, ) + + self.set_unlock_at_as_stop_duration() until = self.calculate_lock_end(trades, self._stop_duration) + return ProtectionReturn( lock=True, until=until, diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 94bfc8d1f..4c76693c8 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -189,7 +189,7 @@ def test_protections_init(default_conf, timeframe, expected_lookback, expected_s if isinstance(expected_stop, int): assert man._protection_handlers[0]._stop_duration == expected_stop else: - assert man._protection_handlers[0].unlock_at.strftime("%H:%M") == expected_stop + assert man._protection_handlers[0].unlock_at_str == expected_stop @pytest.mark.parametrize("is_short", [False, True]) @@ -701,19 +701,19 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): "unlock_at": "01:00", }, "[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, " - "2 stoplosses with profit < -5.00% within 12 candles. Unlocking trading at 01:00.'}]", + "2 stoplosses with profit < -5.00% within 12 candles.'}]", None, ), ( {"method": "LowProfitPairs", "lookback_period_candles": 11, "unlock_at": "03:00"}, "[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with " - "profit < 0.0 within 11 candles. Unlocking trading at 03:00.'}]", + "profit < 0.0 within 11 candles.'}]", None, ), ( {"method": "MaxDrawdown", "lookback_period_candles": 20, "unlock_at": "04:00"}, "[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading " - "if drawdown is > 0.0 within 20 candles. Unlocking trading at 04:00.'}]", + "if drawdown is > 0.0 within 20 candles.'}]", None, ), ],