Track record

Every night the model's newest prediction for each upcoming match is saved. The last one saved before a match kicks off is its locked prediction. Only locked predictions are scored, so nothing can be adjusted after the result. Backtests (predictions of past seasons) are never mixed in: they are on the methodology page.

So far

250 matches scored. Compared with league-average forecasts: model better. Compared with bookmakers' odds: no detectable difference.

Locked predictions
251
Scored
250
Awaiting a result
0
Voided (postponed, suspended, cancelled)
1
Not counted: made after the actual kick-off
0
Missed: no prediction before kick-off
0
Awarded (decided off the pitch), not scored
0

Recording since . Matches before then have no locked predictions, and never count as missed. Updated .

How the model compares

Each scored match is also forecast by two others, on exactly the same matches: base rates (the league's usual home, draw and away frequencies, i.e. knowing nothing about the teams) and the market (bookmakers' closing odds with their margin removed, which arrive a few days after each match). The score is log loss: lower is better, and it punishes confident mistakes hardest.

The difference is the model's log loss minus the other's (negative = model better), with a 95% interval. If the interval includes zero, the verdict is “no detectable difference”.

Against base rates

LeagueMatchesModelBase ratesDifference ± 95% intervalFirst halfSecond halfVerdictMatches needed
All leagues2500.9291.050−0.121 ± 0.044−0.113−0.128model better77
Premier League501.0031.049−0.046 ± 0.096−0.069−0.024no detectable difference73
Bundesliga450.9541.046−0.092 ± 0.1040.000−0.180no detectable difference77
La Liga500.9261.087−0.160 ± 0.087−0.188−0.132model better60
Serie A600.8951.042−0.147 ± 0.105−0.129−0.165model better104
Ligue 1450.8711.024−0.153 ± 0.094−0.172−0.135model better63

Against the market

LeagueMatchesModelMarketDifference ± 95% intervalFirst halfSecond halfVerdictMatches needed
All leagues2500.9290.915+0.015 ± 0.016+0.005+0.024no detectable difference163
Premier League501.0030.967+0.036 ± 0.032+0.054+0.018model worse130
Bundesliga450.9540.980−0.026 ± 0.035−0.041−0.012no detectable difference139
La Liga500.9260.874+0.052 ± 0.034+0.029+0.076model worse148
Serie A600.8950.893+0.002 ± 0.037+0.013−0.008no detectable difference202
Ligue 1450.8710.865+0.006 ± 0.038−0.030+0.040no detectable difference162

“First half” and “second half” split the matches in date order. A difference that holds in only one half is “consistently better (or worse) but modest”. “Matches needed” is roughly how many matches it takes to detect a gap the size the backtest found: 0.08 in log loss against base rates, 0.02 against the market. Below that, “no detectable difference” means “not enough matches yet”, not “no better”.

Calibration

When the model says 60%, does it happen about 60% of the time? Forecasts are grouped by the chance they gave, and each group's average forecast is compared with how often the outcome happened.

Across: the average forecast in each group. Up: how often it happened, with its 95% interval. On the diagonal = honest forecasts.

Calibration, all leagues
OutcomeForecast rangeForecastsAverage forecastHappened95% interval
Home win0%–20%2116%14%5%–35%
Home win20%–40%9531%35%26%–45%
Home win40%–60%9149%55%45%–65%
Home win60%–80%3967%77%62%–87%
Home win80%–100%484%>99%51%–>99%
Draw0%–20%3217%16%7%–32%
Draw20%–40%21825%22%17%–27%
Away win0%–20%5614%9%4%–19%
Away win20%–40%11830%22%16%–30%
Away win40%–60%6548%58%46%–70%
Away win60%–80%1168%82%52%–95%

Scored matches

The 50 most recent of 250, newest first. Times in UTC. Every scored match is in summary.json on the data branch.

Scored matches, newest first
Kick-offMatchScoreHomeDrawAwayLog lossPredictedModel version
Atlético Madrid v BarcelonaLa Liga1–234%24%42%0.8678c2a856
Lens v MarseilleLigue 11–246%24%30%1.2118c2a856
Fiorentina v JuventusSerie A2–226%27%47%1.3308c2a856
TSG Hoffenheim v Schalke 04Bundesliga2–158%24%18%0.5448c2a856
Celta Vigo v LevanteLa Liga0–053%24%23%1.4328c2a856
Villarreal v GetafeLa Liga2–059%25%16%0.5208c2a856
Inter Milan v ComoSerie A2–050%24%26%0.7038c2a856
Manchester United v Aston VillaPremier League4–442%26%32%1.3498c2a856
SC Freiburg v Union BerlinBundesliga2–057%24%19%0.5728c2a856
Le Mans v MonacoLigue 11–121%22%57%1.5038c2a856
Brest v LyonLigue 12–032%25%43%1.1468c2a856
Le Havre v LorientLigue 10–233%29%38%0.9698c2a856
Angers v NiceLigue 10–227%29%44%0.8158c2a856
Real Sociedad v Rayo VallecanoLa Liga1–050%26%24%0.6898c2a856
1. FC Köln v Bayer LeverkusenBundesliga1–223%23%54%0.6118c2a856
Monza v AtalantaSerie A0–119%20%61%0.4978c2a856
Sunderland v ChelseaPremier League1–028%27%45%1.2828c2a856
Genoa v AC MilanSerie A1–223%26%51%0.6728c2a856
Rennes v LilleLigue 11–236%27%37%0.9818c2a856
Crystal Palace v LiverpoolPremier League1–323%25%52%0.6508c2a856
Ipswich Town v BournemouthPremier League1–224%24%52%0.6478c2a856
Brighton & Hove Albion v BrentfordPremier League2–048%25%27%0.7258c2a856
Elche v Real BetisLa Liga0–235%26%39%0.9428c2a856
Napoli v LazioSerie A1–145%27%28%1.3018c2a856
Paris Saint-Germain v TroyesLigue 11–087%9%4%0.1408c2a856
Valencia v Real MadridLa Liga0–317%23%60%0.5158c2a856
Roma v SassuoloSerie A3–071%17%12%0.3488c2a856
Auxerre v Paris FCLigue 10–130%27%43%0.8328c2a856
VfB Stuttgart v Werder BremenBundesliga1–062%20%18%0.4818c2a856
Nottingham Forest v Manchester CityPremier League3–119%25%56%1.6578c2a856
Osasuna v Athletic BilbaoLa Liga1–134%29%37%1.2458c2a856
Parma v BolognaSerie A1–225%29%46%0.7698c2a856
Espanyol v Deportivo La CoruñaLa Liga0–049%27%24%1.3268c2a856
Fulham v Newcastle UnitedPremier League1–137%27%36%1.3068c2a856
Arsenal v Hull CityPremier League1–075%18%7%0.2848c2a856
RB Leipzig v FC AugsburgBundesliga5–266%19%15%0.4208c2a856
Borussia Dortmund v SV ElversbergBundesliga2–172%17%11%0.3268c2a856
Paderborn 07 v Eintracht FrankfurtBundesliga2–232%28%40%1.2838c2a856
Mainz 05 v Bayern MunichBundesliga1–213%17%70%0.3588c2a856
Torino v LecceSerie A1–149%28%23%1.2768c2a856
Cagliari v FrosinoneSerie A1–035%25%40%1.0518c2a856
Málaga v Racing SantanderLa Liga1–247%24%29%1.2558c2a856
Leeds United v Tottenham HotspurPremier League1–041%27%32%0.8858c2a856
Sevilla v AlavésLa Liga0–048%26%26%1.3448c2a856
Everton v Coventry CityPremier League3–153%27%20%0.6458c2a856
Venezia v UdineseSerie A2–132%27%41%1.1528c2a856
Toulouse v StrasbourgLigue 13–139%26%35%0.9288c2a856
Hamburger SV v Borussia MönchengladbachBundesliga3–138%27%35%0.9718c2a856
Deportivo La Coruña v OsasunaLa Liga2–141%27%32%0.8888c2a856
Newcastle United v EvertonPremier League0–148%27%25%1.3768c2a856

How the record is kept honest

  • The lock is the last prediction made before kick-off. “Made” means when it was written to disk, the strictest reading.
  • Postponed, suspended or cancelled: the lock is kept but voided, and never scored. A rearranged match gets a new lock before its new kick-off.
  • Checked against the real kick-off: a prediction that turns out to be from after the actual kick-off is not counted. A match no update predicted in time is recorded as missed.
  • Edits show: each line of the lock log carries a fingerprint (sha256) of the line before it, so changing or deleting an old line breaks the chain. The pipeline checked the chain before writing this summary.
  • Timing: every night's files are committed to the public data branch, which is protected against rewriting. That is strong evidence of when each prediction was published, not proof: it rests on GitHub's records.

Notes from the pipeline

  • Only predictions locked before kick-off by the daily run are scored. Backtests and any retrospective predictions are never mixed in.
  • Differences are the model's log loss minus the other forecaster's (negative = model better), with a 95% interval. With few matches the intervals are wide. Matches needed is roughly how many matches it would take to detect a gap the size the backtest found (0.08 in log loss against base rates, 0.02 against the market).
  • Market probabilities come from closing odds with the margin removed proportionally (an assumption). They appear a few days after each match.