Football · published error bar
The football house projection backtest
This page is fantasy football. The house model keeps vendor volume and
replaces vendor efficiency with each player’s own prior-season rates,
shrunk toward the positional mean. That claim is testable. The script is
tools/backtest.mjs. Run it. NBA, MLB, and NHL do not run
this house model — they rank on per-game (or per-appearance) rates × eGP,
without the football missed-game haircut, and they have no number here.
Local guest state No trackers Published methodology Works offline
What was measured
Leave-one-season-out cross-validation over seven consecutive season pairs, 2018→2019 through 2024→2025. The shrink strength is fitted on six pairs and scored on the seventh, then rotated, so every number above is one the model had not seen. Truth is each player’s actual next-season line, scored through the same scorer as the prediction, so a scoring convention cannot show up as model error. Positional means come from the earlier season only.
Both arms are handed identical volume — carries, receptions, pass attempts — so the comparison isolates one question: does shrinking last year’s efficiency beat using it raw? Which volume they are handed is the whole argument, and both answers are below.
| Raw 2024 efficiency | House (shrunk) | |
|---|---|---|
| Mean absolute error | 18.04 pts | 14.09 pts |
| Improvement | 21.8% less MAE, held out | |
| Spearman rank correlation | 0.952 | 0.972 |
| NDCG@36 (first three rounds, 12-team) | 0.939 | 0.962 |
| Precision@24 | 0.863 — of the 24 players ranked highest, 21 finished top 24 | |
| Season pairs | Seven, 2018→2019 … 2024→2025, each held out in turn | |
| Shrink multiplier | All 7 held-out folds chose the multiplier already shipping. The
constants in engine.js were not re-tuned to make this pass. |
|
| Shipped blend weight | 0.35 (capped; see limits) | |
The number above is a ceiling. Here is the floor.
The 21.8% hands both arms the player’s actual future volume. Nobody has that in August. Hand both arms last season’s volume instead and the same cross-validation returns 0.9% — mean absolute error 37.00 against 36.69, and negative on two of the seven folds. Add back the players who had a real season and then did not come back at all, 865 of them, and cross-validation picks no shrinkage whatsoever.
Efficiency regression buys little when the volume is also stale, because volume error swamps it: 37 points of error against 18. That is the honest shape of this model.
Production sits between those two numbers, and we cannot tell you where. The vendor supplies a projected volume for the season being drafted — a real forecast, not a copy of last year — and the house model replaces only the efficiency. So the ceiling arm overstates the gain and the floor arm understates it. The blend weight is capped at 0.35 for exactly this reason: the cap is the hedge against the ceiling, not a tuning knob.
Other limits
Seven season pairs is better than one. It is not many. The survivor cohort requires six games on both sides, which excludes the player who was hurt in week two — that is what the washout arm above exists to price, and it is the arm where the model looks worst. The production blend partner is the vendor’s full projection, which is stronger than the raw-efficiency baseline benchmarked here, so none of this is a claim about beating a vendor.
This is a comparison to a baseline — extrapolating last season’s efficiency forward, the assumption most projections ship with — not a claim that any other product is wrong.
NBA, MLB, and NHL are not on this page. Those packs ship with the house model off. Season value is rate × eGP. Football’s missed-game haircut is not applied. ESPN hockey and baseball rest-of-season totals are scaled to a rate before they blend with Marcel. Do not cite 21.8% or 0.9% as a basketball, baseball, or hockey accuracy figure.
Reproduce it
From the repo: node tools/backtest.mjs. It runs offline against
tools/season-lines.json and prints all three arms, every fold,
and the HOUSE_MODEL block that public/engine.js
ships. node tools/backtest.mjs --check runs on every push and
fails the build if these numbers drift from
tools/backtest-pin.json. If the page and the script ever
disagree, believe the script — and the build should already have caught it.
Those projections sit on a live board. Free, no account.
Open the draft board