# F3-conf-04 — Multi-horizon forecasts of Canadian quarterly inflation with overlapping targets ## Scientific question Over 40 rolling quarterly origins (2015Q4 to 2025Q3), does a time-series model of your choice forecast Canadian quarterly CPI inflation (annualised, not seasonally adjusted) better than the seasonal-naive forecast at horizons of 1, 2 and 4 quarters, and is the difference in squared-error loss at the 4-quarter horizon statistically distinguishable from zero once the dependence created by overlapping multi-step forecast errors is taken into account? The estimands are the out-of-sample RMSE and MAE of the seasonal-naive forecast and of your model at each horizon, the relative improvement 1 − RMSE_model / RMSE_snaive per horizon, and, at h = 4, the mean loss differential d̄ = mean_t (e²_snaive,t − e²_model,t) with a standard error valid under serial correlation of the loss differential (Diebold–Mariano-type test). ## Inputs - `data/cpi_quarterly.csv` — observed data derived from the Bank of Canada Valet series V41690973 (Total CPI, Canada, index 2002 = 100, not seasonally adjusted, monthly; Statistics Canada table 18-10-0004-01); retrieved 2026-10-30 UTC; see `data/README.md` for URL, SHA-256 and terms. Columns: `quarter` (first month of the quarter, YYYY-MM-01), `cpi_avg` (mean of the three monthly index values), `infl_annualised` (target series: 400 × Δln cpi_avg, percent at annual rate, 6 decimals; empty for 1995Q1). 127 complete quarters, 1995Q1 to 2026Q3. - `data/cpi_monthly.csv` — the monthly index as served (`month`, `cpi`), 381 rows 1995-01 to 2026-09, provided for provenance; the quarterly file is the input of the analysis. ## Information-availability rules - Forecast origins: the 40 quarters t = 2015-10-01, 2016-01-01, …, 2025-07-01 (the `quarter` value). Horizons h ∈ {1, 2, 4}; the target of origin t at horizon h is `infl_annualised` of quarter t + h. - A forecast made at origin t may use only quarters dated ≤ t (expanding window: estimation, model selection and transformations included); a quarter's value is treated as known at the end of that quarter. - Seasonal-naive baseline: the forecast for the target quarter t + h is the value of `infl_annualised` four quarters before the target, i.e. of quarter t + h − 4 (always ≤ t for h ≤ 4). - The h = 4 test: with one forecast per origin, consecutive 4-step-ahead errors overlap, so the loss differential d_t is serially correlated; the standard error of d̄ must be computed with a heteroskedasticity-and-autocorrelation-consistent (long-run variance) estimator with a truncation lag of at least h − 1 = 3 quarters; report the lag used. - Errors are computed on the data as given; do not claim real-time validity. ## Required outputs - `out/forecasts.csv` — one row per (origin, horizon, model), columns `origin_date`, `target_date`, `horizon` (1, 2 or 4), `forecast`, `actual`, `model` (`snaive` or the value of `model_name`). Expected: 40 × 3 × 2 = 240 rows. - `out/results.json` with the keys: | key | type | unit | meaning | |---|---|---|---| | `task_id` | string | — | "F3-conf-04" | | `model_name` | string | — | short name of your model, used in the `model` column | | `model_description` | string | — | one-sentence specification (lags, seasonal terms, direct vs iterated, estimation sample, method) | | `n_origins` | integer | — | 40 | | `first_origin`, `last_origin` | string | date | first and last origin quarters | | `rmse_snaive_h1`, `mae_snaive_h1`, `rmse_snaive_h2`, `mae_snaive_h2`, `rmse_snaive_h4`, `mae_snaive_h4` | number | percentage points (annualised) | seasonal-naive errors per horizon | | `rmse_model_h1`, `mae_model_h1`, `rmse_model_h2`, `mae_model_h2`, `rmse_model_h4`, `mae_model_h4` | number | percentage points (annualised) | your model's errors per horizon | | `rel_improvement_rmse_h1`, `rel_improvement_rmse_h2`, `rel_improvement_rmse_h4` | number | fraction | 1 − rmse_model / rmse_snaive | | `loss_diff_mean_h4` | number | squared percentage points | mean over the 40 origins of e²_snaive − e²_model at h = 4 (positive = model better) | | `loss_diff_se_h4` | number | squared percentage points | HAC standard error of `loss_diff_mean_h4` | | `hac_lags_h4` | integer | quarters | truncation lag used in the HAC estimator | | `hac_kernel_h4` | string | — | kernel used (e.g. "bartlett", "rectangular") | | `dm_stat_h4` | number | — | loss_diff_mean_h4 / loss_diff_se_h4 | | `dm_pvalue_h4` | number | — | two-sided p-value of `dm_stat_h4` | | `feasibility` | string | — | "complete", "partial" or "infeasible" | | `feasibility_reason` | string or null | — | explanation if not complete | | `data_issues` | array of {"issue","handling"} | — | data problems detected and how handled (empty list if none) | | `n_input_rows` | object | — | {"cpi_quarterly.csv": rows read excluding header, "cpi_monthly.csv": rows read excluding header} | | `n_analysed` | integer | — | observations in the last estimation sample | | `random_seed` | integer or null | — | seed used, if any | - `out/report.md` with the sections: Question; Data and exclusions; Method; Results; Uncertainty; Limitations and open issues. - `out/analysis.py` — running `~/wp29/venv/bin/python out/analysis.py` from the workspace root must regenerate `out/results.json` and `out/forecasts.csv` from `data/` only, deterministically. - `out/claims.json` — a list of the central claims of the report: [{"id": "C1", "text": "...", "results_keys": ["..."], "status": "supported"|"unsupported"|"not_evaluated", "caveat": "..."}]. ## Constraints - No network access. Python: `~/wp29/venv/bin/python` (numpy, pandas, scipy, statsmodels available). Do not modify `data/`. - This task is designed to be completed in well under 20 minutes of work.