F3-conf-03 — Rolling-origin ridge forecasts of US industrial production growth
Question
Does a fixed-penalty ridge regression (λ = 50) on eight standardised monthly predictors forecast next month's US industrial production growth (100 × Δln INDPRO, % per month) better than (a) the naive last-value forecast and (b) the expanding historical mean, over 60 rolling origins from 2021-09 to 2026-08 (horizon h = 1)?
Data and exclusions
data/ip_predictors.csv: 417 monthly rows, 1992-01 to 2026-09, current-vintage FRED data (seedata/README.md). No rows were dropped.- Missing values: only
neworder_chgfor 1992-01 and 1992-02. Neither falls in any training window (training rows start at 1992-03), so no imputation was needed. - Minor discrepancy: the README says the first month of every change series is empty, but
ip_growth,oil_chgandpermit_chgare populated for 1992-01. I kept the values as given.ip_growth1992-01 enters only as the second lag in the 1992-03 training row, which is what the task specification implies. - The extreme COVID-19 months of 2020 (ip_growth from −14.14 to +6.35 %) stay in every training window, as the specification requires. They inflate the standardisation statistics and affect the coefficients.
Method
- Origins t = 2021-09-01 … 2026-08-01 (60). Each origin's target is
ip_growthat t+1. - Naive forecast:
ip_growthat t. - Expanding-mean forecast: the mean of
ip_growthfrom 1992-04 to t. - Ridge: the training rows are s = 1992-03 … t−1, with target y_{s+1}. The features are ip_growth_s, ip_growth_{s−1} and ip_growth_{s−2}, plus oil_chg_s, fedfunds_s, permit_chg_s, neworder_chg_s and tcu_s.
- At every origin I recompute the feature means and sample SDs (ddof = 1) and the target mean from that origin's training rows only.
normalisation_window = "expanding". - b = (Z′Z + 50 I)⁻¹ Z′(y − ȳ), with no penalised intercept. Forecast = ȳ + ((z_t − μ)/σ)·b.
- The code asserts that no training target is dated after t.
- At every origin I recompute the feature means and sample SDs (ddof = 1) and the target mean from that origin's training rows only.
- I scored errors against current-vintage actuals and computed RMSE and MAE over the 60 origins.
out/analysis.pyregeneratesresults.json,forecasts.csv(180 rows) anduncertainty.json.
Results
| model | RMSE (%/month) | MAE (%/month) |
|---|---|---|
| naive | 0.8831 | 0.6990 |
| expanding mean | 0.5439 | 0.4293 |
| ridge (λ=50) | 0.5608 | 0.4514 |
- Relative RMSE improvement of ridge over naive: 1 − 0.5608/0.8831 = +0.365 (36.5 % lower RMSE).
- Relative RMSE improvement of ridge over the expanding mean: −0.031. Ridge's RMSE is 3.1 % higher than the mean's, and its MAE is also higher.
So the ridge model clearly beats the naive forecast but does not beat the expanding historical mean over this evaluation window. At the one-month horizon, the eight predictors add no out-of-sample value over the unconditional mean.
Uncertainty
These diagnostics are in out/uncertainty.json, produced by the same script.
- Diebold–Mariano tests (squared loss, with the Harvey–Leybourne–Newbold small-sample correction and a t(59) reference):
- ridge vs naive: statistic −3.36, p = 0.0014. Ridge is significantly better.
- ridge vs mean: statistic +0.54, p = 0.59. There is no significant difference.
- Moving-block bootstrap (block 6, B = 5000, seed 12345), 95 % intervals for the relative RMSE improvement:
- vs naive: [0.248, 0.426].
- vs mean: [−0.158, 0.056].
- With only 60 evaluation months, a small loss to the mean cannot be told apart from a small gain.
Limitations and open issues
- Data vintage. I used current-vintage, revised data and ignored publication lags (INDPRO, TCU and NEWORDER are revised substantially). These results are not real-time forecast performance.
- Model choices. λ = 50 is fixed by the task, not tuned. The level variables (fedfunds, tcu) enter untransformed. The COVID outliers are untreated. Each of these could change the ridge results.
- Evaluation window. It is one short window (2021-09 to 2026-08) and includes the post-pandemic recovery. The relative ranking may not generalise to other periods.
- Uncertainty method. The DM test and the bootstrap both assume approximate stationarity of loss differentials. The bootstrap seed does not affect any value in
results.json.