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.