I Built a Prediction Engine for a World Cup I Never Watched
Let me get the embarrassing part out of the way: I'm ranked #6 in my company's global World Cup prediction game, 75% accuracy, highest in the top 20 — and I have not properly watched a single match of this World Cup.
It's not for lack of love. I'm a Premier League person, an Arsenal fan in Singapore, which means my football education happened on Saturday nights at 10:30pm — a civilised hour. This World Cup is in the Americas, so every kickoff lands between 1am and 5am Singapore time. There's devotion, and then there's a 3am group-stage match on a work night. I know my limits.
But my company's prediction game is global — colleagues in India, Israel, Spain, all posting picks — and I wasn't going to sit it out just because I'd be unconscious during the actual football. So I did the only reasonable thing for someone who works with data: I built something to watch the tournament for me.
Six weeks later it's a full website — score predictions, player ratings, a tournament simulator — that retrains itself twice a day while I sleep. This is the story of what I built, and more usefully, everything I got wrong along the way. Because I built this to learn, and the learning was mostly in the mistakes.

The one idea that does most of the work
My game gives 3 points for an exact score, 1 for the right result. Everyone I know picks the score they think is most likely. That turns out to be subtly wrong, and realising why was the moment this project got interesting.
Real example: the model rated 1–1 the single most likely score in one match, at 15%. But if you compute the expected points of every possible pick — summing what each would earn across all the ways the match could end — picking 1–0 was worth 0.713 points on average, versus 0.642 for the 1–1. The 1–0 is wrong more often, and still worth more, because of how the partial credit stacks up.
So the site never asks "what's the likeliest score?" It asks "which pick maximises expected points under the rules?" Different question, better answer, and honestly a transferable life lesson: optimise what you're actually scored on. (Arsenal optimising for pragmatic football instead of no trophies in May is left as an exercise for the reader.)
What I actually built
- Score predictions for every fixture — a probability for every possible scoreline, blended with bookmaker odds, and the expected-points-optimal pick.
- Player percentile radars — 1,298 players profiled per-90 against others in their position, FBref-style. Messi at 39: 97th percentile for goals, 48th for defensive actions. The data knows him.
- A tournament simulator — the live knockout bracket played 200,000 times per refresh, penalties included, producing every team's title odds.
- A self-updating pipeline — GitHub Actions refits the model twice daily; every pick is frozen in a database before kickoff and graded after. I cannot quietly delete my bad picks, which I'd like the record to show was a deliberate choice and not something I now regret every time Switzerland does something unexpected.

The model is older than some of my friends
Under the hood is Dixon-Coles (1997) — a Poisson model where every team gets an attack and a defence rating, and expected goals come from one line of maths. Not a neural network. Not an LLM. A statistics paper from the year Arsène Wenger won his first league title.
Why? Because I have ~1,600 usable international matches, and on that much data a fancy model memorises noise while a simple one learns football. Every parameter in Dixon-Coles means something I can argue with — "the model thinks Argentina's defence is elite" is a checkable claim. When you're learning, being able to interrogate your model beats squeezing out half a percent with something you can't explain.
The training data: 1,591 matches across every confederation, weighted so recent matches count more, live World Cup results counted 3×, and a FIFA-ranking prior holding up teams with thin histories. Fit by maximum likelihood in Python, exported to JSON, served as plain arithmetic in the browser.
The mistakes (the actual point of this article)
Mistake 1: I trusted big names. I built FIFA-style ratings for all 1,298 players and blended "squad quality" into the predictions. Obviously better players mean a better team, right? The backtest said no: 96 points fell to 86. Match results already contain team strength; famous names added bias. As an Arsenal fan who watched expensively assembled squads finish above us for years while we "won the transfer window" — I should have known. The feature now lives on the Players page for humans to enjoy, with exactly zero weight in the model.
Mistake 2: I froze my own model and didn't notice for two weeks. My data client cached API responses to disk permanently — correct for historical data, silently catastrophic for a live tournament. Sixty-six matches came and went while the model kept making confident predictions from matchday-one knowledge. Nothing crashed. That's the scary part: a stale model doesn't fail loudly, it fails plausibly. The fix took five minutes; the lesson took longer. The model's last-refit date now sits permanently in the site's nav bar, because freshness you can't see is freshness you can't trust.
Mistake 3: I argued with 41 bookmakers. My model made Panama 64% favourites over Ghana. The market said Ghana. Ghana won 1–0 — and a market-calibrated pick would have nailed the exact score while my model-only pick earned nothing. The market aggregates information no side-project can see: injuries, team news, dressing-room moods. The site now calibrates its probabilities to a de-vigged consensus of 41 bookmakers and contributes its own edge on exact scorelines. Humility, but make it mathematical.
Mistake 4: I forgot knockout football has different rules. In knockouts, 1–1 isn't a final score — it's an invitation to extra time and penalties. That quietly breaks every naive calculation: "exactly 2 total goals" becomes nearly impossible. I got burned on this by an actual wild-pick question before rebuilding the maths to roll drawn scorelines through extra time and a shootout model. Model the competition, not just the game.

The receipts
Because every pick is frozen pre-kickoff, I can show the model's actual report card — there's a whole page on the site for it, the Model Lab:
- 66.4% correct results on 396 held-out historical matches; ~75% for my picks in the actual game.
- A calibration curve that hugs the diagonal — when the model says 60%, it happens about 60% of the time. This is the chart I'm proudest of, because you can't fake it retroactively.
- A Brier score of 0.166 (0.333 is guessing).

And the honest ceiling: about a third of football matches will defy any reasonable forecast. That's not a bug, it's the sport — it's why we watch. A model claiming 90% is lying. One that hits two-thirds and tells you which picks are coin-flips is the useful thing.
What's next (and a question)
I built this with Claude Code as a pair-engineer — it wrote most of the code and ran every backtest I asked for; I supplied the scepticism and the mistakes documented above. As a way to learn statistics, data pipelines, and validation, I can't recommend the setup enough: a leaderboard, a deadline the real world enforces, and a scoreboard that grades you every matchday. Even the matchdays you sleep through.
Which brings me to the real question. The World Cup ends this soon. The Premier League starts next month — matches at genuinely watchable Singapore hours, a 38-game season of data instead of a 7-game knockout, and a model owner with a documented Arsenal bias that the backtests will need to hold in check.
Should I build one for the EPL season? And what should it predict — scorelines, the title race, top four, relegation odds?
I have a feeling I already know what the model will say about Arsenal's title chances. I'm just not sure I'm emotionally ready for a calibrated answer.
Built for an office prediction game played for bragging rights — and, at #1 in Singapore. Not betting advice: the model has never heard of "North London energy," and I refuse to teach it.



