Prepare protections for backtesting

This commit is contained in:
Matthias 2020-11-16 20:09:34 +01:00
parent 3426e99b8b
commit 98c88fa58e
6 changed files with 84 additions and 34 deletions

View File

@ -202,6 +202,10 @@ class Trade(_DECL_BASE):
""" """
__tablename__ = 'trades' __tablename__ = 'trades'
use_db: bool = True
# Trades container for backtesting
trades: List['Trade'] = []
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan") orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan")
@ -562,6 +566,43 @@ class Trade(_DECL_BASE):
else: else:
return Trade.query 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 @staticmethod
def get_open_trades() -> List[Any]: def get_open_trades() -> List[Any]:
""" """

View File

@ -3,7 +3,7 @@ Protection manager class
""" """
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, List from typing import Dict, List, Optional
from freqtrade.persistence import PairLocks from freqtrade.persistence import PairLocks
from freqtrade.plugins.protections import IProtection from freqtrade.plugins.protections import IProtection
@ -43,8 +43,9 @@ class ProtectionManager():
""" """
return [{p.name: p.short_desc()} for p in self._protection_handlers] return [{p.name: p.short_desc()} for p in self._protection_handlers]
def global_stop(self) -> bool: def global_stop(self, now: Optional[datetime] = None) -> bool:
now = datetime.now(timezone.utc) if not now:
now = datetime.now(timezone.utc)
result = False result = False
for protection_handler in self._protection_handlers: for protection_handler in self._protection_handlers:
if protection_handler.has_global_stop: if protection_handler.has_global_stop:
@ -57,8 +58,9 @@ class ProtectionManager():
result = True result = True
return result return result
def stop_per_pair(self, pair) -> bool: def stop_per_pair(self, pair, now: Optional[datetime] = None) -> bool:
now = datetime.now(timezone.utc) if not now:
now = datetime.now(timezone.utc)
result = False result = False
for protection_handler in self._protection_handlers: for protection_handler in self._protection_handlers:
if protection_handler.has_local_stop: if protection_handler.has_local_stop:

View File

@ -35,13 +35,16 @@ class CooldownPeriod(IProtection):
Get last trade for this pair Get last trade for this pair
""" """
look_back_until = date_now - timedelta(minutes=self._stop_duration) look_back_until = date_now - timedelta(minutes=self._stop_duration)
filters = [ # filters = [
Trade.is_open.is_(False), # Trade.is_open.is_(False),
Trade.close_date > look_back_until, # Trade.close_date > look_back_until,
Trade.pair == pair, # Trade.pair == pair,
] # ]
trade = Trade.get_trades(filters).first() # trade = Trade.get_trades(filters).first()
if trade: 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) self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info)
until = self.calculate_lock_end([trade], self._stop_duration) until = self.calculate_lock_end([trade], self._stop_duration)

View File

@ -40,13 +40,15 @@ class LowProfitPairs(IProtection):
Evaluate recent trades for pair Evaluate recent trades for pair
""" """
look_back_until = date_now - timedelta(minutes=self._lookback_period) look_back_until = date_now - timedelta(minutes=self._lookback_period)
filters = [ # filters = [
Trade.is_open.is_(False), # Trade.is_open.is_(False),
Trade.close_date > look_back_until, # Trade.close_date > look_back_until,
] # ]
if pair: # if pair:
filters.append(Trade.pair == pair) # filters.append(Trade.pair == pair)
trades = Trade.get_trades(filters).all()
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: if len(trades) < self._trade_limit:
# Not enough trades in the relevant period # Not enough trades in the relevant period
return False, None, None return False, None, None

View File

@ -44,11 +44,8 @@ class MaxDrawdown(IProtection):
Evaluate recent trades for drawdown ... Evaluate recent trades for drawdown ...
""" """
look_back_until = date_now - timedelta(minutes=self._lookback_period) look_back_until = date_now - timedelta(minutes=self._lookback_period)
filters = [
Trade.is_open.is_(False), trades = Trade.get_trades_proxy(is_open=False, close_date=look_back_until)
Trade.close_date > look_back_until,
]
trades = Trade.get_trades(filters).all()
trades_df = pd.DataFrame([trade.to_json() for trade in trades]) trades_df = pd.DataFrame([trade.to_json() for trade in trades])

View File

@ -43,16 +43,21 @@ class StoplossGuard(IProtection):
Evaluate recent trades Evaluate recent trades
""" """
look_back_until = date_now - timedelta(minutes=self._lookback_period) look_back_until = date_now - timedelta(minutes=self._lookback_period)
filters = [ # filters = [
Trade.is_open.is_(False), # Trade.is_open.is_(False),
Trade.close_date > look_back_until, # Trade.close_date > look_back_until,
or_(Trade.sell_reason == SellType.STOP_LOSS.value, # or_(Trade.sell_reason == SellType.STOP_LOSS.value,
and_(Trade.sell_reason == SellType.TRAILING_STOP_LOSS.value, # and_(Trade.sell_reason == SellType.TRAILING_STOP_LOSS.value,
Trade.close_profit < 0)) # Trade.close_profit < 0))
] # ]
if pair: # if pair:
filters.append(Trade.pair == pair) # filters.append(Trade.pair == pair)
trades = Trade.get_trades(filters).all() # 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: if len(trades) > self._trade_limit:
self.log_once(f"Trading stopped due to {self._trade_limit} " self.log_once(f"Trading stopped due to {self._trade_limit} "