All research

Netherlands vs Sweden, World Cup 2026

A statistical model for one World Cup match, priced against the bookmaker and frozen in git before kickoff.

When
June 2026
Type
Solo project
Stack
Python, pandas, pytest
Links
Code, report
  • Weighted ensemble0.179
  • Average ensemble0.180
  • Poisson0.181
  • Dixon-Coles0.181
  • Elo0.186
  • Base rates0.226
Walk-forward Ranked Probability Score on a common set of 19,500 international matches. Lower is better. The top four intervals overlap, so I treat those models as tied.
Show as a table
ModelRPS95% CI
Weighted ensemble0.1790.177 to 0.181
Average ensemble0.1800.178 to 0.181
Poisson0.1810.178 to 0.183
Dixon-Coles0.1810.178 to 0.183
Elo0.1860.184 to 0.188
Base rates0.2260.224 to 0.227

Question

Can a transparent goal model price a single international match well enough to disagree with the market? And can I show afterwards that I did not move the goalposts once the result was in?

Method

  • International results back to 2006, FIFA rankings, a self-computed Elo, and odds from two bookmaker sources.
  • Features use only information from before kickoff. I checked this by flipping match results and confirming the pre-match features did not change.
  • A ladder of models from a base-rate floor up to Poisson, Dixon-Coles, a Bayesian hierarchical model and ensembles. Every prediction is walk-forward: each match is predicted by a model trained only on earlier matches.
  • Scored with RPS and log-loss, with bootstrap confidence intervals, and the de-vigged bookmaker line run through the same scoring.
  • Stakes from expected value against de-vigged odds, sized at quarter-Kelly, with a bankroll Monte Carlo.
Bet
NED win
Odds taken
1.74
Model
62.7%
Market, de-vigged
55.6%
Edge
+9.1%

Staked at quarter-Kelly and frozen before kickoff.

The freeze

The prediction and the bet slip were committed and tagged prematch-freeze before kickoff. Everything written after the match is append-only on top of that tag. The tag ended up on commit b399a33 instead of the planned 7273af0, and the report says so.

Result

The model is well calibrated and beats the base-rate floor over twenty years of matches. It does not beat the other structured models in any way I would defend: calling the ensemble best by 0.002 of RPS is noise.

It also has a measured flaw. It priced over 2.5 goals at 71% when the sharp market said about 56%, because the Poisson models carry lopsided qualifier scorelines into a tight match. Pricing against the market is how I caught it, so I cut totals from the live bet, apart from one small over 2.5 kept on purpose to watch the bias play out.

The match finished 5 to 1 and both bets won. That is the least informative fact on this page: one match cannot separate a good model from a lucky one.

What it cannot answer yet

The goal models are trained and scored on internationals, while the staking and closing-line value are checked on club odds, because usable international closing odds barely exist. The two never mix. That keeps the evaluation honest, but model quality and betting mechanics are measured on different data. Whether it beats the market only has a live answer, and the ledger needs dozens of pre-registered bets before that answer means anything.