From 00d4820bc108976b759170efa0127e1e7960b5fc Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Nov 2020 07:49:30 +0100 Subject: [PATCH] Add low_profit_pairs --- docs/includes/protections.md | 15 ++++ freqtrade/constants.py | 2 +- .../plugins/protections/low_profit_pairs.py | 81 +++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 freqtrade/plugins/protections/low_profit_pairs.py diff --git a/docs/includes/protections.md b/docs/includes/protections.md index 078ba0c2b..aa0ca0f97 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -21,6 +21,21 @@ Protections will protect your strategy from unexpected events and market conditi !!! Note `StoplossGuard` considers all trades with the results `"stop_loss"` and `"trailing_stop_loss"` if the result was negative. +#### Low Profit Pairs + +`LowProfitpairs` uses all trades for a pair within a `lookback_period` (in minutes) to determine the overall profit ratio. +If that ratio is below `required_profit`, that pair will be locked for `stop_duration` (in minutes). + +```json +"protections": [{ + "method": "LowProfitpairs", + "lookback_period": 60, + "trade_limit": 4, + "stop_duration": 60, + "required_profit": 0.02 +}], +``` + ### Full example of Protections The below example stops trading if more than 4 stoploss occur within a 1 hour (60 minute) limit. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 6319d1f62..812883da0 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', 'CooldownPeriod'] +AVAILABLE_PROTECTIONS = ['StoplossGuard', 'CooldownPeriod', 'LowProfitpairs'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py new file mode 100644 index 000000000..739642de7 --- /dev/null +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -0,0 +1,81 @@ + +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 LowProfitpairs(IProtection): + + def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: + super().__init__(config, protection_config) + + self._lookback_period = protection_config.get('lookback_period', 60) + self._trade_limit = protection_config.get('trade_limit', 1) + self._stop_duration = protection_config.get('stop_duration', 60) + self._required_profit = protection_config.get('required_profit', 0.0) + + def short_desc(self) -> str: + """ + 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.") + + 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.') + + def _low_profit(self, date_now: datetime, pair: str) -> ProtectionReturn: + """ + Evaluate recent trades for pair + """ + look_back_until = date_now - timedelta(minutes=self._lookback_period) + filters = [ + Trade.is_open.is_(False), + Trade.close_date > look_back_until, + ] + if pair: + filters.append(Trade.pair == pair) + trades = Trade.get_trades(filters).all() + if len(trades) < self._trade_limit: + # Not enough trades in the relevant period + return False, None, None + + profit = sum(trade.close_profit for trade in trades) + if profit < self._required_profit: + self.log_on_refresh( + logger.info, + f"Trading for {pair} stopped due to {profit} < {self._required_profit} " + f"within {self._lookback_period} minutes.") + until = date_now + timedelta(minutes=self._stop_duration) + return True, until, self._reason(profit) + + 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 + """ + 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._low_profit(date_now, pair=None)