Model selection and forecasting — the humbling benchmark
▸ Pretest — guess, even if you don't know
You fit AR(1), AR(2), ... up to AR(10) to the same daily return series. Which model will have the lowest in-sample mean squared error?
The overfitting ratchet
AR(2) contains AR(1) as a special case (set the second coefficient to zero). So the best-fitting AR(2) is at least as good in-sample as the best AR(1) — and with noisy data, strictly better, because the extra coefficient bends toward whatever noise pattern this particular sample happens to contain. Same for AR(3) over AR(2), and for every ARMA extension from D1-05.
This is the time-series face of the disease you met in A2-05 and D4-01: maximizing in-sample fit selects for luck. Fit a rich enough model and you can reproduce history exactly — and forecast nothing. So we need scorekeeping that either charges for parameters or hides data from the fit.
AIC and BIC — paying for parameters
Information criteria charge for parameters directly. For a model with parameters and maximized likelihood fit on points:
Plain reading: the term rewards fit (it falls as fit improves), the first term is the fine per parameter, and lower is better for both. The difference is the fine schedule: AIC charges a flat 2 per parameter; BIC charges , which is already ~5.5 at 250 daily observations and grows with the sample. So BIC penalizes harder and picks smaller models. For daily financial data, where true signals are faint and extra parameters mostly harvest noise, the stingier BIC is usually the safer default. Statsmodels prints both for any ARMA fit; you just have to resist the urge to ignore them.
Information criteria are cheap and use all the data — but they still score in-sample likelihood, just with a handicap. The sterner test hides data.
Splitting time: train/test without shuffling
The machine-learning reflex is to shuffle the data and split randomly. For time series this is forbidden. Shuffling puts 2023 observations in the training set for "predicting" 2019 — look-ahead bias (D4-01) manufactured by your own evaluation harness. Autocorrelation makes it worse: a test point randomly sandwiched between two training points is partly memorized, not forecast.
The rule: temporal order is sacred. Train on the first chunk, test on the later chunk, and let information flow only forward:
split = int(len(x) * 0.7)
train, test = x[:split], x[split:] # fit on train ONLY, forecast test
One split gives one verdict; rolling the split forward gives walk-forward analysis, the full treatment in Track D4.
The naive benchmark
Score forecasts on the test set with mean squared error or mean absolute error:
MSE punishes big misses quadratically; MAE is more forgiving of outliers. But a raw error number means nothing alone — you need a baseline, and time series has a brutally effective one: the naive forecast, tomorrow equals today (). For a random walk (D1-02), the naive forecast is optimal. So "beat naive out-of-sample" is the minimum bar for claiming your model found structure — and for prices, most models fail it. For returns, the analogous humbling benchmark is the constant forecast (tomorrow's return equals the historical mean, or just zero): decades of academic horse races show sophisticated return forecasts struggling to beat even that.
The honest scoreboard
Where do linear time-series models actually land, out-of-sample, on financial data?
- Return direction and magnitude: rarely beat naive. Daily return autocorrelations are ~0.01–0.05 (D1-01) — the predictable fraction of variance is tiny, and transaction costs eat most of what survives. Any backtest saying otherwise should trigger the D4-01 checklist, not celebration.
- Volatility: routinely beat naive. Squared returns are strongly autocorrelated — vol clusters. Models that forecast risk rather than return have genuine, repeatable out-of-sample skill.
That asymmetry is the doorway to the rest of this track: the next two lessons are about the forecasts that actually work.
Try it
Race the two classic baselines. The naive forecast predicts each point with the previous point; the mean forecast predicts every point with the training mean (here: the mean of all points before the last step, a miniature train/test split):
Implement naive_mse(x): the MSE of predicting x[t] with x[t-1], i.e. mean of (x[1:] - x[:-1]) squared. And mean_mse(x): the MSE of predicting every x[t] for t >= 1 with the mean of x[:-1]. Both return floats.
⧉ Review cardWhy can in-sample fit never choose between nested models?
⧉ Review cardAIC vs BIC — formulas in words, and which penalizes harder?
⧉ Review cardWhy must you never shuffle a time-series train/test split?
⧉ Review cardWhat is the naive forecast and why is it the benchmark?
⧉ Review cardWhere do linear time-series models honestly succeed and fail in markets?
Predict before the next lesson
Next up is D1-07: volatility, ARCH and GARCH — the models built for the one thing that is forecastable. Before reading, predict:
- If today's return is a huge shock (say −5%), what should your forecast of tomorrow's variance do?
- Design instinct: how would you write a recursion where today's variance depends on yesterday's squared shock and yesterday's variance?
◈ Calibration check
Could you explain to a colleague why AR(10) always beats AR(1) in-sample, how BIC arbitrates, and why the naive forecast is the bar to clear?
1 = guessing · 5 = could teach it
⏻ End of lesson
Mark it read to book its 5 review cards into your deck.