BacktestRunner (v1)

Fund-aware backtester with synthetic tick generation and walk-forward support

The v1 BacktestRunner is the original backtest engine inside python/horizon/fund/. Horizon’s new hz.run(mode="backtest") loop replaces it for most use cases, but the v1 runner is still useful for:

  • Fund-level backtests that exercise AlphaModel, SignalEnsemble, HypothesisManager and other v1 modules
  • Synthetic tick generation with embedded regime dynamics
  • Walk-forward training with parameter retuning per window
  • Tearsheet output with the full institutional metric set

Import

python
from horizon.fund._backtest_runner import (
    BacktestRunner,
    SyntheticTick,
    BacktestConfig as V1BacktestConfig,
)

Differences from Horizon’s hz.run()

AspectHorizon hz.run(mode="backtest")v1 BacktestRunner
Strategy interfaceStrategy subclass with evaluate(f, universe)Fund-module callbacks
SignalsFirst-class Signal typeInternal representation
Portfolio sizingPluggable Sizer (Kelly, Carver, EqualWeight)Built-in PortfolioOptimizer only
Risk enforcement7-layer RiskEngineFund-level risk config
Ledger authorityPositionLedger is source of truthInternal bookkeeping
MetricsMetricsCollector with equity curve + trade logInstitutional tearsheet (quantstats-like)
Data sourcesDataSource protocol (SyntheticGBM, DictSource, custom)SyntheticTick generator + CSV
Use caseEvery new strategyFund-module research

API

python
class BacktestRunner:
    def __init__(
        self,
        config: BacktestConfig,
        portfolio_optimizer: PortfolioOptimizer | None = None,
        alpha_model: AlphaModel | None = None,
        signal_ensemble: SignalEnsemble | None = None,
    ) -> None:
        ...

    def add_strategy(
        self,
        name: str,
        callback: Callable,
    ) -> None:
        """Register a strategy (signature differs from Horizon Strategy)."""

    def run(
        self,
        start_date: str,
        end_date: str,
        markets: list[str],
    ) -> BacktestResult:
        ...

    def walk_forward(
        self,
        start_date: str,
        end_date: str,
        markets: list[str],
        train_window: str,
        test_window: str,
        step: str,
    ) -> WalkForwardResult:
        ...

    def tearsheet(self, result: BacktestResult, output: str) -> None:
        """Generate an HTML/PDF tearsheet from backtest results."""

Walk-forward

The v1 runner has native walk-forward with per-window retuning:

python
from horizon.fund._backtest_runner import BacktestRunner

runner = BacktestRunner(config=my_config)
runner.add_strategy("momentum", my_momentum_callback)

wf_result = runner.walk_forward(
    start_date="2020-01-01",
    end_date="2024-12-31",
    markets=["AAPL", "MSFT", "NVDA"],
    train_window="2y",
    test_window="3m",
    step="3m",
)

print(f"Aggregate Sharpe: {wf_result.aggregate_sharpe:.2f}")
for window in wf_result.windows:
    print(f"  {window.test_start} to {window.test_end}: Sharpe={window.sharpe:.2f}")

Synthetic tick generation

python
from horizon.fund._backtest_runner import SyntheticTick

# Single synthetic tick
tick = SyntheticTick(
    market_id="BTC-USD",
    timestamp=1704067200.0,
    price=42000.0,
    volume=1.5,
    bid=41995.0,
    ask=42005.0,
)

For full synthetic time series, v1 used a CSV-driven generator. Horizon’s SyntheticGBM is the modern replacement: seeded, deterministic, and explicit about regimes.

Tearsheet output

v1 can emit a full institutional-style tearsheet:

python
result = runner.run(start_date="2023-01-01", end_date="2024-12-31", markets=["AAPL"])
runner.tearsheet(result, output="report.html")

The tearsheet includes:

  • Equity curve (linear + log)
  • Drawdown chart
  • Monthly returns heatmap
  • Rolling Sharpe / Sortino / Calmar
  • Trade distribution histogram
  • Per-strategy attribution (when multiple strategies are registered)
  • Factor attribution (when PerformanceAttribution is wired in)
  • Benchmark comparison

When to use v1 BacktestRunner

Research using v1 fund modules If you're specifically testing `AlphaModel`, `SignalEnsemble`, or `HypothesisManager`, the v1 runner wires them in natively.
Walk-forward with retuning Until Horizon's a future release walk-forward wiring lands, the v1 runner is the way to run proper walk-forward with parameter retuning.
Tearsheets For institutional-style reports, the v1 tearsheet generator is more polished than Horizon's current metric output.
Legacy test suites Existing tests and notebooks that depend on the v1 API keep working unchanged.

When to use Horizon’s hz.run()

New strategies Any new strategy development should use the Horizon `Strategy` protocol and `hz.run()`. It's cleaner, faster, and better tested.
Multi-asset backtests Horizon's asset-class-neutral design makes mixing equities, options, and prediction markets straightforward.
Risk-enforcement tests The 7-layer RiskEngine is only available via `hz.run()`.
Deterministic CI tests Horizon's seeded `SyntheticGBM` produces bit-identical results across runs, which is essential for a test suite.

Migrating from v1 to Horizon

Most v1 backtests translate to Horizon by:

Rewrite the strategy

v1 strategies were callbacks with loose interfaces. Subclass Strategy and implement evaluate(f, universe).

Replace synthetic ticks

Use SyntheticGBM or DictSource instead of SyntheticTick sequences.

Use hz.BacktestConfig

Drop V1BacktestConfig; use hz.BacktestConfig(start=, end=, initial_cash_usd=).

Inspect result as BacktestResult

result.sharpe, result.equity_curve, result.trades are available directly. Use horizon[research] for rich tearsheets (wrapper around quantstats).

Source

python/horizon/fund/_backtest_runner.py. ~600 lines.

Next