From 75a5161650072fc5592bcb63e6f11cc8e2aab07f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 09:53:13 +0100 Subject: [PATCH] Support multis-strategy backtests with protections --- freqtrade/optimize/backtesting.py | 14 ++++++++ freqtrade/persistence/models.py | 8 +++++ freqtrade/persistence/pairlock_middleware.py | 8 +++++ .../plugins/protections/stoploss_guard.py | 4 +-- tests/optimize/test_backtesting.py | 34 +++++++++++++++++-- 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 1819e5617..e3f5e7671 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -120,8 +120,10 @@ class Backtesting: self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0]) Trade.use_db = False + Trade.reset_trades() PairLocks.timeframe = self.config['timeframe'] PairLocks.use_db = False + PairLocks.reset_locks() if self.config.get('enable_protections', False): self.protections = ProtectionManager(self.config) @@ -130,6 +132,11 @@ class Backtesting: # Load one (first) strategy self._set_strategy(self.strategylist[0]) + def __del__(self): + LoggingMixin.show_output = True + PairLocks.use_db = True + Trade.use_db = True + def _set_strategy(self, strategy): """ Load strategy into backtesting @@ -321,6 +328,13 @@ class Backtesting: f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}" ) trades = [] + PairLocks.use_db = False + Trade.use_db = False + if enable_protections: + # Reset persisted data - used for protections only + + PairLocks.reset_locks() + Trade.reset_trades() # Use dict of lists with data for performance # (looping lists is a lot faster than pandas DataFrames) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 9b8f561b8..07f4b5a4f 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -327,6 +327,14 @@ class Trade(_DECL_BASE): 'open_order_id': self.open_order_id, } + @staticmethod + def reset_trades() -> None: + """ + Resets all trades. Only active for backtesting mode. + """ + if not Trade.use_db: + Trade.trades = [] + def adjust_min_max_rates(self, current_price: float) -> None: """ Adjust the max_rate and min_rate. diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index 6ce91ee6b..8644146d8 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -21,6 +21,14 @@ class PairLocks(): timeframe: str = '' + @staticmethod + def reset_locks() -> None: + """ + Resets all locks. Only active for backtesting mode. + """ + if not PairLocks.use_db: + PairLocks.locks = [] + @staticmethod def lock_pair(pair: str, until: datetime, reason: str = None, *, now: datetime = None) -> None: """ diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 4dbc71048..71e74880c 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -55,8 +55,8 @@ class StoplossGuard(IProtection): # 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 + trades = [trade for trade in trades1 if str(trade.sell_reason) == SellType.STOP_LOSS.value + or (str(trade.sell_reason) == SellType.TRAILING_STOP_LOSS.value and trade.close_profit < 0)] if len(trades) > self._trade_limit: diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 45cbea68e..15ad18bf9 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -95,6 +95,7 @@ def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None: end_date=max_date, max_open_trades=1, position_stacking=False, + enable_protections=config.get('enable_protections', False), ) # results :: assert len(results) == num_results @@ -532,10 +533,39 @@ def test_processed(default_conf, mocker, testdatadir) -> None: def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir) -> None: - # TODO: Evaluate usefullness of this, the patterns and buy-signls are unrealistic mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - tests = [['raise', 19], ['lower', 0], ['sine', 35]] + tests = [ + ['sine', 35], + ['raise', 19], + ['lower', 0], + ['sine', 35], + ['raise', 19] + ] + # While buy-signals are unrealistic, running backtesting + # over and over again should not cause different results + for [contour, numres] in tests: + simple_backtest(default_conf, contour, numres, mocker, testdatadir) + +def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None: + # TODO: Evaluate usefullness of this, the patterns and buy-signls are unrealistic + default_conf['protections'] = [ + { + "method": "CooldownPeriod", + "stop_duration": 3, + }] + + default_conf['enable_protections'] = True + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + tests = [ + ['sine', 9], + ['raise', 10], + ['lower', 0], + ['sine', 9], + ['raise', 10], + ] + # While buy-signals are unrealistic, running backtesting + # over and over again should not cause different results for [contour, numres] in tests: simple_backtest(default_conf, contour, numres, mocker, testdatadir)