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,HypothesisManagerand 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()
| Aspect | Horizon hz.run(mode="backtest") | v1 BacktestRunner |
|---|---|---|
| Strategy interface | Strategy subclass with evaluate(f, universe) | Fund-module callbacks |
| Signals | First-class Signal type | Internal representation |
| Portfolio sizing | Pluggable Sizer (Kelly, Carver, EqualWeight) | Built-in PortfolioOptimizer only |
| Risk enforcement | 7-layer RiskEngine | Fund-level risk config |
| Ledger authority | PositionLedger is source of truth | Internal bookkeeping |
| Metrics | MetricsCollector with equity curve + trade log | Institutional tearsheet (quantstats-like) |
| Data sources | DataSource protocol (SyntheticGBM, DictSource, custom) | SyntheticTick generator + CSV |
| Use case | Every new strategy | Fund-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
PerformanceAttributionis 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.