Skip to content

BacktestRunner

BacktestRunner replays historical market data through a strategy, simulates order execution, and collects performance statistics. Supports both batch and interactive modes.

class BacktestRunner : public ISignalHandler
{
public:
  using EventCallback = std::function<void(const replay::ReplayEvent&, const BacktestState&)>;
  using PauseCallback = std::function<void(const BacktestState&)>;

  explicit BacktestRunner(const BacktestConfig& config = {});

  // Strategy setup
  void setStrategy(IStrategy* strategy);
  void addMarketDataSubscriber(IMarketDataSubscriber* subscriber);
  void addExecutionListener(IOrderExecutionListener* listener);

  // Non-interactive mode
  BacktestResult run(replay::IMultiSegmentReader& reader);

  // Interactive mode
  void start(replay::IMultiSegmentReader& reader);
  void resume();
  void step();
  void stepUntil(BacktestMode mode);
  void pause();
  void stop();

  // Breakpoints
  void addBreakpoint(Breakpoint bp);
  void clearBreakpoints();
  void setBreakOnSignal(bool enable);

  // State inspection
  BacktestState state() const;
  bool isPaused() const;
  bool isFinished() const;

  // Callbacks (interactive mode)
  void setEventCallback(EventCallback cb);
  void setPauseCallback(PauseCallback cb);

  // Results
  BacktestResult result() const;
  BacktestResult extractResult();  // Move results out (clears internal state)

  // ISignalHandler
  void onSignal(const Signal& signal) override;

  // Access internals
  SimulatedExecutor& executor() noexcept;
  IClock& clock() noexcept;
  const BacktestConfig& config() const noexcept;
};

Two Modes

Non-Interactive Mode

Synchronous execution from start to end:

BacktestRunner runner(config);
runner.setStrategy(&strategy);

// Blocks until complete
BacktestResult result = runner.run(*reader);

Interactive Mode

Async execution with pause/step control. See Interactive Backtest Mode for full documentation.

BacktestRunner runner(config);
runner.setStrategy(&strategy);

// Start in background (begins paused)
std::thread t([&]() { runner.start(*reader); });

// Control execution
runner.step();    // One event
runner.resume();  // Run until breakpoint/end
runner.pause();   // Pause execution

t.join();

Strategy Setup

void setStrategy(IStrategy* strategy);
void addMarketDataSubscriber(IMarketDataSubscriber* subscriber);

setStrategy connects the strategy to receive market events. Use addMarketDataSubscriber to add additional subscribers (e.g., bar aggregators, analytics).

Data Flow

flowchart TB
    RE[ReplayEvent] --> BR[BacktestRunner]

    BR --> SE1[SimulatedExecutor.onTrade/onBookUpdate]
    BR --> ST[Strategy.onTrade/onBookUpdate]

    ST --> Emit[emitMarketBuy / emitMarketSell]
    Emit --> Signal[BacktestRunner.onSignal]
    Signal --> Submit[SimulatedExecutor.submitOrder]
    Submit --> Fill[Fill]
    Fill --> Result[BacktestResult]

Usage

// 1. Config
BacktestConfig config;
config.initialCapital = 10000.0;
config.feeRate = 0.0004;

BacktestRunner runner(config);

// 2. Strategy
MyStrategy strategy(/*params*/);
runner.setStrategy(&strategy);

// 3. Execution listeners (optional)
runner.addExecutionListener(&positionTracker);

// 4. Data
replay::ReaderFilter filter;
filter.symbols = {1};
auto reader = replay::createMultiSegmentReader("./data", filter);

// 5. Run
BacktestResult result = runner.run(*reader);
auto stats = result.computeStats();

std::cout << "Return: " << stats.returnPct << "%\n";
std::cout << "Sharpe: " << stats.sharpeRatio << "\n";

Notes

  • Virtual clock advances based on event timestamps from reader
  • Strategy receives events in the same order as in real-time
  • Signals are converted to orders and submitted to SimulatedExecutor
  • All fills are recorded in BacktestResult

See Also