[docs]classBacktestingEngine:""" Orchestrates the event-driven backtesting process. Initializes all main components: EventQueue, DataHandler, Strategies, PortfolioManager, SimulatedExecutionHandler and runs the main event loop. """def__init__(self,options:BacktestingOptions,data_source:DataSource,strategies:List[BaseStrategy],position_sizing_method:Optional[BasePositionSizing]=None,is_stepping_mode:Optional[bool]=False):self._initialize_config(options)self.event_queue=EventQueue()self.data_handler=DataHandler(event_queue=self.event_queue,symbols=self.config.symbols,start_date=self.config.start_date,end_date=self.config.end_date,interval=self.config.interval,data_source=data_source)self.strategies:List[BaseStrategy]=[]forstrategy_instanceinstrategies:strategy_instance.set_event_queue(self.event_queue)self.strategies.append(strategy_instance)decimal_transaction_cost=Decimal(str(self.config.transaction_cost_percent))decimal_slippage_percent=Decimal(str(self.config.slippage_percent))self.portfolio_manager=PortfolioManager(event_queue=self.event_queue,symbols=self.config.symbols,initial_cash=self.config.initial_cash,transaction_cost_percent=decimal_transaction_cost,slippage_percent=decimal_slippage_percent,position_sizing_method=position_sizing_method)self.execution_handler=SimulatedExecutionHandler(event_queue=self.event_queue,transaction_cost_percent=decimal_transaction_cost,slippage_percent=decimal_slippage_percent)self.is_stepping_mode=is_stepping_modelogging.info("Backtesting Engine initialized.")def_initialize_config(self,options:BacktestingOptions):""" Initializes the backtest config by the following rules: - if an alpheast_config.json is not present in project root, use passed options - otherwise, load the json into a BacktestingOptions and override with any passed option values In both cases, perform validation at the end. """is_json_loaded=Falseproject_root=find_project_root()json_file_path=os.path.join(project_root,"alpheast_config.json")ifproject_rootisnotNoneelseNoneifjson_file_pathisnotNoneandos.path.exists(json_file_path):try:backtest_options=ConfigLoader.load_backtest_config_from_json(json_file_path)is_json_loaded=TrueexceptExceptionase:logging.warning(f"Failed to load alpheast_config.json: {e}")ifis_json_loaded:backtest_options.override(options)self.config=backtest_options.validate()else:self.config=options.validate()self.config.log()# Full Run
[docs]defrun(self)->Optional[BacktestResults]:""" Runs the main event loop of the backtesting engine """ifself.is_stepping_mode:raiseRuntimeError("Engine is in stepping mode, you need to call step_forward() instead.")logging.info(f"Starting Backtest for {self.config.symbols} from {self.config.start_date} to {self.config.end_date}")whileself.data_handler.continue_backtest()ornotself.event_queue.empty():# --- 1. Push next MarketEvents for the current interval ---ifself.data_handler.continue_backtest():self.data_handler.stream_next_market_event()# --- 2. Process all events currently in the queue ---whilenotself.event_queue.empty():self._process_next_event()# -- Post-Backtest Analysis ---returnself._finalize_backtest_results()
# Stepping
[docs]defstep_forward(self)->bool:""" Processes one time step (one market event and all subsequent events) Returns True if a market event was processed. """ifnotself.is_stepping_mode:raiseRuntimeError("Engine is not in stepping mode, you need to call run() instead.")market_event_available=Falseifself.data_handler.continue_backtest():self.data_handler.stream_next_market_event()market_event_available=Truewhilenotself.event_queue.empty():self._process_next_event()returnmarket_event_available
[docs]defreset(self):""" Resets the engine's internal state for a new sequence of step-by-step execution. This should be called when starting a new backtest simulation in stepping mode. Assumes the engine was initially created with `enable_stepping_mode=True`. """ifnotself.is_stepping_mode:raiseRuntimeError("Engine was not initialized in stepping mode. Cannot call `reset_for_stepping_mode()`.")whilenotself.event_queue.empty():try:self.event_queue.get_nowait()exceptqueue.Empty:breakself.data_handler.reset()self.portfolio_manager.reset()self.execution_handler.reset()self.strategies_initialized=Falseself.current_simulation_date=Nonelogging.info("Backtesting Engine reset complete.")
def_process_next_event(self):event=self.event_queue.get()ifeventisNone:returnlogging.debug(f"Processing event: {event}")ifevent.type==EventType.MARKET:forstrategyinself.strategies:strategy.on_market_event(event)self.portfolio_manager.on_market_event(event)self.execution_handler.on_market_event(event)elifevent.type==EventType.SIGNAL:self.portfolio_manager.on_signal_event(event)elifevent.type==EventType.ORDER:self.execution_handler.on_order_event(event)elifevent.type==EventType.FILL:self.portfolio_manager.on_fill_event(event)elifevent.type==EventType.DAILY_UPDATE:self.portfolio_manager.on_daily_update_event(event)else:logging.warning(f"Unknown event type received: {event.type}")def_finalize_backtest_results(self)->Optional[BacktestResults]:""" Helper method to collect and return backtest results. """daily_values=self.portfolio_manager.get_daily_values()benchmark_daily_values=self.portfolio_manager.get_benchmark_daily_values()trade_log=self.portfolio_manager.get_trade_log()final_portfolio_summary=self.portfolio_manager.get_summary()ifnotdaily_values:logging.error("No daily values recorded, skipping Summary.")returnNoneperformance_metrics=calculate_performance_metrics(daily_values=daily_values,trade_log=trade_log,benchmark_daily_values=benchmark_daily_values)results=BacktestResults(performance_metrics=performance_metrics,daily_values=daily_values,benchmark_daily_values=benchmark_daily_values,trade_log=trade_log,final_portfolio_summary=final_portfolio_summary,start_date=self.config.start_date,end_date=self.config.end_date,initial_cash=self.config.initial_cash)logging.info("--- Backtest Finished ---")returnresults