From ffca4623e738d8ccf112cb4f7ef68693786f9bbc Mon Sep 17 00:00:00 2001 From: farmage Date: Mon, 4 Jul 2022 10:18:45 +0300 Subject: [PATCH] TratingTime periods protection --- freqtrade/constants.py | 3 +- freqtrade/plugins/protections/trading_time.py | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 freqtrade/plugins/protections/trading_time.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 18dbea259..6790a7441 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -1,4 +1,5 @@ # pragma pylint: disable=too-few-public-methods +# flake8: noqa: E501 """ bot constants @@ -34,7 +35,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'OffsetFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] -AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] +AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard', 'TradingTime'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] BACKTEST_BREAKDOWNS = ['day', 'week', 'month'] BACKTEST_CACHE_AGE = ['none', 'day', 'week', 'month'] diff --git a/freqtrade/plugins/protections/trading_time.py b/freqtrade/plugins/protections/trading_time.py new file mode 100644 index 000000000..38b70ca8d --- /dev/null +++ b/freqtrade/plugins/protections/trading_time.py @@ -0,0 +1,81 @@ +# flake8: noqa: E501 + +import logging +from datetime import datetime, timedelta +from typing import Any, Dict, Optional + +from freqtrade.constants import LongShort +from freqtrade.plugins.protections import IProtection, ProtectionReturn + + +logger = logging.getLogger(__name__) + + +class TradingTime(IProtection): + + has_global_stop: bool = True + has_local_stop: bool = False + + def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: + super().__init__(config, protection_config) + self._start_time = datetime.strptime(protection_config.get('start_time', '00:00'), "%H:%M") + self._end_time = datetime.strptime(protection_config.get('end_time', '23:59'), "%H:%M") + + self._update_trading_period() + + def _update_trading_period(self) -> None: + now = datetime.now() + self.trade_start, self.trade_end = ( + now.replace(hour=self._start_time.hour, minute=self._start_time.minute, second=0), + now.replace(hour=self._end_time.hour, minute=self._end_time.minute, second=0) + ) + + self.next_trading_day = self.trade_start + timedelta(days=1) + + def short_desc(self) -> str: + """ + Short method description - used for startup-messages + """ + return (f"{self.name} - Limit trading to time period") + + def _reason(self) -> str: + """ + LockReason to use + """ + return (f'Current time is not in Allowed time period {self.trade_start}-{self.trade_end}' + f'trading locked until {self.next_trading_day}.') + + def _allowed_trading_period(self, date_now: datetime) -> Optional[ProtectionReturn]: + """ + Evaluate recent trades for drawdown ... + """ + self._update_trading_period() + now = datetime.now() + + if not (self.trade_start < now < self.trade_end): + return ProtectionReturn( + lock=True, + until=self.next_trading_day, + reason=self._reason() + ) + + return None + + def global_stop(self, date_now: datetime, side: LongShort) -> Optional[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 + """ + return self._allowed_trading_period(date_now) + + def stop_per_pair( + self, pair: str, date_now: datetime, side: LongShort) -> Optional[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 None