Allow "detailed" backtesting timeframe to look into the candle
This commit is contained in:
		| @@ -22,7 +22,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", | |||||||
|                         "max_open_trades", "stake_amount", "fee", "pairs"] |                         "max_open_trades", "stake_amount", "fee", "pairs"] | ||||||
|  |  | ||||||
| ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", | ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", | ||||||
|                                         "enable_protections", "dry_run_wallet", |                                         "enable_protections", "dry_run_wallet", "detail_timeframe", | ||||||
|                                         "strategy_list", "export", "exportfilename"] |                                         "strategy_list", "export", "exportfilename"] | ||||||
|  |  | ||||||
| ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", | ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", | ||||||
|   | |||||||
| @@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = { | |||||||
|         help='Override the value of the `stake_amount` configuration setting.', |         help='Override the value of the `stake_amount` configuration setting.', | ||||||
|     ), |     ), | ||||||
|     # Backtesting |     # Backtesting | ||||||
|  |     "detail_timeframe": Arg( | ||||||
|  |         '--timeframe-detail', | ||||||
|  |         help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).', | ||||||
|  |     ), | ||||||
|     "position_stacking": Arg( |     "position_stacking": Arg( | ||||||
|         '--eps', '--enable-position-stacking', |         '--eps', '--enable-position-stacking', | ||||||
|         help='Allow buying the same pair multiple times (position stacking).', |         help='Allow buying the same pair multiple times (position stacking).', | ||||||
|   | |||||||
| @@ -242,6 +242,9 @@ class Configuration: | |||||||
|             except ValueError: |             except ValueError: | ||||||
|                 pass |                 pass | ||||||
|  |  | ||||||
|  |         self._args_to_config(config, argname='detail_timeframe', | ||||||
|  |                              logstring='Parameter --detail-timeframe detected, ' | ||||||
|  |                              'using {} for intra-candle backtesting') | ||||||
|         self._args_to_config(config, argname='stake_amount', |         self._args_to_config(config, argname='stake_amount', | ||||||
|                              logstring='Parameter --stake-amount detected, ' |                              logstring='Parameter --stake-amount detected, ' | ||||||
|                              'overriding stake_amount to: {} ...') |                              'overriding stake_amount to: {} ...') | ||||||
|   | |||||||
| @@ -86,6 +86,16 @@ class Backtesting: | |||||||
|                                        "configuration or as cli argument `--timeframe 5m`") |                                        "configuration or as cli argument `--timeframe 5m`") | ||||||
|         self.timeframe = str(self.config.get('timeframe')) |         self.timeframe = str(self.config.get('timeframe')) | ||||||
|         self.timeframe_min = timeframe_to_minutes(self.timeframe) |         self.timeframe_min = timeframe_to_minutes(self.timeframe) | ||||||
|  |         # Load detail timeframe if specified | ||||||
|  |         self.timeframe_detail = str(self.config.get('detail_timeframe', '')) | ||||||
|  |         if self.timeframe_detail: | ||||||
|  |             self.timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail) | ||||||
|  |             if self.timeframe_min <= self.timeframe_detail_min: | ||||||
|  |                 raise OperationalException( | ||||||
|  |                     "Detail timeframe must be smaller than strategy timeframe.") | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             self.timeframe_detail_min = 0 | ||||||
|  |  | ||||||
|         self.pairlists = PairListManager(self.exchange, self.config) |         self.pairlists = PairListManager(self.exchange, self.config) | ||||||
|         if 'VolumePairList' in self.pairlists.name_list: |         if 'VolumePairList' in self.pairlists.name_list: | ||||||
| @@ -158,7 +168,7 @@ class Backtesting: | |||||||
|                 conf['protections'] = strategy.protections |                 conf['protections'] = strategy.protections | ||||||
|             self.protections = ProtectionManager(self.config, strategy.protections) |             self.protections = ProtectionManager(self.config, strategy.protections) | ||||||
|  |  | ||||||
|     def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: |     def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange, Dict[str, DataFrame]]: | ||||||
|         """ |         """ | ||||||
|         Loads backtest data and returns the data combined with the timerange |         Loads backtest data and returns the data combined with the timerange | ||||||
|         as tuple. |         as tuple. | ||||||
| @@ -174,6 +184,18 @@ class Backtesting: | |||||||
|             fail_without_data=True, |             fail_without_data=True, | ||||||
|             data_format=self.config.get('dataformat_ohlcv', 'json'), |             data_format=self.config.get('dataformat_ohlcv', 'json'), | ||||||
|         ) |         ) | ||||||
|  |         if self.timeframe_detail: | ||||||
|  |             detail_data = history.load_data( | ||||||
|  |                 datadir=self.config['datadir'], | ||||||
|  |                 pairs=self.pairlists.whitelist, | ||||||
|  |                 timeframe=self.timeframe_detail, | ||||||
|  |                 timerange=self.timerange, | ||||||
|  |                 startup_candles=0, | ||||||
|  |                 fail_without_data=True, | ||||||
|  |                 data_format=self.config.get('dataformat_ohlcv', 'json'), | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             detail_data = None | ||||||
|  |  | ||||||
|         min_date, max_date = history.get_timerange(data) |         min_date, max_date = history.get_timerange(data) | ||||||
|  |  | ||||||
| @@ -186,7 +208,7 @@ class Backtesting: | |||||||
|                                                  self.required_startup, min_date) |                                                  self.required_startup, min_date) | ||||||
|  |  | ||||||
|         self.progress.set_new_value(1) |         self.progress.set_new_value(1) | ||||||
|         return data, self.timerange |         return data, self.timerange, detail_data | ||||||
|  |  | ||||||
|     def prepare_backtest(self, enable_protections): |     def prepare_backtest(self, enable_protections): | ||||||
|         """ |         """ | ||||||
| @@ -318,7 +340,8 @@ class Backtesting: | |||||||
|         else: |         else: | ||||||
|             return sell_row[OPEN_IDX] |             return sell_row[OPEN_IDX] | ||||||
|  |  | ||||||
|     def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: |     def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, | ||||||
|  |                                          sell_row: Tuple) -> Optional[LocalTrade]: | ||||||
|         sell_candle_time = sell_row[DATE_IDX].to_pydatetime() |         sell_candle_time = sell_row[DATE_IDX].to_pydatetime() | ||||||
|         sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX],  # type: ignore |         sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX],  # type: ignore | ||||||
|                                          sell_candle_time, sell_row[BUY_IDX], |                                          sell_candle_time, sell_row[BUY_IDX], | ||||||
| @@ -346,6 +369,29 @@ class Backtesting: | |||||||
|  |  | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |     def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: | ||||||
|  |         if self.timeframe_detail: | ||||||
|  |             sell_candle_time = sell_row[DATE_IDX].to_pydatetime() | ||||||
|  |             sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min) | ||||||
|  |  | ||||||
|  |             detail_data = self.detail_data[trade.pair] | ||||||
|  |             detail_data = detail_data.loc[ | ||||||
|  |                 (detail_data['date'] >= sell_candle_time) & | ||||||
|  |                 (detail_data['date'] < sell_candle_end) | ||||||
|  |              ] | ||||||
|  |             detail_data['buy'] = sell_row[BUY_IDX] | ||||||
|  |             detail_data['sell'] = sell_row[SELL_IDX] | ||||||
|  |             headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] | ||||||
|  |             for det_row in detail_data[headers].values.tolist(): | ||||||
|  |                 res = self._get_sell_trade_entry_for_candle(trade, det_row) | ||||||
|  |                 if res: | ||||||
|  |                     return res | ||||||
|  |  | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             return self._get_sell_trade_entry_for_candle(trade, sell_row) | ||||||
|  |  | ||||||
|     def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]: |     def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]: | ||||||
|         try: |         try: | ||||||
|             stake_amount = self.wallets.get_trade_stake_amount(pair, None) |             stake_amount = self.wallets.get_trade_stake_amount(pair, None) | ||||||
| @@ -591,7 +637,7 @@ class Backtesting: | |||||||
|         """ |         """ | ||||||
|         data: Dict[str, Any] = {} |         data: Dict[str, Any] = {} | ||||||
|  |  | ||||||
|         data, timerange = self.load_bt_data() |         data, timerange, self.detail_data = self.load_bt_data() | ||||||
|         logger.info("Dataload complete. Calculating indicators") |         logger.info("Dataload complete. Calculating indicators") | ||||||
|  |  | ||||||
|         for strat in self.strategylist: |         for strat in self.strategylist: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user