diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 56c790d55..30a611189 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -18,16 +18,23 @@ class CooldownPeriod(IProtection): """ LockReason to use """ - reason = f"Cooldown period for {self.stop_duration_str}." + reason = "Cooldown period" + if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." - return reason + return f"{reason} until {self.unlock_at_str}." + else: + return f"{reason}of {self.stop_duration_str}." def short_desc(self) -> str: """ - Short method description - used for startup-messages + Short method description - used for startup messages """ - return f"{self.name} - Cooldown period of {self.stop_duration_str}." + result = f"{self.name} - Cooldown period " + + if self.unlock_at_str is not None: + return f"{result} until {self.unlock_at_str}." + else: + return f"{result}of {self.stop_duration_str}." def _cooldown_period(self, pair: str, date_now: datetime) -> Optional[ProtectionReturn]: """ @@ -47,9 +54,10 @@ class CooldownPeriod(IProtection): 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) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end([trade], self._stop_duration) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index da0fc7a78..bf86caefe 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -36,19 +36,21 @@ class IProtection(LoggingMixin, ABC): self.unlock_at: Optional[datetime] = None tf_in_min = timeframe_to_minutes(config["timeframe"]) - if "stop_duration_candles" in protection_config: - self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) - self._stop_duration = tf_in_min * self._stop_duration_candles + if "unlock_at" in protection_config: + self.unlock_at = self.calculate_unlock_at() else: - self._stop_duration = int(protection_config.get("stop_duration", 60)) + if "stop_duration_candles" in protection_config: + self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) + self._stop_duration = tf_in_min * self._stop_duration_candles + else: + self._stop_duration = int(protection_config.get("stop_duration", 60)) + if "lookback_period_candles" in protection_config: self._lookback_period_candles = int(protection_config.get("lookback_period_candles", 1)) self._lookback_period = tf_in_min * self._lookback_period_candles else: self._lookback_period = int(protection_config.get("lookback_period", 60)) - self.set_unlock_at_as_stop_duration() - LoggingMixin.__init__(self, logger) @property @@ -90,24 +92,10 @@ class IProtection(LoggingMixin, ABC): return self.unlock_at.strftime("%H:%M") return None - def set_unlock_at_as_stop_duration(self) -> None: + def calculate_unlock_at(self) -> datetime: """ - Calculates the stop_duration based on the unlock_at protection config value and sets it. + Calculate and update the unlock time based on the unlock at config. """ - 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")), "%H:%M" @@ -116,9 +104,7 @@ class IProtection(LoggingMixin, ABC): 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 + return unlock_at.replace(tzinfo=timezone.utc) @abstractmethod def short_desc(self) -> str: @@ -156,7 +142,6 @@ class IProtection(LoggingMixin, ABC): max_date = max_date.replace(tzinfo=timezone.utc) until = max_date + timedelta(minutes=stop_minutes) - return until @staticmethod diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 6024fe894..b997254ab 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -34,12 +34,11 @@ class LowProfitPairs(IProtection): """ LockReason to use """ - reason = ( - f"{profit} < {self._required_profit} in {self.lookback_period_str}, " - f"locking for {self.stop_duration_str}." - ) + reason = f"{profit} < {self._required_profit} in {self.lookback_period_str}, locking" if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." + reason += f" until {self.unlock_at_str}." + else: + reason += f" for {self.stop_duration_str}." return reason def _low_profit( @@ -74,8 +73,10 @@ class LowProfitPairs(IProtection): logger.info, ) - self.set_unlock_at_as_stop_duration() - until = self.calculate_lock_end(trades, self._stop_duration) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 3f97d418e..264ad57d0 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -39,10 +39,12 @@ class MaxDrawdown(IProtection): """ reason = ( f"{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, " - f"locking for {self.stop_duration_str}." + f"locking " ) if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." + reason += f" until {self.unlock_at_str}." + else: + reason += f" for {self.stop_duration_str}." return reason def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]: @@ -74,8 +76,10 @@ class MaxDrawdown(IProtection): logger.info, ) - self.set_unlock_at_as_stop_duration() - until = self.calculate_lock_end(trades, self._stop_duration) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 329b6b772..f36a2f157 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -36,12 +36,11 @@ class StoplossGuard(IProtection): """ LockReason to use """ - reason = ( - f"{self._trade_limit} stoplosses in {self._lookback_period} min, " - f"locking for {self._stop_duration} min." - ) + reason = f"{self._trade_limit} stoplosses in {self._lookback_period} min, " f"locking " if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." + reason += f" until {self.unlock_at_str}." + else: + reason += f" for {self._stop_duration} min." return reason def _stoploss_guard( @@ -82,8 +81,10 @@ class StoplossGuard(IProtection): logger.info, ) - self.set_unlock_at_as_stop_duration() - until = self.calculate_lock_end(trades, self._stop_duration) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( lock=True,