def build_config() -> RunConfig[YfinanceConfig]:
"""
Builds the backtest configuration for a Bollinger Band mean-reversion
parameter sweep on AAPL daily bars, 2000-2026.
"""
ticker = "AAPL"
interval = Interval.DAY_1
start = datetime(2000, 1, 1)
end = datetime(2026, 1, 1)
initial_cash = 1_000
initial_position = 0
fill_behavior = FillBehavior.INSTANT # fills orders immediately at the bar's price, no partial fills
slippage = 0.001 # 0.1% price slippage applied per fill, to simulate imperfect execution
commission_per_unit = 0.0 # fee per share bought
start_unix_ms = int(start.timestamp() * 1000)
end_unix_ms = int(end.timestamp() * 1000)
# ResearchRunner will grid-search every combination of these values,
# instantiating BollingerBandMeanReversion(**params) for each one and
# running a full backtest per combination. Total runs = product of list
# lengths below (11 * 5 * 4 * 1 = 220 backtests in this case).
param_space: dict[str, list[int | float | str]] = {
# Lookback window (in bars) used to compute the Bollinger Band's
# moving average and standard deviation. Swept from 5 to 25 in
# steps of 2 -> [5, 7, 9, ..., 25].
"bollinger_bands_period": list(range(5, 26, 2)),
# Band width, in standard deviations, above/below the moving
# average. Smaller values -> tighter bands -> more frequent,
# lower-conviction signals. Larger values -> wider bands -> rarer signals.
"bollinger_bands_stdev": [0.5, 1.0, 1.5, 2.0, 2.5],
# Fraction of available cash committed to each buy signal.
# 1.0 means trade 100% of cash on every entry; lower values leave cash
# in portfolio (less risky, as shown when graphing).
"allocation": [0.25, 0.5, 0.75, 1.0],
# Held fixed at a single value here since this run only trades
# one ticker, but keeping it in param_space keeps the strategy
# constructor signature uniform for the grid search.
"symbol": [ticker],
}
return RunConfig[YfinanceConfig](
broker=Yfinance(NYSECalendar()),
broker_config=YfinanceConfig(ticker, interval, start_unix_ms, end_unix_ms),
strategy_factory=BollingerBandMeanReversion,
param_space=param_space,
initial_cash=initial_cash,
initial_position=initial_position,
fill_behavior=fill_behavior,
slippage=slippage,
commission_per_unit=commission_per_unit,
)