skip to content

~/work/world-cup-2026-engine · DEPLOYED

World Cup 2026 Prediction Engine

A stacked ML ensemble — Poisson, XGBoost, LightGBM and Elo — run through 10,000 Monte Carlo tournament simulations to forecast the 2026 World Cup, backtested on three prior cups.

pythonxgboostlightgbmscikit-learnstreamlit

The problem

Predicting a 48-team World Cup is a calibration problem wearing a prediction problem’s clothes. Football is low-scoring and high-variance — the favourite loses often enough that a single “who wins” guess is close to useless. What’s actually useful is a probability that’s honest: if the model says a team wins 30% of the time, it should win about 30% of the time. So the engine isn’t built to be right about the winner; it’s built to be calibrated about how likely each outcome is, then to carry that uncertainty all the way through a tournament bracket.

The ensemble

No single model earns trust on its own, so each match outcome is four views stacked together:

  • A Poisson goal model fits per-team attack/defence strengths and samples scorelines, which gives win/draw/loss probabilities with the right shape for a low-scoring sport.
  • XGBoost and LightGBM classifiers each read a 37-feature match representation — Elo, time-decayed form, rolling xG, head-to-head, squad value/age/caps, confederation, World Cup experience, match context — trained on internationals since 1990.
  • A logistic-regression meta-learner stacks the three, trained out-of-fold so no base model grades its own homework, with Platt scaling on top to pull the final probabilities back onto empirical frequencies.

Elo runs alongside as both a feature and a sanity check, with tournament-weighted K-factors (32 for World Cups, 20 for qualifiers, 10 for friendlies) so a friendly never moves a rating like a final does.

# one tournament, simulated 10,000 times (multiprocessing)
def simulate_tournament(models):
    groups = play_group_stage(models)          # 12 groups, ensemble per match
    standings = rank(groups)                    # pts, GD, GF, head-to-head
    qualified = top_two(standings) + best_thirds(standings, n=8)
    return play_knockouts(qualified, models)    # incl. penalty shootouts

champions = Counter(simulate_tournament(models) for _ in range(10_000))

Why simulate instead of just predicting

Rating a single match doesn’t tell you who lifts the trophy — seven knockout rounds of variance sit between the group stage and the final. So the whole tournament is run 10,000 times: each pass plays every group match through the ensemble, resolves standings by FIFA’s tiebreakers, advances the best four third-placed teams, and plays the knockouts through a simulated final, penalty shootouts included. Championship and per-stage odds are the aggregate, reported with Wilson 95% confidence intervals so the dashboard never shows a number more precise than the sample earns.

Does it actually work?

The honest answer this site insists on: backtested, not yet validated on 2026. On a 192-match holdout of the 2014, 2018 and 2022 World Cups, the calibrated ensemble scores 55.7% accuracy, 0.914 log loss, and a 0.190 ranked probability score — an RPS skill score of 0.216 over a uniform baseline. RPS, not accuracy, is the number I trust here: it rewards being calibrated rather than confident. (LightGBM posts gaudier figures in-sample; those are optimistically biased, and the out-of-sample backtest is the one that counts.) The 2026 run is live on a Streamlit dashboard — the tournament itself is the test set I don’t have yet.

Decisions & tradeoffs

  • Calibration over accuracy. Platt scaling slightly lowers raw accuracy and meaningfully improves RPS and log loss. For a betting-shaped problem, a well-calibrated 60% beats an overconfident 75%.
  • Stacking over picking a favourite model. The meta-learner consistently beat its best single base learner out-of-fold — ensembling bought robustness, not just a fractional metric bump.
  • Data was the ceiling. Squad features (market value, caps, top-league share) and time-decayed form moved the needle more than hyperparameter tuning — SHAP confirmed Elo difference and squad value dominate the importances.

What broke and what I’d change

  • Early backtests leaked: features computed over the full history let the model peek at the future. Fixed with strict temporal cutoffs per match — accuracy dropped, which was the whole point.
  • Third-place advancement is fiddly. FIFA’s “best four of six” rule needed its own tiebreak logic, and an early version advanced the wrong teams in tight groups.
  • Next: an automatic calibration check on every retrain, and replacing the static squad snapshots with an agent that reconciles roster changes on its own — which is exactly what’s queued next in the lab.