diff --git a/config_full.json.example b/config_full.json.example index 5ee2a1faf..96aa82d5f 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -75,6 +75,13 @@ "refresh_period": 1440 } ], + "protections": [ + { + "method": "StoplossGuard", + "lookback_period": 60, + "trade_limit": 4 + } + ], "exchange": { "name": "bittrex", "sandbox": false, diff --git a/docs/includes/protections.md b/docs/includes/protections.md new file mode 100644 index 000000000..078ba0c2b --- /dev/null +++ b/docs/includes/protections.md @@ -0,0 +1,36 @@ +## Protections + +Protections will protect your strategy from unexpected events and market conditions. + +### Available Protection Handlers + +* [`StoplossGuard`](#stoploss-guard) (default, if not configured differently) + +#### Stoploss Guard + +`StoplossGuard` selects all trades within a `lookback_period` (in minutes), and determines if the amount of trades that resulted in stoploss are above `trade_limit` - in which case it will stop trading until this condition is no longer true. + +```json +"protections": [{ + "method": "StoplossGuard", + "lookback_period": 60, + "trade_limit": 4 +}], +``` + +!!! Note + `StoplossGuard` considers all trades with the results `"stop_loss"` and `"trailing_stop_loss"` if the result was negative. + +### Full example of Protections + +The below example stops trading if more than 4 stoploss occur within a 1 hour (60 minute) limit. + +```json +"protections": [ + { + "method": "StoplossGuard", + "lookback_period": 60, + "trade_limit": 4 + } + ], +``` diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index ff64ca789..5185c93f0 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -14,8 +14,7 @@ logger = logging.getLogger(__name__) class ProtectionManager(): - def __init__(self, exchange, config: dict) -> None: - self._exchange = exchange + def __init__(self, config: dict) -> None: self._config = config self._protection_handlers: List[IProtection] = [] @@ -26,8 +25,6 @@ class ProtectionManager(): continue protection_handler = ProtectionResolver.load_protection( protection_handler_config['method'], - exchange=exchange, - protectionmanager=self, config=config, protection_config=protection_handler_config, ) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index b10856f70..75d1fb3ad 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -1,6 +1,7 @@ import logging from abc import ABC, abstractmethod +from datetime import datetime from typing import Any, Dict @@ -9,8 +10,9 @@ logger = logging.getLogger(__name__) class IProtection(ABC): - def __init__(self, config: Dict[str, Any]) -> None: + def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: self._config = config + self._protection_config = protection_config @property def name(self) -> str: @@ -22,3 +24,10 @@ class IProtection(ABC): Short method description - used for startup-messages -> Please overwrite in subclasses """ + + @abstractmethod + def stop_trade_enters_global(self, date_now: datetime) -> bool: + """ + Stops trading (position entering) for all pairs + This must evaluate to true for the whole period of the "cooldown period". + """ diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py new file mode 100644 index 000000000..3418dd1da --- /dev/null +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -0,0 +1,55 @@ + +import logging +from datetime import datetime, timedelta +from typing import Any, Dict + +from sqlalchemy import or_, and_ + +from freqtrade.persistence import Trade +from freqtrade.plugins.protections import IProtection +from freqtrade.strategy.interface import SellType + + +logger = logging.getLogger(__name__) + + +class StoplossGuard(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', 10) + + def short_desc(self) -> str: + """ + Short method description - used for startup-messages + """ + return f"{self.name} - Frequent Stoploss Guard" + + def _stoploss_guard(self, date_now: datetime, pair: str = None) -> bool: + """ + Evaluate recent trades + """ + look_back_until = date_now - timedelta(minutes=self._lookback_period) + filters = [ + Trade.is_open.is_(False), + Trade.close_date > look_back_until, + or_(Trade.sell_reason == SellType.STOP_LOSS.value, + and_(Trade.sell_reason == SellType.TRAILING_STOP_LOSS.value, + Trade.close_profit < 0)) + ] + if pair: + filters.append(Trade.pair == pair) + trades = Trade.get_trades(filters).all() + + if len(trades) > self.trade_limit: + return True + + return False + + def stop_trade_enters_global(self, date_now: datetime) -> bool: + """ + Stops trading (position entering) for all pairs + This must evaluate to true for the whole period of the "cooldown period". + """ + return self._stoploss_guard(date_now, pair=None) diff --git a/freqtrade/resolvers/protection_resolver.py b/freqtrade/resolvers/protection_resolver.py index 9a85104c3..928bd4633 100644 --- a/freqtrade/resolvers/protection_resolver.py +++ b/freqtrade/resolvers/protection_resolver.py @@ -24,21 +24,16 @@ class ProtectionResolver(IResolver): initial_search_path = Path(__file__).parent.parent.joinpath('plugins/protections').resolve() @staticmethod - def load_protection(protection_name: str, exchange, protectionmanager, - config: Dict, protection_config: Dict) -> IProtection: + def load_protection(protection_name: str, config: Dict, protection_config: Dict) -> IProtection: """ Load the protection with protection_name :param protection_name: Classname of the pairlist - :param exchange: Initialized exchange class - :param protectionmanager: Initialized protection manager :param config: configuration dictionary :param protection_config: Configuration dedicated to this pairlist :return: initialized Protection class """ return ProtectionResolver.load_object(protection_name, config, - kwargs={'exchange': exchange, - 'pairlistmanager': protectionmanager, - 'config': config, - 'pairlistconfig': protection_config, + kwargs={'config': config, + 'protection_config': protection_config, }, )