From 98c88fa58e890630a2c9221d48a31c42423a28ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Nov 2020 20:09:34 +0100 Subject: [PATCH] Prepare protections for backtesting --- freqtrade/persistence/models.py | 41 +++++++++++++++++++ freqtrade/plugins/protectionmanager.py | 12 +++--- .../plugins/protections/cooldown_period.py | 17 ++++---- .../plugins/protections/low_profit_pairs.py | 16 ++++---- .../protections/max_drawdown_protection.py | 7 +--- .../plugins/protections/stoploss_guard.py | 25 ++++++----- 6 files changed, 84 insertions(+), 34 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 04d5a7695..d262a6784 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -202,6 +202,10 @@ class Trade(_DECL_BASE): """ __tablename__ = 'trades' + use_db: bool = True + # Trades container for backtesting + trades: List['Trade'] = [] + id = Column(Integer, primary_key=True) orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan") @@ -562,6 +566,43 @@ class Trade(_DECL_BASE): else: return Trade.query + @staticmethod + def get_trades_proxy(*, pair: str = None, is_open: bool = None, + open_date: datetime = None, close_date: datetime = None, + ) -> List['Trade']: + """ + Helper function to query Trades. + Returns a List of trades, filtered on the parameters given. + In live mode, converts the filter to a database query and returns all rows + In Backtest mode, uses filters on Trade.trades to get the result. + + :return: unsorted List[Trade] + """ + if Trade.use_db: + trade_filter = [] + if pair: + trade_filter.append(Trade.pair == pair) + if open_date: + trade_filter.append(Trade.open_date > open_date) + if close_date: + trade_filter.append(Trade.close_date > close_date) + if is_open is not None: + trade_filter.append(Trade.is_open.is_(is_open)) + return Trade.get_trades(trade_filter).all() + else: + # Offline mode - without database + sel_trades = [trade for trade in Trade.trades] + if pair: + sel_trades = [trade for trade in sel_trades if trade.pair == pair] + if open_date: + sel_trades = [trade for trade in sel_trades if trade.open_date > open_date] + if close_date: + sel_trades = [trade for trade in sel_trades if trade.close_date + and trade.close_date > close_date] + if is_open is not None: + sel_trades = [trade for trade in sel_trades if trade.is_open == is_open] + return sel_trades + @staticmethod def get_open_trades() -> List[Any]: """ diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index 03a09cc58..a8edd4e4b 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -3,7 +3,7 @@ Protection manager class """ import logging from datetime import datetime, timezone -from typing import Dict, List +from typing import Dict, List, Optional from freqtrade.persistence import PairLocks from freqtrade.plugins.protections import IProtection @@ -43,8 +43,9 @@ class ProtectionManager(): """ return [{p.name: p.short_desc()} for p in self._protection_handlers] - def global_stop(self) -> bool: - now = datetime.now(timezone.utc) + def global_stop(self, now: Optional[datetime] = None) -> bool: + if not now: + now = datetime.now(timezone.utc) result = False for protection_handler in self._protection_handlers: if protection_handler.has_global_stop: @@ -57,8 +58,9 @@ class ProtectionManager(): result = True return result - def stop_per_pair(self, pair) -> bool: - now = datetime.now(timezone.utc) + def stop_per_pair(self, pair, now: Optional[datetime] = None) -> bool: + if not now: + now = datetime.now(timezone.utc) result = False for protection_handler in self._protection_handlers: if protection_handler.has_local_stop: diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index e5eae01dd..2d7d7b4c7 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -35,13 +35,16 @@ class CooldownPeriod(IProtection): Get last trade for this pair """ look_back_until = date_now - timedelta(minutes=self._stop_duration) - filters = [ - Trade.is_open.is_(False), - Trade.close_date > look_back_until, - Trade.pair == pair, - ] - trade = Trade.get_trades(filters).first() - if trade: + # filters = [ + # Trade.is_open.is_(False), + # Trade.close_date > look_back_until, + # Trade.pair == pair, + # ] + # trade = Trade.get_trades(filters).first() + trades = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) + if trades: + # Get latest trade + trade = sorted(trades, key=lambda t: t.close_date)[-1] self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) until = self.calculate_lock_end([trade], self._stop_duration) diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 4721ea1a2..9d5ed35b4 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -40,13 +40,15 @@ class LowProfitPairs(IProtection): 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() + # filters = [ + # Trade.is_open.is_(False), + # Trade.close_date > look_back_until, + # ] + # if pair: + # filters.append(Trade.pair == pair) + + trades = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) + # trades = Trade.get_trades(filters).all() if len(trades) < self._trade_limit: # Not enough trades in the relevant period return False, None, None diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index e0c91243b..f1c77d1d9 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -44,11 +44,8 @@ class MaxDrawdown(IProtection): Evaluate recent trades for drawdown ... """ look_back_until = date_now - timedelta(minutes=self._lookback_period) - filters = [ - Trade.is_open.is_(False), - Trade.close_date > look_back_until, - ] - trades = Trade.get_trades(filters).all() + + trades = Trade.get_trades_proxy(is_open=False, close_date=look_back_until) trades_df = pd.DataFrame([trade.to_json() for trade in trades]) diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 7a13ead57..4dbc71048 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -43,16 +43,21 @@ class StoplossGuard(IProtection): 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() + # 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() + + trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) + trades = [trade for trade in trades1 if trade.sell_reason == SellType.STOP_LOSS + or (trade.sell_reason == SellType.TRAILING_STOP_LOSS + and trade.close_profit < 0)] if len(trades) > self._trade_limit: self.log_once(f"Trading stopped due to {self._trade_limit} "