Explainable, self-calibrating football match-prediction engine — built for the 2026 World Cup.
xPoints predicts World Cup match results by maximising expected points under the Kicktipp 3/1/0 scoring scheme. It anchors to de-vigged market odds, applies a calibrated tournament draw-nudge, and predicts the argmax tendency plus its modal scoreline. Every probability is computed by tested code — not by hand — and the engine grades its own predictions after each match and re-calibrates.
The methodology is the spec: methodology.md is a human-readable decision spec, and engine/predict.py deterministically executes it. Change a rule in prose → change one constant in code → the backtest confirms whether it helped.
- Expected-value decision rule. Under 3/1/0,
EV = 1·P(tendency) + 2·P(exact score). The engine predicts the argmax-EV tendency, then the single most-likely scoreline within it — it never hedges a draw it can't justify. - Self-calibrating, with a guardrail against overfitting. After each batch it computes Brier score and by-tier hit-rate (
engine/season.py) and proposes a re-weight only after ≥8 probability-bearing games — the discipline that caught an early over-tune (documented in the calibration log). - Honest confidence tiers. Every pick ships as 🟢 clear-favourite / 🟡 lean / 🔴 coin-flip, with a stated ceiling (~58–65% overall, ~80% on the 🟢 slate). No inflated accuracy claims.
- Human-in-the-loop by design. The engine runs data → compute → cards → grade → propose automatically; the human owns exactly three judgment calls (
RUNBOOK-HITL.md). Designing that automation/judgment boundary is the point. - A written track record.
methodology.md§5 is a calibration log across four engine versions — including post-mortems of calls that were wrong. The engine beats hand-managed picks (62% vs 46% tendency) precisely because it removes emotional hedging.
market odds (free sources)
│ de-vig strip the overround → true P(home / draw / away)
▼
+6% tournament draw-nudge market underprices draws (~11 pts, full-sample stable)
│
+5–7% draw overlay only if Sports Mole / Forebet call a draw; can only reinforce
▼
argmax(P_home, P_draw, P_away) → tendency → confidence tier (🟢 / 🟡 / 🔴)
│
modal scoreline within tendency, anchored to the O/U line → final pick + card
│
after kickoff: grade (3/1/0) → Brier + by-tier attribution → propose re-weight (≥8 games)
Honest, no cherry-picking — the same rule applied to every game:
- ~73% tendency on the 🟢 clear-favourite slate — the "confident product".
- ~62% engine tendency across a full batch, vs 46% on earlier hand-managed picks: discipline beats gut.
- Overall was dragged down by an abnormal early draw cluster (group stage ran ~34% draws vs market-implied ~20%); as draws regress, overall climbs toward ~60%.
Match-by-match log in results-log.md; every calibration decision in methodology.md.
pip install -r requirements.txt
python -m pytest # 62 tests, ~1sPredict a matchday from a fixture:
python -m engine.run_matchday matches/2026-06-28_Panama-vs-England.jsonLive odds are optional — set ODDS_API_KEY (see .env.example) to pull from The Odds API; without it you pass odds/probabilities directly in the fixture.
engine/
predict.py decision rule: de-vig → nudge → argmax → tier → scoreline
scoring.py 3/1/0 grading
backtest.py replay predictions against actual results
season.py Brier score + by-tier accuracy + draw-rate calibration
weights.py propose_reweight() — guarded re-calibration (≥8 games)
ko_strategy.py knockout mode (extra time / penalties, no draws)
connectors/ odds_api.py — The Odds API, env-based key
tests/ 62 tests covering every engine module
matches/ teams/ per-match & per-team data (json + human-readable card)
methodology.md the decision spec the engine executes
Spec-driven and agent-orchestrated (Claude Code): the methodology is authored as an executable spec, the engine ports it deterministically, and 62 tests lock the behaviour. The human owns the judgment — the strategy, the calibration calls, and every pick submitted. That spec-first, test-locked, human-in-the-loop loop is how I take a system from 0→1.
Python 3.12 · pydantic · pytest. Dependency-light and fully deterministic — every number is reproducible and tested.
Scope: built for the Kicktipp 3/1/0 prediction game. Public betting odds are used only as the best-calibrated probability prior, not for wagering.
MIT — see LICENSE.