SPB Git

spb/wp29_uqo

Public

# Real-time forecasting of US retail-sales YoY growth from ALFRED vintages (T1-forecast)

# Question

Using only data published at each of 10 month-end forecast origins (first_origin = 2025-10-31 to last_origin = 2026-07-31, n_origins = 10), how accurately could the year-over-year growth of US retail and food services sales (RSAFS, g = 100·ln(L_m/L_{m−12})) be forecast 1 and 3 months beyond the last published month, and does a model of my own design beat the no-change forecast when errors are measured against the first-release YoY of each target month?

# Data and exclusions

Inputs (all SHA-256 hashes verified against data/README.md; row counts in n_input_rows): 10 RSAFS and 10 PAYEMS ALFRED vintages, UMCSENT (current), RSAFS current vintage, and first_release.csv (actuals).

Real-time design. The forecast at origin V uses only RSAFS_vintage_V.csv; vintage_used = origin date for all 80 rows of out/forecasts.csv. The target is last(V)+h, where last(V) is the last month in that vintage (last_month_by_origin). Publication was irregular: vintages 2025-10-31 (last month 2025-08) and 2026-03-31 (last 2026-01) end two months before the origin, and vintage 2026-04-30 adds two months (2026-02 and 2026-03) at once. As a result 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-04 and 2026-06 … 2026-09. Actuals are yoy_first_release. RSAFS_current.csv is used only for the revision estimand (d). PAYEMS and UMCSENT were audited but are not used by any forecast.

Exclusions. None (out/exclusions.csv): all 80 (origin, horizon, model) pairs have a forecast and a first-release actual; n_analysed = 20 pairs for my model (10 per horizon). Other data issues are listed in data_issues: delayed PAYEMS release and benchmark shift; empty UMCSENT fields before 1978; a large RSAFS annual benchmark revision between the last origin vintage and the current vintage; and first releases dated at month-end granularity.

# Method

  • Baselines (from vintage V): naive = g_{last(V)}; seasonal naive = g_{T−12}; AR(1) = OLS of g_m on a constant and g_{m−1} over 1993-02…last(V), iterated h steps.
  • My model, "base-effect drift" (model_name, model_description): the log level h months ahead is 100·ln L_{last} + h·μ, where μ = g_{last}/12 is the mean monthly log change over the last 12 months of vintage V. The YoY forecast subtracts 100·ln L_{T−12}, which is already published in vintage V. The model keeps the recent trend. Unlike the naive forecast, which implicitly assumes each future month repeats the monthly change of the same month a year earlier, it accounts for the known base effects: the monthly changes that leave the 12-month window. Nothing is estimated and no predictor other than RSAFS vintage V is used. I fixed the specification before computing any evaluation error and did not tune it on the 10 origins. No other candidate model was evaluated.
  • Evaluation: RMSE and MAE against first release; DM test with d_t = e²_naive − e²_model and a Bartlett long-run variance with lag h−1, normal two-sided p-value, no HLN factor (as specified). Additional uncertainty measures: a circular block bootstrap (block length h, B = 10,000, random_seed = 20261030) of RMSE_model − RMSE_naive, and as a sensitivity check only, the HLN-corrected DM with t(9) p-values.
  • Protocol note: the baselines were computed in the same script run as the model rather than in a separate earlier run. The model specification had been written in out/state/log.md before that run.

# Results

Table 1 (out/table1.csv; RMSE / MAE in percentage points of YoY growth, n = 10 each):

model h=1 RMSE h=1 MAE h=3 RMSE h=3 MAE
naive 0.997 (rmse_naive_h1) 0.849 (mae_naive_h1) 1.420 (rmse_naive_h3) 1.200 (mae_naive_h3)
seasonal naive 1.675 (rmse_snaive_h1) 1.307 (mae_snaive_h1) 1.316 (rmse_snaive_h3) 1.066 (mae_snaive_h3)
AR(1) 0.941 (rmse_ar1_h1) 0.798 (mae_ar1_h1) 1.187 (rmse_ar1_h3) 0.953 (mae_ar1_h3)
base-effect drift 0.550 (rmse_model_h1) 0.486 (mae_model_h1) 1.142 (rmse_model_h3) 0.934 (mae_model_h3)
  • At h=1 my model has the lowest RMSE and MAE of the four methods. Its RMSE is 44.8% below the naive forecast (rel_improvement_rmse_h1 = 0.448). DM = 2.43, p = 0.015 (dm_stat_h1, dm_pvalue_h1), so conclusion_code_h1 = model_better_significant.
  • At h=3 my model again has the lowest RMSE and MAE, 19.6% below naive (rel_improvement_rmse_h3 = 0.196). However, DM = 1.55, p = 0.122 (dm_stat_h3, dm_pvalue_h3), so conclusion_code_h3 = model_better_not_significant.
  • AR(1) improves only slightly on the naive forecast. The seasonal naive forecast is the worst method at h=1 but beats naive at h=3.
  • Revisions (estimand d): the mean absolute difference between current-vintage and first-release YoY growth of the 10 h=1 target months is 0.548 pp (mean_abs_revision_h1_targets). All ten revisions are negative (mean_revision_h1_targets_current_minus_first = −0.548; individual values in revisions_h1_targets), reflecting the downward benchmark revision of RSAFS levels. This is about the same size as my model's h=1 RMSE (0.550) and about half the naive h=1 RMSE: under today's data the "truth" would look quite different.
  • out/figure1.png shows first-release actuals and the naive, AR(1) and model forecasts by target month. The largest misses for every method come from the surge in first-release growth in 2026-04 to 2026-06 and the drop in 2026-07.

# Uncertainty

There are only 10 origins per horizon, and at h=3 the errors from consecutive origins overlap. The DM test relies on asymptotic normality and a long-run variance estimated from 10 points, so its size is unreliable in samples this small.

  • At h=1 (lag 0, LRV = dm_lrv_h1 = 0.809) the result survives the HLN small-sample correction with t(9) critical values, but only barely: sens_dm_hln_pvalue_t_h1 = 0.047. The block bootstrap 95% interval for RMSE_model − RMSE_naive is [−0.697, −0.209] (boot_ci95_lo_h1, boot_ci95_hi_h1), which excludes zero. The h=1 evidence that the model beats naive is therefore fairly consistent across methods, but it rests on 10 observations from one short period (late 2025 to mid 2026).
  • At h=3 (lags 1–2, LRV = dm_lrv_h3 = 2.119) the improvement is not significant: normal p = 0.122, HLN/t(9) p = sens_dm_hln_pvalue_t_h3 = 0.277. The bootstrap interval [−0.689, 0.081] (boot_ci95_lo_h3, boot_ci95_hi_h3) includes zero. A block bootstrap with n = 10 and block length 3 is itself crude.
  • With n = 10, an observed relative improvement of about 20% is compatible with no true improvement. The h=3 result should be read as "no evidence of a difference", not as evidence of equal accuracy.

# Limitations and open issues

  • Small evaluation sample (10 origins over a period with irregular publication). This period includes delayed releases, which change the effective horizon relative to the calendar. The results may not generalise to other periods.
  • The first-release "truth" is defined at month-end granularity by the task designer. Later revisions are large (estimand d), so rankings evaluated against current data could differ; I did not evaluate this, because the task specifies first-release evaluation.
  • The model's gain comes from treating the known base effects explicitly. It cannot anticipate genuine shocks such as the 2026-05/06 surge, and it ignores PAYEMS and UMCSENT. I evaluated no other model, so no claim is made about whether these predictors would help.
  • DM inference follows the task specification (normal p-values, no HLN factor). The HLN and bootstrap numbers are sensitivity checks only.
  • All tasks were completed; feasibility = "complete".
Files · main
Menu

Theme