From 2a66c33a4e2c6ae4d116321a2fd8b46638f34354 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 24 Oct 2020 16:52:26 +0200 Subject: [PATCH] Add locks per pair --- config_full.json.example | 4 ++ freqtrade/constants.py | 2 +- freqtrade/plugins/protectionmanager.py | 9 +++ .../plugins/protections/cooldown_period.py | 68 +++++++++++++++++++ freqtrade/plugins/protections/iprotection.py | 9 +++ .../plugins/protections/stoploss_guard.py | 11 ++- 6 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 freqtrade/plugins/protections/cooldown_period.py diff --git a/config_full.json.example b/config_full.json.example index eb20065ce..839f99dbd 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -81,6 +81,10 @@ "lookback_period": 60, "trade_limit": 4, "stopduration": 60 + }, + { + "method": "CooldownPeriod", + "stopduration": 20 } ], "exchange": { diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 9a93bfae3..d06047f4c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -27,7 +27,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter'] -AVAILABLE_PROTECTIONS = ['StoplossGuard'] +AVAILABLE_PROTECTIONS = ['StoplossGuard', 'CooldownPeriod'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index c4822a323..b0929af88 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -57,3 +57,12 @@ class ProtectionManager(): PairLocks.lock_pair('*', until, reason) return True return False + + def stop_per_pair(self, pair) -> bool: + now = datetime.now(timezone.utc) + for protection_handler in self._protection_handlers: + result, until, reason = protection_handler.stop_per_pair(pair, now) + if result and until: + PairLocks.lock_pair(pair, until, reason) + return True + return False diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py new file mode 100644 index 000000000..c6b6685b2 --- /dev/null +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -0,0 +1,68 @@ + +import logging +from datetime import datetime, timedelta +from typing import Any, Dict + + +from freqtrade.persistence import Trade +from freqtrade.plugins.protections import IProtection, ProtectionReturn + + +logger = logging.getLogger(__name__) + + +class CooldownPeriod(IProtection): + + def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: + super().__init__(config, protection_config) + + self._stopduration = protection_config.get('stopduration', 60) + + def _reason(self) -> str: + """ + LockReason to use + """ + return (f'Cooldown period for {self._stopduration} min.') + + def short_desc(self) -> str: + """ + Short method description - used for startup-messages + """ + return (f"{self.name} - Cooldown period.") + + def _cooldown_period(self, pair: str, date_now: datetime, ) -> ProtectionReturn: + """ + Get last trade for this pair + """ + look_back_until = date_now - timedelta(minutes=self._stopduration) + filters = [ + Trade.is_open.is_(False), + Trade.close_date > look_back_until, + Trade.pair == pair, + ] + trade = Trade.get_trades(filters).first() + if trade: + self.log_on_refresh(logger.info, f"Cooldown for {pair} for {self._stopduration}.") + until = trade.close_date + timedelta(minutes=self._stopduration) + return True, until, self._reason() + + return False, None, None + + def global_stop(self, date_now: datetime) -> ProtectionReturn: + """ + Stops trading (position entering) for all pairs + This must evaluate to true for the whole period of the "cooldown period". + :return: Tuple of [bool, until, reason]. + If true, all pairs will be locked with until + """ + # Not implemented for cooldown period. + return False, None, None + + def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + """ + Stops trading (position entering) for this pair + This must evaluate to true for the whole period of the "cooldown period". + :return: Tuple of [bool, until, reason]. + If true, this pair will be locked with until + """ + return self._cooldown_period(pair, date_now) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index cadf01184..5dbcf72f6 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -36,3 +36,12 @@ class IProtection(LoggingMixin, ABC): Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". """ + + @abstractmethod + def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + """ + Stops trading (position entering) for this pair + This must evaluate to true for the whole period of the "cooldown period". + :return: Tuple of [bool, until, reason]. + If true, this pair will be locked with until + """ diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index db3655a38..18888b854 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta -from typing import Any, Dict, Tuple +from typing import Any, Dict from sqlalchemy import and_, or_ @@ -68,3 +68,12 @@ class StoplossGuard(IProtection): If true, all pairs will be locked with until """ return self._stoploss_guard(date_now, pair=None) + + def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + """ + Stops trading (position entering) for this pair + This must evaluate to true for the whole period of the "cooldown period". + :return: Tuple of [bool, until, reason]. + If true, this pair will be locked with until + """ + return False, None, None