def _set_strategy(self, strategy: IStrategy): """ Load strategy into backtesting """ self.strategy: IStrategy = strategy strategy.dp = self.dataprovider # Attach Wallets to Strategy baseclass strategy.wallets = self.wallets # Set stoploss_on_exchange to false for backtesting, # since a "perfect" stoploss-sell is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, DataFrame], timerange: TimeRange): self.progress.init_step(BacktestState.ANALYZE, 0) logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) backtest_start_time = datetime.now(timezone.utc) self._set_strategy(strat) strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)() # Use max_open_trades in backtesting, except --disable-max-market-positions is set if self.config.get('use_max_market_positions', True): # Must come from strategy config, as the strategy may modify this setting. max_open_trades = self.strategy.config['max_open_trades'] else: logger.info( 'Ignoring max_open_trades (--disable-max-market-positions was used) ...') max_open_trades = 0 # need to reprocess data every time to populate signals preprocessed = self.strategy.advise_all_indicators(data) # Trim startup period from analyzed dataframe preprocessed_tmp = trim_dataframes(preprocessed, timerange, self.required_startup) if not preprocessed_tmp: raise OperationalException( "No data left after adjusting for startup candles.") # Use preprocessed_tmp for date generation (the trimmed dataframe). # Backtesting will re-trim the dataframes after buy/sell signal generation. min_date, max_date = history.get_timerange(preprocessed_tmp) logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(max_date - min_date).days} days).') # Execute backtest and store results results = self.backtest( processed=preprocessed, start_date=min_date, end_date=max_date, max_open_trades=max_open_trades, position_stacking=self.config.get('position_stacking', False), enable_protections=self.config.get('enable_protections', False), ) backtest_end_time = datetime.now(timezone.utc) results.update({ 'run_id': self.run_ids.get(strat.get_strategy_name(), ''), 'backtest_start_time': int(backtest_start_time.timestamp()), 'backtest_end_time': int(backtest_end_time.timestamp()), }) self.all_results[self.strategy.get_strategy_name()] = results return min_date, max_date
def _normalize_attributes(strategy: IStrategy) -> IStrategy: """ Normalize attributes to have the correct type. """ # Sort and apply type conversions if hasattr(strategy, 'minimal_roi'): strategy.minimal_roi = dict( sorted({ int(key): value for (key, value) in strategy.minimal_roi.items() }.items(), key=lambda t: t[0])) if hasattr(strategy, 'stoploss'): strategy.stoploss = float(strategy.stoploss) return strategy
def _normalize_attributes(strategy: IStrategy) -> IStrategy: """ Normalize attributes to have the correct type. """ # Assign deprecated variable - to not break users code relying on this. if hasattr(strategy, 'timeframe'): strategy.ticker_interval = strategy.timeframe # Sort and apply type conversions if hasattr(strategy, 'minimal_roi'): strategy.minimal_roi = dict(sorted( {int(key): value for (key, value) in strategy.minimal_roi.items()}.items(), key=lambda t: t[0])) if hasattr(strategy, 'stoploss'): strategy.stoploss = float(strategy.stoploss) return strategy
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange): logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) backtest_start_time = datetime.now(timezone.utc) self._set_strategy(strat) strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)() # Use max_open_trades in backtesting, except --disable-max-market-positions is set if self.config.get('use_max_market_positions', True): # Must come from strategy config, as the strategy may modify this setting. max_open_trades = self.strategy.config['max_open_trades'] else: logger.info( 'Ignoring max_open_trades (--disable-max-market-positions was used) ...' ) max_open_trades = 0 # need to reprocess data every time to populate signals preprocessed = self.strategy.ohlcvdata_to_dataframe(data) # Trim startup period from analyzed dataframe for pair, df in preprocessed.items(): preprocessed[pair] = trim_dataframe( df, timerange, startup_candles=self.required_startup) min_date, max_date = history.get_timerange(preprocessed) logger.info( f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(max_date - min_date).days} days)..') # Execute backtest and store results results = self.backtest( processed=preprocessed, start_date=min_date.datetime, end_date=max_date.datetime, max_open_trades=max_open_trades, position_stacking=self.config.get('position_stacking', False), enable_protections=self.config.get('enable_protections', False), ) backtest_end_time = datetime.now(timezone.utc) self.all_results[self.strategy.get_strategy_name()] = { 'results': results, 'config': self.strategy.config, 'locks': PairLocks.get_all_locks(), 'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']), 'backtest_start_time': int(backtest_start_time.timestamp()), 'backtest_end_time': int(backtest_end_time.timestamp()), } return min_date, max_date
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange): logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) backtest_start_time = datetime.now(timezone.utc) self._set_strategy(strat) strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)() # Use max_open_trades in backtesting, except --disable-max-market-positions is set if self.config.get('use_max_market_positions', True): # Must come from strategy config, as the strategy may modify this setting. max_open_trades = self.strategy.config['max_open_trades'] else: logger.info( 'Ignoring max_open_trades (--disable-max-market-positions was used) ...') max_open_trades = 0 # need to reprocess data every time to populate signals preprocessed = self.strategy.ohlcvdata_to_dataframe(data) # Trim startup period from analyzed dataframe for pair in list(preprocessed): df = preprocessed[pair] df = trim_dataframe(df, timerange, startup_candles=self.required_startup) if len(df) > 0: preprocessed[pair] = df else: logger.warning(f'{pair} has no data left after adjusting for startup candles, ' f'skipping.') del preprocessed[pair] if not preprocessed: raise OperationalException( "No data left after adjusting for startup candles.") min_date, max_date = history.get_timerange(preprocessed) logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(max_date - min_date).days} days).') # Execute backtest and store results results = self.backtest( processed=preprocessed, start_date=min_date, end_date=max_date, max_open_trades=max_open_trades, position_stacking=self.config.get('position_stacking', False), enable_protections=self.config.get('enable_protections', False), ) backtest_end_time = datetime.now(timezone.utc) results.update({ 'backtest_start_time': int(backtest_start_time.timestamp()), 'backtest_end_time': int(backtest_end_time.timestamp()), }) self.all_results[self.strategy.get_strategy_name()] = results return min_date, max_date
def _set_strategy(self, strategy: IStrategy): """ Load strategy into backtesting """ self.strategy: IStrategy = strategy strategy.dp = self.dataprovider # Set stoploss_on_exchange to false for backtesting, # since a "perfect" stoploss-sell is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False if self.config.get('enable_protections', False): conf = self.config if hasattr(strategy, 'protections'): conf = deepcopy(conf) conf['protections'] = strategy.protections self.protections = ProtectionManager(conf)
def validate_strategy(strategy: IStrategy) -> IStrategy: if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: # Require new method warn_deprecated_setting(strategy, 'sell_profit_only', 'exit_profit_only', True) warn_deprecated_setting(strategy, 'sell_profit_offset', 'exit_profit_offset', True) warn_deprecated_setting(strategy, 'use_sell_signal', 'use_exit_signal', True) warn_deprecated_setting(strategy, 'ignore_roi_if_buy_signal', 'ignore_roi_if_entry_signal', True) if not check_override(strategy, IStrategy, 'populate_entry_trend'): raise OperationalException( "`populate_entry_trend` must be implemented.") if not check_override(strategy, IStrategy, 'populate_exit_trend'): raise OperationalException( "`populate_exit_trend` must be implemented.") if check_override(strategy, IStrategy, 'check_buy_timeout'): raise OperationalException( "Please migrate your implementation " "of `check_buy_timeout` to `check_entry_timeout`.") if check_override(strategy, IStrategy, 'check_sell_timeout'): raise OperationalException( "Please migrate your implementation " "of `check_sell_timeout` to `check_exit_timeout`.") if check_override(strategy, IStrategy, 'custom_sell'): raise OperationalException( "Please migrate your implementation of `custom_sell` to `custom_exit`." ) else: # TODO: Implementing one of the following methods should show a deprecation warning # buy_trend and sell_trend, custom_sell warn_deprecated_setting(strategy, 'sell_profit_only', 'exit_profit_only') warn_deprecated_setting(strategy, 'sell_profit_offset', 'exit_profit_offset') warn_deprecated_setting(strategy, 'use_sell_signal', 'use_exit_signal') warn_deprecated_setting(strategy, 'ignore_roi_if_buy_signal', 'ignore_roi_if_entry_signal') if (not check_override(strategy, IStrategy, 'populate_buy_trend') and not check_override(strategy, IStrategy, 'populate_entry_trend')): raise OperationalException( "`populate_entry_trend` or `populate_buy_trend` must be implemented." ) if (not check_override(strategy, IStrategy, 'populate_sell_trend') and not check_override(strategy, IStrategy, 'populate_exit_trend')): raise OperationalException( "`populate_exit_trend` or `populate_sell_trend` must be implemented." ) strategy._populate_fun_len = len( getfullargspec(strategy.populate_indicators).args) strategy._buy_fun_len = len( getfullargspec(strategy.populate_buy_trend).args) strategy._sell_fun_len = len( getfullargspec(strategy.populate_sell_trend).args) if any(x == 2 for x in [ strategy._populate_fun_len, strategy._buy_fun_len, strategy._sell_fun_len ]): strategy.INTERFACE_VERSION = 1 return strategy