SPB Git

spb/wp29_uqo

Public

# 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, and first_release.csv (28 target months). All SHA-256 hashes match the README (see out/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 of out/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_release from first_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.csv is 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 in model_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), so conclusion_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), so conclusion_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.png shows 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_date for every row of out/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 and target_date is 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 reports passed: false for 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.
Files · main
Menu

Theme