real-time forecast record
++ Every forecast, archived before the answer exists. +
++ A forecast is only evidence if it was fixed in public before the outturn + was published. Each round below was committed with a timestamp, and is + scored here against realised data — whatever that shows. +
+The record is one round old.
++ 1 round archived; 0 forecast period(s) scored. + The earliest period that can be scored is 2026Q2, once the ONS first quarterly estimate lands. +
+ ++ There is no headline accuracy figure on this page yet, because one + cannot honestly be computed from zero observations. That is the point of + publishing the record from its first day rather than from the day it + starts to look good: the empty state is part of the evidence, and it is + checkable that nothing was quietly discarded before the record began. +
++ What already exists is a pseudo-out-of-sample evaluation — the + boe-svar model re-estimated at 49 expanding-window origins and compared + with random-walk, drift and AR(1) baselines. That is a genuine test of + the method and it is reported on the + validation page. It is not the + same thing as a real-time record, because the modeller already knew what + happened. Both are needed; only the second is on this page. +
+What was claimed, and when.
+| Round | Model | Data edge | Periods | Scored | Artifact |
|---|---|---|---|---|---|
| 2026-07-21 | +boe-svar | +2026Q1 | +13 | +0 | +2026-07-21/boe-svar.json |
+
+ Rounds are append-only. Files under forecasts/rounds/ can be
+ added but never edited or deleted, and a
+ CI job
+ fails any pull request that tries. If a forecast turns out to have been
+ built on a mistake, the correction is a new round; the superseded one
+ stays in the history where it can still be counted against us.
+
Four numbers, not one.
++ A single accuracy headline hides the failures that matter, so each round + is scored on four things: +
+| Measure | What it catches |
|---|---|
| Mean absolute error | +Typical size of a miss, in percentage points. | +
| Signed bias | +Whether the model is persistently high or low. A model that is always 0.4pp too optimistic is a different — and more fixable — problem than one that is 0.4pp out in random directions, and MAE cannot tell them apart. | +
| Band coverage | +How often the outturn fell inside the 68% and 90% credible bands. A fan chart whose 68% band catches 30% of outturns is overconfident regardless of how good its central path looks. | +
| Baseline comparison | +Error relative to a random walk, a random walk with drift, and an AR(1). Beating a naive baseline is the minimum bar for a model to have earned its complexity; a forecast reported without one is marketing. | +
Why outturns are versioned rather than overwritten
++ Official data is revised, sometimes substantially and sometimes years + later. A track record that always scores against the latest vintage + quietly rewrites its own history every time the ONS revises a quarter, + and can flatter or punish a forecast that never changed. +
+
+ So forecasts/outturns.json stores the vintage alongside
+ every observation and appends revisions rather than replacing them.
+ Each score records which vintage it was computed against, and can be
+ reproduced exactly.
+
Limits worth stating plainly.
+-
+
- + The record is short. One round is not a track record, + and will not be one for at least a year. Nothing on this page should be + read as demonstrated forecasting skill until the scored count is well + into double figures. + +
- + Two variables only. UK GDP growth and CPI inflation. + Bank Rate, unemployment, wage growth and borrowing are not forecast + here yet. + +
- + The coefficients are not re-estimated each round. The + boe-svar replication stays estimated on the paper's 1992Q1–2023Q2 + sample; only the conditioning data edge moves. This keeps the hosted + model identical to the published replication, at the cost of ignoring + post-2023 information in the coefficients themselves. + +
- + Rounds are irregular. They follow data-edge refreshes, + not a fixed calendar. A regular cadence is the intended next step, and + until it exists the round spacing is itself a discretionary choice. + +
", + f" {rounds} round{plural} archived; {scored} forecast " + "period(s) scored.", + ] + if card["next_period_to_score"]: + lines.append( + " The earliest period that can be scored is " + f"{esc(card['next_period_to_score'])}, once the " + "ONS first quarterly estimate lands." + ) + else: + lines.append(" Every archived period now has an outturn.") + lines.append("
") + return "\n".join(lines) + + +def render_rounds(card: dict) -> str: + rows = [] + for detail in card["detail"]: + href = f"{REPO_BLOB}/{detail['path']}" + label = "/".join(Path(detail["path"]).parts[-2:]) + rows.append( + "{esc(label)}