An open-source statistical model that forecasts 2026 FIFA World Cup matches and title odds — Elo ratings → Dixon-Coles bivariate Poisson → Monte Carlo simulation. No machine-learning black box, no scraped bookmaker odds: just transparent, reproducible football maths.
▶ Live predictions (full 48-team, 50,000-simulation model): https://cup26matches.com · How it works / methodology · Live insight feed · Interactive bracket simulator
🔴 The tournament is LIVE (Jun 11 – Jul 19). The production model now conditions on real results: finished matches are locked, eliminated teams collapse to 0%, the actual bracket (incl. the new best-third qualification, solved with bipartite matching) is used, and only the remaining matches are simulated — re-run automatically within minutes of every full-time whistle.
This repo open-sources the core match model + our honest backtest so you can run, inspect and reproduce the numbers.
It's tested the honest way — walk-forward, out-of-sample on 913 real internationals (Oct 2023 – Jun 2026). Every match is predicted using only data available before kickoff, then scored against the actual result — with proper scoring rules (RPS, log-loss, Brier), not just accuracy, because accuracy alone rewards lucky guessing. Reproduce it yourself in one command:
node backtest.mjs| Metric (763 evaluated, 150 burn-in) | Model | Baseline |
|---|---|---|
| Ranked Probability Score (the football standard, ↓) | 0.175 | coin-flip 0.241 |
| Log-loss (↓) | 0.89 | coin-flip 1.10 |
| Brier score (↓) | 0.52 | coin-flip 0.67 |
| Expected Calibration Error (↓) | 2.3% | < 5% = well-calibrated |
| Correct result (win/draw/loss) | 62% | always-home 49% · coin-flip 33% |
| When a clear favourite (p ≥ 50%) | 69% | — |
A forecaster is honest when the things it calls "70%" happen about 70% of the time. Pooling every probability the model issued across the out-of-sample matches:
| Model said | Actually happened | n |
|---|---|---|
| 5% | 7% | 225 |
| 15% | 13% | 374 |
| 26% | 24% | 804 |
| 35% | 32% | 205 |
| 45% | 54% | 200 |
| 55% | 56% | 149 |
| 65% | 67% | 136 |
| 75% | 76% | 95 |
| 85% | 85% | 100 |
Changelog — Jun 11, 2026: Monte Carlo raised to 50,000 trials (5× lower tail noise); in-tournament conditioning is live; backtest extended with RPS + a reliability curve + ECE; data refreshed through Jun 2026. · Jun 7: goal-model variance denominator 350→400; per-team strength priors applied on the live site on top of this core model.
No model is a crystal ball — football is high-variance and draws are genuinely hard. These are well-calibrated estimates, and we make no claim to beat the betting market.
The model's call on every finished match of the tournament, updated as it happens:
20/32 correct picks (63%) · avg RPS 0.152 (coin-flip ≈ 0.245) · updated 2026-06-20
| Date | Result | Model's pick | |
|---|---|---|---|
| 2026-06-19 | Scotland 0–1 Morocco | Morocco 61% | ✅ |
| 2026-06-19 | Brazil 3–0 Haiti | Brazil 83% | ✅ |
| 2026-06-19 | USA 2–0 Australia | USA 49% | ✅ |
| 2026-06-19 | Turkey 0–1 Paraguay | Turkey 41% | ❌ |
| 2026-06-18 | Czech Republic 1–1 South Africa | Czech Republic 43% | ❌ |
| 2026-06-18 | Mexico 1–0 South Korea | Mexico 51% | ✅ |
| 2026-06-18 | Switzerland 4–1 Bosnia & Herzegovina | Switzerland 61% | ✅ |
| 2026-06-18 | Canada 6–0 Qatar | Canada 60% | ✅ |
| 2026-06-17 | Portugal 1–1 DR Congo | Portugal 71% | ❌ |
| 2026-06-17 | Uzbekistan 1–3 Colombia | Colombia 65% | ✅ |
| 2026-06-17 | England 4–2 Croatia | England 52% | ✅ |
| 2026-06-17 | Ghana 1–0 Panama | Ghana 41% | ✅ |
| 2026-06-16 | France 3–1 Senegal | France 55% | ✅ |
| 2026-06-16 | Iraq 1–4 Norway | Norway 69% | ✅ |
| 2026-06-16 | Argentina 3–0 Algeria | Argentina 68% | ✅ |
| 2026-06-16 | Austria 3–1 Jordan | Austria 56% | ✅ |
| 2026-06-15 | Belgium 1–1 Egypt | Belgium 58% | ❌ |
| 2026-06-15 | Iran 2–2 New Zealand | Iran 54% | ❌ |
| 2026-06-15 | Spain 0–0 Cape Verde | Spain 83% | ❌ |
| 2026-06-15 | Saudi Arabia 1–1 Uruguay | Uruguay 56% | ❌ |
| 2026-06-14 | Germany 7–1 Curaçao | Germany 80% | ✅ |
| 2026-06-14 | Ivory Coast 1–0 Ecuador | Ecuador 47% | ❌ |
| 2026-06-14 | Netherlands 2–2 Japan | Netherlands 44% | ❌ |
| 2026-06-14 | Sweden 5–1 Tunisia | Sweden 44% | ✅ |
| 2026-06-13 | Qatar 1–1 Switzerland | Switzerland 62% | ❌ |
| 2026-06-13 | Brazil 1–1 Morocco | Brazil 45% | ❌ |
| 2026-06-13 | Haiti 0–1 Scotland | Scotland 51% | ✅ |
| 2026-06-13 | Australia 2–0 Turkey | Australia 40% | ✅ |
| 2026-06-12 | Canada 1–1 Bosnia & Herzegovina | Canada 59% | ❌ |
| 2026-06-12 | USA 4–1 Paraguay | USA 60% | ✅ |
| 2026-06-11 | Mexico 2–0 South Africa | Mexico 71% | ✅ |
| 2026-06-11 | South Korea 2–1 Czech Republic | South Korea 49% | ✅ |
Every call is listed — hits and misses. Probabilities are the model's frozen pre-match numbers (ratings don't re-fit mid-tournament), so nothing here is retro-fitted. Reproduce with node track-record.mjs.
Run a blog, forum or fan site? The live model is embeddable — free, auto-updating all tournament:
<!-- Live title-race board (top-10 championship odds, 50k sims) -->
<iframe src="https://cup26matches.com/embed/title-race/" width="100%" height="430"
style="border:0;border-radius:12px" loading="lazy" title="World Cup 2026 title odds"></iframe>
<!-- Real-time next-match strip (live W/D/L, rotates at kickoff) -->
<iframe src="https://cup26matches.com/embed/next-match/" width="100%" height="92"
style="border:0;border-radius:10px" loading="lazy" title="Next World Cup 2026 match"></iframe>More widgets + copy-paste snippets: cup26matches.com/en/widgets
Open data (CC BY 4.0 — free to use/quote/chart with a link back): the full per-team tournament probabilities, regenerated after every match — probabilities.json · probabilities.csv
No dependencies. Node 18+.
git clone https://github.com/Hicruben/world-cup-2026-prediction-model.git
cd world-cup-2026-prediction-model
node predict.mjs brazil argentina # head-to-head probabilities
node predict.mjs usa mexico usa # 3rd arg = home team (host bonus)
node backtest.mjs # reproduce the accuracy numbers
node calibrate.mjs # rebuild ratings from data/results.jsonExample:
$ node predict.mjs spain germany
spain (Elo 2074) vs germany (Elo 1927) [neutral]
spain win 53.2% ████████████████
draw 26.8% ████████
germany win 20.0% ██████
- Team strength (Elo). Each nation starts from a long-run prior, then is calibrated on
recent real internationals — wins over strong sides in important games move a rating more than
friendlies, and recent form outweighs old form. See
calibrate.mjs. - Each match (Dixon-Coles Poisson). Ratings → expected goals → a Dixon-Coles bivariate
Poisson gives win/draw/loss probabilities. The Dixon-Coles correction fixes plain Poisson's
well-known under-count of low-scoring draws (0-0, 1-1). See
elo.mjs. - The tournament (Monte Carlo). The live site plays all 104 matches 50,000 times through the real bracket to get championship & advancement odds — and, now the tournament is underway, locks every finished result (real standings, real qualifiers, real bracket slots) and simulates only what's left. Full write-up: cup26matches.com/methodology.
| File | What |
|---|---|
elo.mjs |
The match model — Elo, Dixon-Coles τ, Poisson, matchProb, sampleMatch |
calibrate.mjs |
Build calibrated ratings from data/results.json |
backtest.mjs |
Walk-forward out-of-sample evaluation (RPS, log-loss, Brier, ECE + reliability curve) |
predict.mjs |
CLI head-to-head predictor |
track-record.mjs |
Regenerates the live 2026 track-record table in this README |
data/results.json |
913 real international results (Oct 2023 – Jun 2026) |
data/elo-calibrated.json |
Calibrated Elo for the 48 finalists |
data/wc2026-results.json |
Finished 2026 World Cup matches (feeds the track record) |
data/model-backtest.json |
Saved backtest metrics |
MIT — see LICENSE. Built by Cup26 AI. If you use it, a link back is appreciated. ⭐ the repo if you find it useful!