Real-time forecasting of US retail-sales YoY growth from ALFRED vintages (T1-forecast)
Question
Using only the data published at each of 10 month-end origins (2025-10-31 … 2026-07-31; first_origin, last_origin, n_origins), how accurately can the year-over-year growth g_m = 100·ln(L_m/L_{m−12}) of RSAFS (US retail and food services sales, SA) be forecast h = 1 and h = 3 months beyond the last published month last(V)? And does a model of my own design beat the naive no-change forecast when errors are measured against the first-release value of each target month?
Data and exclusions
- Inputs (row counts in
n_input_rows): 10 RSAFS and 10 PAYEMS ALFRED vintages, UMCSENT (current vintage), RSAFS current vintage, andfirst_release.csv(28 target months). All SHA-256 hashes match the README (seeout/state/data_audit.md). - Real-time design. The forecast made at origin V uses only
RSAFS_vintage_V.csv(vintage_used= origin date for all 80 rows ofout/forecasts.csv). The last published month last(V) was 2025-08, 2025-09, 2025-10, 2025-11, 2025-12, 2026-01, 2026-03, 2026-04, 2026-05, 2026-06 for the 10 origins in order (supplementary.real_time_design). Publication delays: the first six origins (2025-10-31 … 2026-03-31) end two calendar months before the origin month; the 2026-04-30 vintage adds two months at once (2026-02 and 2026-03), after which the lag is one month. Consequently the h = 1 targets are 2025-09 … 2026-02 and 2026-04 … 2026-07 (2026-03 is never an h = 1 target) and the h = 3 targets are 2025-11 … 2026-02, 2026-03, 2026-04, 2026-06 … 2026-09. - Actuals are
yoy_first_releasefromfirst_release.csv; all 20 targets are present, so the exclusion ledger (out/exclusions.csv) removes nothing: 20 pairs analysed for the model (n_analysed), 80 forecast rows overall. RSAFS_current.csvis used only for the revision estimand (d), after all forecasts are computed. PAYEMS and UMCSENT were read and audited but are not used by the chosen model.- Data issues and their handling are listed in
data_issues(irregular publication timing, simultaneous first release of 2026-02/2026-03, PAYEMS benchmark revision in the 2026-02-28 vintage, a large RSAFS benchmark level revision after the last origin, pre-1978 gaps in UMCSENT, COVID observations in the AR(1) sample).
Method
- Baselines, all from vintage V: naive g_{last(V)} for both horizons; seasonal naive g_{T−12}; AR(1) OLS of g_m on a constant and g_{m−1} for 1993-02 ≤ m ≤ last(V), iterated h steps.
- My model (
model_name= RW-drift-base-effect; full text inmodel_description). A random walk with drift in log levels that is aware of base effects: ln L̂_T = ln L_{last} + h·δ with δ = average monthly log change over the 12 months ending at last(V); the YoY forecast is 100·ln(L̂_T / L_{T−12}), where the base level L_{T−12} is already published in the vintage. The idea: the change in YoY growth over the next h months equals the new monthly changes minus the monthly changes of the corresponding months a year earlier that drop out of the 12-month window. The second part is already known at the origin, and the naive forecast ignores it. The model has no estimated parameters. Its specification was fixed and logged (out/state/log.md) before any evaluation error was computed. Because nothing was selected on the evaluation sample, there is no look-ahead in model choice. - Evaluation: RMSE and MAE over the 10 origins per horizon against first-release actuals. The Diebold–Mariano test uses d_t = e²_naive − e²_model and a Bartlett (Newey–West) long-run variance with truncation lag h − 1, without the HLN correction, with a two-sided N(0,1) p-value. Revision (d) is the mean over the 10 h = 1 targets of |g^current − g^first|. The script is deterministic (
random_seed= null; no resampling is used).
Results
RMSE / MAE in percentage points (out/table1.csv; keys rmse_<model>_h<h>, mae_<model>_h<h>):
| model | h=1 RMSE | h=1 MAE | h=3 RMSE | h=3 MAE |
|---|---|---|---|---|
| naive | 0.997 | 0.849 | 1.420 | 1.200 |
| seasonal naive | 1.675 | 1.307 | 1.316 | 1.066 |
| AR(1) | 0.941 | 0.798 | 1.187 | 0.953 |
| RW-drift-base-effect | 0.550 | 0.486 | 1.142 | 0.934 |
- At h = 1 the model's RMSE is 45% below the naive forecast's (
rel_improvement_rmse_h1= 0.448). Its squared error is lower at 8 of the 10 origins (supplementary.n_origins_model_sqerr_lower_h1). DM = 2.43, p = 0.015 (dm_stat_h1,dm_pvalue_h1), soconclusion_code_h1= model_better_significant. - At h = 3 the improvement is 20% (
rel_improvement_rmse_h3= 0.196), with lower squared error at 6 of 10 origins (supplementary.n_origins_model_sqerr_lower_h3). DM = 1.55, p = 0.122 (dm_stat_h3,dm_pvalue_h3), soconclusion_code_h3= model_better_not_significant. - The model also has the lowest RMSE and MAE of all four methods at both horizons. The AR(1) beats the naive forecast only slightly (
rmse_ar1_h1,rmse_ar1_h3). The seasonal naive forecast is the worst at h = 1 and second-best among the baselines at h = 3 (rmse_snaive_h1,rmse_snaive_h3). - Revision: the first-release YoY growth of the 10 h = 1 target months differs from today's published growth by 0.548 pp on average in absolute value (
mean_abs_revision_h1_targets). All of these revisions are downward: the signed mean is −0.548 (supplementary.mean_revision_h1_targets_signed). This revision is about the size of the model's h = 1 MAE (mae_model_h1), so evaluating against current data would give materially different error levels. - Figure:
out/figure1.pngshows the first-release actuals and the naive and model forecasts by target month.
Uncertainty
- Only 10 origins are available per horizon, so the DM statistic's normal approximation is weak. The 10 origins also cover a single episode (a dip in growth around 2025-12 followed by a rise to about 6.5% in mid-2026).
- At h = 1 the evidence is moderately strong. The p-value is 0.015 (
dm_pvalue_h1), and with the HLN small-sample correction and Student-t(9) reference it is 0.047 (supplementary.dm_pvalue_hln_t_h1, sensitivity only), just under 5%. So the h = 1 significance survives the small-sample correction, but only barely. - At h = 3 the overlapping 3-step errors are serially correlated, and the long-run variance rests on only two autocovariance lags estimated from 10 points. The test does not reject (
dm_pvalue_h3= 0.122; the HLN version gives 0.277,supplementary.dm_pvalue_hln_t_h3). The 20% RMSE gain at h = 3 is therefore not statistically distinguishable from zero. - No bootstrap interval was computed: with 10 serially dependent pairs, a block bootstrap would add little information.
Limitations and open issues
- Gate conflict (open issue). The provided gate checks
origin_date < target_datefor every row ofout/forecasts.csv. The 40 rows with h = 1 fail this check. This is not information leakage. Under the task's own definition, the target is last(V)+1 andtarget_dateis the first day of that month. Because RSAFS is published with a 1–2 month lag, that date always lies before the month-end origin date. The real-time condition that matters, target month > last(V) (the target is not yet published in vintage V), holds for all 80 rows (out/state/checks.md). I kept the column exactly as TASK.md specifies rather than relabel dates. As a result, the latest gate receipt reportspassed: falsefor this item only. - Small evaluation sample (10 origins, one macro episode). The ranking could change over a different period.
- The model uses no external predictors (PAYEMS, UMCSENT). I did not test whether they would add information, and I make no claim about it.
- Actuals use the month-end "first release" definition of
first_release.csv. The 2026-08 and 2026-09 targets (h = 3, last two origins) are first released after a large benchmark level revision. Growth rates are computed within a single vintage, which limits the effect, but those two actuals are on a revised basis. - The AR(1) baseline is estimated on the full sample including the COVID swings, as prescribed. This probably biases its slope, but that is part of the baseline's definition.