A play-by-play Monte Carlo simulator, not a regression on final scores. Every projection here is the summary of 10,000 simulated games.
Each simulated game starts at a kickoff and runs play by play. At every snap a gradient-boosted classifier picks the play type from the game state — down, distance, field position, clock, score, and both teams' recent form. A second set of models resolves what happens: yards gained (quantile regression across 13 quantiles, so the tail of a 60-yard run is drawn from a real distribution rather than a mean), completion, sack, interception, fumble, touchdown, and field-goal success by distance. Clock, downs and possession advance from the outcome. All 10,000 games advance in lockstep as array operations, which is what makes a full slate cheap enough to re-run weekly.
No player identities and no team dummies. Every team and quarterback enters as three rolling windows of their own past production — career, last 17 games, last 8 — computed strictly from games that had already finished. That is enforced in SQL with windowed frames that exclude the current row, so a game can never see its own result. Pooling this way means a rookie quarterback is described by the same features as a veteran, and a mid-season starter change is a change of inputs rather than a missing entity.
Play models are fitted on 2010–2022. Early stopping uses 2023–24. 2025 was not looked at until the final evaluation — no tuning decision was made against it. The calibration that maps raw engine output to a published projection was also fitted on 2023–24 only. This is why the track record separates the two rather than pooling all 816 games into one flattering number.
The raw engine is a slightly compressed picture of football: it scores a little low and its game-to-game spread is a little narrow. Two corrections fix that, both fitted out of sample. The centre is a least-squares stack of the engine's simulated mean and a separate direct margin model — the stack absorbs the rescaling and keeps whatever signal each carries alone. The spread is one global factor applied to every game's simulated deviations, which fixes the systematic under-dispersion while preserving the engine's own view of which games are volatile.
The market is still better at the thing that matters most. On the 2025 holdout the model's margin MAE was 10.06 against the closing line's 9.72. It picks winners at a similar rate but is not more accurate about margins.
The play engine also produces slightly too many touchdowns and too few field-goal attempts relative to real football, which is a known open issue rather than a fixed one. Interval coverage is close to nominal, so the uncertainty it reports is roughly honest — but a projection that disagrees with the market is more often the model being wrong than the market being wrong.
Play-by-play, schedules, rosters and closing lines come from
nflfastR via nfl_data_py. Lines are the closing numbers carried in
that dataset and may differ from any individual book.
Every Possible Sunday — built Sep 10, 2026. Play-by-play and lines from nflfastR via nfl_data_py. Team marks belong to their owners and are used here to identify clubs.