BacktestConfig

Every field of the backtest configuration dataclass

BacktestConfig is a nested dataclass passed to hz.run(mode="backtest", backtest=...). Every field controls some aspect of the simulation.

Import

python
from horizon import BacktestConfig

Full field list

python
@dataclass
class BacktestConfig:
    # Time window
    start: str = ""                      # "YYYY-MM-DD"
    end: str = ""
    initial_cash_usd: float = 100_000.0

    # Per-asset-class data sources (optional. when None, use a default)
    data_sources: dict[AssetClass, Any] = field(default_factory=dict)

    # Fill and fee models
    fill_model: Any = field(default_factory=RealisticFill)
    fee_model: Any = field(default_factory=TieredFees)

    # Tick mechanics
    tick_resolution: str = "1s"
    warmup_period: str = "60d"

    # Output
    save_to: str = ""
    snapshot_equity_every: str = "1d"
    save_signals: bool = True
    save_features: bool = False

    # Per-venue budget overrides
    venue_budgets: dict[str, float] = field(default_factory=dict)

Time window

startstr
ISO date string (`"2023-01-01"`). Backtest begins from this date.
endstr
ISO date string. Backtest stops here.
initial_cash_usdfloat
Starting equity. The ledger initializes with this cash and P&L accumulates from here. All risk limits and Kelly sizing compound from this base.

Data sources (per asset class)

python
from horizon.asset_classes import Equity, Option, Prediction
from horizon.data import SyntheticGBM

bt = BacktestConfig(
    initial_cash_usd=100_000,
    data_sources={
        Equity: SyntheticGBM(["AAPL", "MSFT"], n_bars=252, seed=42),
        # Option: historical_options_source(...),
        # Prediction: polymarket_historical_source(...),
    },
)

In the current Horizon implementation, you can also pass a single data_source directly to hz.run(): the per-asset-class dict is the future-proof path when mixing multiple asset classes in one backtest.

Fill model

python
@dataclass
class RealisticFill:
    latency_ms: int = 200                 # simulated order → fill delay
    slippage_bps: float = 2.0             # marketable orders pay half-spread + slippage
    reject_prob: float = 0.01             # random order rejection probability
    partial_fill_prob: float = 0.10       # probability of partial fill on limit orders
    spread_as_cost: bool = True            # treat the spread as a trading cost

Fee model

python
@dataclass
class TieredFees:
    equity: Any = field(default_factory=PerShareFee)
    option: Any = field(default_factory=PerContractFee)
    prediction: Any = field(default_factory=lambda: PercentageFee(pct=0.02))
    perp: Any = field(default_factory=lambda: PercentageFee(pct=0.0005))

Individual fee models:

python
@dataclass
class PerShareFee:
    fee_per_share: float = 0.005
    min_per_order: float = 1.0

@dataclass
class PerContractFee:
    fee_per_contract: float = 0.65

@dataclass
class PercentageFee:
    pct: float = 0.02

Defaults reflect approximate retail rates:

  • Equity: $0.005/share, $1 minimum (typical for IBKR tiered)
  • Option: $0.65/contract (typical for most retail brokers)
  • Prediction: 2% (Polymarket)
  • Perp: 5 bps (Hyperliquid taker)

Override per asset class:

python
bt = BacktestConfig(
    fee_model=TieredFees(
        equity=PerShareFee(fee_per_share=0.003),  # lower than default
        prediction=PercentageFee(pct=0.01),        # assume cheaper venue
    ),
)

Tick mechanics

tick_resolutionstr
Simulated clock step. Metadata today; will be enforced in a future version. Common values: `"1s"`, `"1m"`, `"1h"`, `"1d"`.
warmup_periodstr
How much history to preload before the first strategy evaluation. Prevents features from returning NaN for the first `window` bars.

Output controls

save_tostr
Path to save results. Empty = no save.
snapshot_equity_everystr
How often to snapshot the equity curve. `"1d"` keeps one point per day; `"1h"` keeps hourly.
save_signalsbool
Log every signal emitted during the backtest. Essential for post-hoc IC / feature-importance analysis.
save_featuresbool
Log feature values per tick. Larger disk footprint (grows with n_features × n_bars × n_markets). Off by default.

Venue budgets

python
bt = BacktestConfig(
    initial_cash_usd=100_000,
    venue_budgets={
        "alpaca": 60_000,
        "ibkr": 40_000,
    },
)

Per-venue budgets override the default (which allocates all equity to a single venue). Useful for multi-venue backtests where you want to simulate realistic capital allocation.

Minimal example

python
import horizon as hz

result = hz.run(
    mode="backtest",
    backtest=hz.BacktestConfig(initial_cash_usd=100_000),
    strategies=[...],
    data_source=...,
    ...
)

Full example

python
from horizon import BacktestConfig
from horizon.backtest import RealisticFill, TieredFees, PerShareFee

bt = BacktestConfig(
    start="2023-01-01",
    end="2024-12-31",
    initial_cash_usd=500_000,
    fill_model=RealisticFill(
        latency_ms=150,
        slippage_bps=1.5,
        reject_prob=0.005,
    ),
    fee_model=TieredFees(
        equity=PerShareFee(fee_per_share=0.003, min_per_order=0.5),
    ),
    tick_resolution="1m",
    warmup_period="90d",
    save_signals=True,
    save_features=True,
    venue_budgets={"alpaca": 300_000, "ibkr": 200_000},
)

result = hz.run(mode="backtest", backtest=bt, ...)

Next