Running a Backtest
hz.run(mode='backtest', ...) from end to end
The minimal call
python
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.portfolio import KellyOptimizer
from horizon.quant import TSMomentum
from horizon.risk import RiskProfile
result = hz.run(
mode="backtest",
strategies=[TSMomentum(lookback=20)],
asset_classes=[Equity],
universe=StaticUniverse([
Market(id=t, asset_class=AssetClass.Equity)
for t in ["AAPL", "MSFT", "NVDA"]
]),
portfolio=KellyOptimizer(kelly_fraction=0.25),
risk=RiskProfile.moderate(),
data_source=SyntheticGBM(["AAPL", "MSFT", "NVDA"], n_bars=252, seed=42),
backtest=hz.BacktestConfig(initial_cash_usd=100_000),
)
Required arguments
modestrMust be `"backtest"` for backtest mode.
strategieslistList of `Strategy` subclasses or instances. Multiple strategies run simultaneously.
asset_classeslist[AssetClass]Which asset classes the backtest covers. Usually auto-inferred from strategies.
universeDiscoveryProviderThe market universe. Use `StaticUniverse([Market(...), ...])` for a fixed list.
data_sourceDataSourceHistorical data source. See [Data Sources](/docs/backtest/data-sources).
Optional arguments
venuesdict[str, Venue]Dict of venue name → Venue instance. Defaults to a `Paper` venue with `initial_cash_usd` from the backtest config.
portfolioSizerPortfolio sizer. Defaults to `EqualWeight()`.
riskRiskConfigRisk configuration. Defaults to `RiskConfig()` (empty: no stops, no drawdown guards).
lifecycleLifecycleConfigLifecycle config (rebalance schedule, option roll DTE, etc.).
backtestBacktestConfigBacktest-specific configuration. See [BacktestConfig](/docs/backtest/config).
executorsdict[AssetClass, Executor]Override the default executor per asset class. Default is `EquityExecutor` for equities.
metricsMetricsConfig | boolMetrics collection config. `True` enables defaults.
dashboardboolStart a TUI dashboard (not yet wired in backtest mode).
paramsdictFree-form params passed into `Context.params` for strategies to read.
The return value
python
result: BacktestResult
Fields:
python
@dataclass
class BacktestResult:
sharpe: float
sortino: float
total_return: float
max_drawdown: float
n_trades: int
equity_curve: list[tuple[datetime, float]]
trades: list[TradeRecord]
signals_log: Any
metrics_path: str
Inspecting results
python
# Summary
print(f"Sharpe: {result.sharpe:+.3f}")
print(f"Sortino: {result.sortino:+.3f}")
print(f"Total return: {result.total_return:+.2%}")
print(f"Max drawdown: {result.max_drawdown:.2%}")
print(f"Trades: {result.n_trades}")
# Equity curve
for ts, equity in result.equity_curve[:5]:
print(ts, equity)
print("...")
for ts, equity in result.equity_curve[-5:]:
print(ts, equity)
# Per-trade detail
for trade in result.trades[:10]:
print(
trade.market_id,
trade.side,
trade.quantity,
trade.price,
f"fee={trade.fee:.2f}",
f"P&L={trade.realized_pnl:+.2f}",
trade.strategy_id,
)
Saving results
python
import json
# Trade log as CSV
import csv
with open("trades.csv", "w") as fh:
writer = csv.DictWriter(fh, fieldnames=[
"timestamp", "market_id", "side", "quantity", "price",
"fee", "realized_pnl", "strategy_id",
])
writer.writeheader()
for t in result.trades:
writer.writerow({
"timestamp": t.timestamp.isoformat(),
"market_id": t.market_id,
"side": t.side,
"quantity": t.quantity,
"price": t.price,
"fee": t.fee,
"realized_pnl": t.realized_pnl,
"strategy_id": t.strategy_id,
})
# Equity curve as CSV
with open("equity.csv", "w") as fh:
writer = csv.writer(fh)
writer.writerow(["timestamp", "equity"])
for ts, eq in result.equity_curve:
writer.writerow([ts.isoformat(), eq])
# Summary as JSON
summary = {
"sharpe": result.sharpe,
"sortino": result.sortino,
"total_return": result.total_return,
"max_drawdown": result.max_drawdown,
"n_trades": result.n_trades,
}
with open("summary.json", "w") as fh:
json.dump(summary, fh, indent=2)
Plotting
With horizon[research]:
python
import plotly.graph_objects as go
ts = [t for t, _ in result.equity_curve]
eq = [e for _, e in result.equity_curve]
fig = go.Figure()
fig.add_trace(go.Scatter(x=ts, y=eq, mode="lines", name="Equity"))
fig.update_layout(
title="Backtest equity curve",
xaxis_title="Date",
yaxis_title="USD",
)
fig.show()