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);
// Custom executor / pre-trade gates
void setExecutor(IOrderExecutor* executor) noexcept;
IOrderExecutor* customExecutor() const noexcept;
void setRiskManager(IRiskManager* rm) noexcept;
void setOrderValidator(IOrderValidator* ov) noexcept;
void setKillSwitch(IKillSwitch* ks) noexcept;
void setPnLTracker(IPnLTracker* tracker) noexcept;
// Non-interactive mode
BacktestResult run(replay::IMultiSegmentReader& reader);
BacktestResult runBars(const std::vector<BarEvent>& bars);
BacktestResult runTape(const std::filesystem::path& data_dir);
BacktestResult runTapes(const std::vector<std::filesystem::path>& data_dirs);
// 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;
};
Entry Points¶
| Method | Input | Notes |
|---|---|---|
run(reader) |
An IMultiSegmentReader |
The general path |
runBars(bars) |
A std::vector<BarEvent> |
Each bar updates the SimulatedExecutor so resting orders and SL/TP match against bar.high / bar.low / bar.close, then dispatches to Strategy::onBar and any registered subscriber. Bars must be in non-decreasing endTime order |
runTape(dir) |
One .floxlog directory |
Opens the tape via replay::createMultiSegmentReader. Throws if dir is not a .floxlog directory or holds no segments |
runTapes(dirs) |
N .floxlog directories, merged on read |
Symbols are rekeyed into the engine registry via (metadata.exchange, name), so strategies that pre-resolved venue-tagged symbols see the merger's ids. Throws if any input is not .floxlog, or if two inputs declare overlapping book streams for the same symbol (OverlappingBookStreamError). runTapes({t}) equals runTape(t) modulo the rekey |
Pre-Trade Gates¶
All four hooks are optional; an unset hook is a no-op. The runner holds raw pointers and does not delete them.
Gates fire on entry-type signals (Market, Limit, Stop*, TakeProfit*, TrailingStop) and on
the order they produce. Cancel, CancelAll and Modify pass through ungated — they reduce, not
add, exposure. Reduce-only orders also bypass: when caps tighten you do not want to be stuck in a
position.
setExecutor replaces the built-in SimulatedExecutor for signal routing (submit, cancel, replace,
OCO, cancelAll). The simulator is left intact to match live data into BacktestResult; the custom
executor must report fills through its own execution-listener path. Pass nullptr to revert. The
caller retains ownership.
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;
// Optional: realistic fill simulation.
config.defaultSlippage = {SlippageModel::FIXED_BPS, 0, Price{}, 1.0, 0.0};
config.queueModel = QueueModel::TOB;
config.riskFreeRate = 0.0;
config.metricsAnnualizationFactor = 252.0;
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. - The runner calls
executor.applyConfig(config)at construction, so slippage, per-symbol overrides, and queue simulation are ready before any events are processed. - Trade events from the replay stream pass their quantities to the executor via
onTrade(symbol, price, qty, isBuy), which is required for queue-simulated fills. - All fills are recorded in
BacktestResult.
See Also¶
- Interactive Backtest Mode — Pause, step, breakpoints
- SimulatedExecutor — Order execution simulation
- BacktestResult — Performance statistics
- Slippage
- Queue simulation