Simplest Backtest

The absolute minimum: a 20-line working example

The smallest possible working backtest. Shows the core pattern without any bells or whistles.

The file

python
# simplest.py
import horizon as hz
from horizon.asset_classes import AssetClass, Equity
from horizon.data import SyntheticGBM
from horizon.discovery import StaticUniverse
from horizon.discovery.base import Market
from horizon.quant import TSMomentum

result = hz.run(
    mode="backtest",
    strategies=[TSMomentum(lookback=20)],
    asset_classes=[Equity],
    universe=StaticUniverse([Market(id="AAPL", asset_class=AssetClass.Equity)]),
    data_source=SyntheticGBM(["AAPL"], n_bars=100, seed=1),
    backtest=hz.BacktestConfig(initial_cash_usd=100_000),
)

print(f"Sharpe: {result.sharpe:+.3f}")
print(f"Trades: {result.n_trades}")
print(f"Max DD: {result.max_drawdown:.2%}")
print(f"Equity curve length: {len(result.equity_curve)}")

Run it

bash
PYTHONPATH=. python3 simplest.py

What happens

Universe

One market: AAPL (tagged as Equity).

Data

SyntheticGBM generates 100 seeded bars for AAPL.

Strategy

TSMomentum(lookback=20) looks at 20-bar returns and emits signals when they exceed a threshold.

Portfolio

Default EqualWeight sizer (no portfolio= argument).

Risk

Default RiskConfig(): empty, no constraints.

Venue

Default paper venue with $100k starting capital.

Run

The full tick loop fires for 100 bars, producing a BacktestResult.

Minimum required arguments

python
hz.run(
    mode="backtest",          # required
    strategies=[...],         # required
    asset_classes=[...],      # required (or auto-inferred from strategies)
    universe=...,             # required
    data_source=...,          # required for backtest mode
    backtest=...,             # optional but you'll want it
)

Everything else is optional and gets sensible defaults.

Next