A research-style, walk-forward backtesting notebook for simple equity signals using daily US equity/ETF data. The project focuses on leakage-safe feature construction, realistic next-day execution, transaction costs, and out-of-sample evaluation.
- Downloads daily OHLCV data from Stooq for a small universe of liquid US tickers
- Builds rolling features (returns, rolling mean/std, z-scores)
- Generates trading signals (mean reversion) and simulates next-day execution
- Runs a rolling walk-forward evaluation (train window → test window)
- Applies per-side transaction costs based on turnover
- Reports out-of-sample performance metrics and plots equity/drawdown
- Source: Stooq daily CSV
- Universe (20 tickers): SPY, AAPL, MSFT, AMZN, NVDA, GOOGL, META, JPM, XOM, JNJ, PG, KO, PEP, WMT, COST, HD, V, MA, UNH, LLY
- Date range: 1970-01-02 to 2026-01-15
- Daily returns:
r_t = P_t / P_{t-1} - 1 - Rolling z-score over a window
w:z_t = (r_t - mean_w(r)) / std_w(r)
- Enter long when
z_t < entry_z - Position is applied next day to avoid lookahead:
pos_{t+1} = signal_t
A stateful rule to reduce tail risk / overholding:
- Enter long when
z_t < entry_z - Exit when
z_t > exit_z(fixedexit_z = -1in current comparison) - Next-day execution: positions are shifted by 1 day
- Execution: next trading day (signals computed on day t, positions applied on day t+1)
- Portfolio: equal-weight across tickers each day
- Costs: 5 bps per side (0.0005) applied when a sleeve changes position (turnover-based)
Rolling walk-forward backtest:
- Train window: 504 trading days (~2 years)
- Test window: 63 trading days (~3 months)
- Step size: 63 trading days
- Parameter selection on each train window using Sharpe (net of costs)
- Test features computed using train + test history (realistic availability), but performance is measured on test dates only
Comparison from the current run:
| Variant | Sharpe (OOS) | Max Drawdown (OOS) | Avg Daily Turnover (OOS) |
|---|---|---|---|
| Entry-only | 0.223 | -0.255 | 0.044 |
| Entry + Exit (exit_z = -1) | 0.353 | -0.122 | 0.039 |
Interpretation:
- Adding an exit rule improved risk-adjusted performance and roughly halved max drawdown while slightly reducing turnover.
- Open notebooks/01_stooq_walkforward.ipynb and run top-to-bottom:
- download prices
- compute features/signals
- run walk-forward backtests
- produce plots and metrics
Dependencies are listed in requirements.txt.
- Add benchmark comparisons (SPY buy & hold, equal-weight buy & hold)
- Add parameter sweeps for exit thresholds and windows
- Add volatility targeting / position sizing
- Move notebook logic into
src/and create a singlescripts/run_backtest.pyrunner - Add unit tests for turnover/cost logic and leakage checks