diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 7b37b2303..e5eae01dd 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -22,13 +22,13 @@ class CooldownPeriod(IProtection): """ LockReason to use """ - return (f'Cooldown period for {self._stop_duration} min.') + return (f'Cooldown period for {self.stop_duration_str}.') def short_desc(self) -> str: """ Short method description - used for startup-messages """ - return (f"{self.name} - Cooldown period of {self._stop_duration} min.") + return (f"{self.name} - Cooldown period of {self.stop_duration_str}.") def _cooldown_period(self, pair: str, date_now: datetime, ) -> ProtectionReturn: """ @@ -42,7 +42,7 @@ class CooldownPeriod(IProtection): ] trade = Trade.get_trades(filters).first() if trade: - self.log_once(f"Cooldown for {pair} for {self._stop_duration}.", logger.info) + self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) until = self.calculate_lock_end([trade], self._stop_duration) return True, until, self._reason() diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 7a5a87f47..684bf6cd3 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Tuple from freqtrade.exchange import timeframe_to_minutes +from freqtrade.misc import plural from freqtrade.mixins import LoggingMixin from freqtrade.persistence import Trade @@ -26,12 +27,16 @@ class IProtection(LoggingMixin, ABC): self._protection_config = protection_config tf_in_min = timeframe_to_minutes(config['timeframe']) if 'stop_duration_candles' in protection_config: - self._stop_duration = (tf_in_min * protection_config.get('stop_duration_candles')) + self._stop_duration_candles = protection_config.get('stop_duration_candles', 1) + self._stop_duration = (tf_in_min * self._stop_duration_candles) else: + self._stop_duration_candles = None self._stop_duration = protection_config.get('stop_duration', 60) if 'lookback_period_candles' in protection_config: - self._lookback_period = tf_in_min * protection_config.get('lookback_period_candles', 60) + self._lookback_period_candles = protection_config.get('lookback_period_candles', 1) + self._lookback_period = tf_in_min * self._lookback_period_candles else: + self._lookback_period_candles = None self._lookback_period = protection_config.get('lookback_period', 60) LoggingMixin.__init__(self, logger) @@ -40,6 +45,30 @@ class IProtection(LoggingMixin, ABC): def name(self) -> str: return self.__class__.__name__ + @property + def stop_duration_str(self) -> str: + """ + Output configured stop duration in either candles or minutes + """ + if self._stop_duration_candles: + return (f"{self._stop_duration_candles} " + f"{plural(self._stop_duration_candles, 'candle', 'candles')}") + else: + return (f"{self._stop_duration} " + f"{plural(self._stop_duration, 'minute', 'minutes')}") + + @property + def lookback_period_str(self) -> str: + """ + Output configured lookback period in either candles or minutes + """ + if self._lookback_period_candles: + return (f"{self._lookback_period_candles} " + f"{plural(self._lookback_period_candles, 'candle', 'candles')}") + else: + return (f"{self._lookback_period} " + f"{plural(self._lookback_period, 'minute', 'minutes')}") + @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 70ef5b080..4721ea1a2 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -26,14 +26,14 @@ class LowProfitPairs(IProtection): Short method description - used for startup-messages """ return (f"{self.name} - Low Profit Protection, locks pairs with " - f"profit < {self._required_profit} within {self._lookback_period} minutes.") + f"profit < {self._required_profit} within {self.lookback_period_str}.") def _reason(self, profit: float) -> str: """ LockReason to use """ - return (f'{profit} < {self._required_profit} in {self._lookback_period} min, ' - f'locking for {self._stop_duration} min.') + return (f'{profit} < {self._required_profit} in {self.lookback_period_str}, ' + f'locking for {self.stop_duration_str}.') def _low_profit(self, date_now: datetime, pair: str) -> ProtectionReturn: """ diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 2a83cdeba..e0c91243b 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -30,14 +30,14 @@ class MaxDrawdown(IProtection): Short method description - used for startup-messages """ return (f"{self.name} - Max drawdown protection, stop trading if drawdown is > " - f"{self._max_allowed_drawdown} within {self._lookback_period} minutes.") + f"{self._max_allowed_drawdown} within {self.lookback_period_str}.") def _reason(self, drawdown: float) -> str: """ LockReason to use """ - return (f'{drawdown} > {self._max_allowed_drawdown} in {self._lookback_period} min, ' - f'locking for {self._stop_duration} min.') + return (f'{drawdown} > {self._max_allowed_drawdown} in {self.lookback_period_str}, ' + f'locking for {self.stop_duration_str}.') def _max_drawdown(self, date_now: datetime) -> ProtectionReturn: """ @@ -62,7 +62,7 @@ class MaxDrawdown(IProtection): if drawdown > self._max_allowed_drawdown: self.log_once( f"Trading stopped due to Max Drawdown {drawdown:.2f} < {self._max_allowed_drawdown}" - f" within {self._lookback_period} minutes.", logger.info) + f" within {self.lookback_period_str}.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) return True, until, self._reason(drawdown) diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 520607337..7a13ead57 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -29,7 +29,7 @@ class StoplossGuard(IProtection): Short method description - used for startup-messages """ return (f"{self.name} - Frequent Stoploss Guard, {self._trade_limit} stoplosses " - f"within {self._lookback_period} minutes.") + f"within {self.lookback_period_str}.") def _reason(self) -> str: """ diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 819ae805e..22fe33e19 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -350,7 +350,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): None ), ({"method": "CooldownPeriod", "stop_duration": 60}, - "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 min.'}]", + "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 minutes.'}]", None ), ({"method": "LowProfitPairs", "lookback_period": 60, "stop_duration": 60}, @@ -363,6 +363,26 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): "within 60 minutes.'}]", None ), + ({"method": "StoplossGuard", "lookback_period_candles": 12, "trade_limit": 2, + "stop_duration": 60}, + "[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, " + "2 stoplosses within 12 candles.'}]", + None + ), + ({"method": "CooldownPeriod", "stop_duration_candles": 5}, + "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 5 candles.'}]", + None + ), + ({"method": "LowProfitPairs", "lookback_period_candles": 11, "stop_duration": 60}, + "[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with " + "profit < 0.0 within 11 candles.'}]", + None + ), + ({"method": "MaxDrawdown", "lookback_period_candles": 20, "stop_duration": 60}, + "[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading if drawdown is > 0.0 " + "within 20 candles.'}]", + None + ), ]) def test_protection_manager_desc(mocker, default_conf, protectionconf, desc_expected, exception_expected):