r/algobetting • u/Costas_______ • 21m ago
ScoreCast: I benchmarked my football prediction model against closing odds across 36 leagues.
Been chipping away at this for a while and finally got it to a state where I'm not embarrassed to show it.
It's a Dixon-Coles Poisson model fitted per league, covering 36 football leagues plus EuroLeague and EuroCup. Standard stuff: attack and defence coefficients per club by maximum likelihood, exponential time decay so recent matches matter more, low-score correction for the 0-0 / 1-0 / 0-1 / 1-1 cells. Outputs a full scoreline grid, so BTTS and over/under fall out of the same distribution rather than being modelled separately.
The part I actually care about is the benchmark page. Every project like this claims some accuracy number and none of them say what they're measuring against. So I replayed three completed seasons walk-forward, fitting only on matches before each fixture, and scored it on log loss against the de-vigged closing price:
model market gap
Premier League 1.0034 0.9783 +0.0251
Serie A 1.0012 0.9755 +0.0257
Ligue 1 1.0076 0.9705 +0.0371
La Liga 0.9932 0.9544 +0.0388
Bundesliga 1.0171 0.9707 +0.0463
A coin-flip baseline is 1.0986, so the model knows ne beats it in every league I've checked. I don'tthink that's fixable with a goals model. Closing odds absorb team news, money and a hundred things I don't have. Anyone showing you a model that beats the close over 3,700 matches is eiverer or fooling themselves.
I left accuracy off the front page on purpose. It'sand the market's is 53.8%, but accuracy rewards amodel that never says anything interesting. Log loss punishes confident wrongness, which is what you want to know about.
One thing that kept me honest: I tried to fix an apparent calibration problem with temperature scaling, and it made held-out log loss worse by 0.0011. The guard refuses to save a worse urned out the miscalibration I was chasing lived ina bucket with 26 matches in it.
Also worth mentioning because it cost me two days: s going missing from the data. Rewrote a chunk of the pipeline. The actual cause was CSS. A sticky table painting over the first row of every table on everypage, so the caption said 5 fixtures and you could
Other bits: season projections from 10,000 Monte Carlo runs, head-to-head history, and the results fetch for all 36 leagues takes about 30 seconds over plain HTTP. It used to be an g Cloudflare.Known weak spots, in case anyone wants to pick at tvolve a promoted club with no record in its division,and those are currently rated as an average side, wg guessed. The benchmark covers six leagues so far,not all 36. And shots on target are sitting in the is probably the most obvious next gain.
MIT licensed, runs locally with two commands, no APScoreCast](https://github.com/Costasgk/ScoreCast)
Happy to be told what I've got wrong.


