Bootroom
← Bootroom

Receipts

Projection accuracy

42 matches · 1,312 predictions · 10 May 2026 – 29 May 2026

A projection you can't check is just an opinion. Every match we project is logged before kickoff and, once it has been played, graded against what actually happened. Below is the model's track record: how often each call landed, and its Brier score — the mean squared error of a probability, where lower is better and 0 is perfect.

MarketNPredictedActualBrier
Home win4240%33%0.239
Draw4227%36%0.240
Away win4233%31%0.217
Both teams score4258%50%0.258
Over 2.5 goals4255%33%0.281
Over 3.5 goals4235%19%0.191
Anytime scorer5308%5%0.051
Anytime assist5305%4%0.037
All markets1,3120.081

Calibration

When the model says 30%, does it happen 30% of the time? Each point groups predictions by the probability we gave them; the closer a point sits to the diagonal, the better calibrated the model. Points above the line are events that happened more often than predicted, below the line less often.

Predicted 4%, actual 3% (n=776)Predicted 13%, actual 6% (n=238)Predicted 26%, actual 26% (n=103)Predicted 34%, actual 29% (n=66)Predicted 44%, actual 26% (n=58)Predicted 55%, actual 41% (n=44)Predicted 63%, actual 48% (n=25)Predicted 70%, actual 0% (n=2)050100050100Predicted %Actual %
ModelPerfect calibration

Track record for model v3.0.0, v3.1.0. It is young: early figures move a lot as matches settle, and markets or buckets with few predictions are noisy. We publish it anyway, and it updates as results come in.

Data quality

2025–26 season · last checked 14 Jun 2026

Sources

Players

API-Football
Goals / gm
Goals per game across the season.
Shots on target / gm
Shots on target per game.
Assists / gm
Assists per game across the season.
Key passes / gm
Passes that directly create a shot, per game.
Passes / gm
Total passes attempted per game.
Pass accuracy
Completed passes divided by attempts.
Dribbles (succ.) / gm
Successful dribbles per game.
Dribble success
Successful dribbles divided by attempts.
Tackles / gm
Tackles attempted per game.
Interceptions / gm
Interceptions per game.
Duels won / gm
Duels won per game (ground + aerial combined).
Duel win rate
Duels won divided by duels contested.
Saves / gm
Saves per game (GK only).
Save %
Saves divided by (saves + goals conceded), 0 to 1 (GK only).
Goals conceded / gm
Goals conceded per game (GK only).
Penalty saves / gm
Penalty saves per game (GK only).
Clean sheet %
Fraction of appearances with zero goals conceded (GK only).
SofaScore
Shots / gm
Shots attempted per game (SofaScore where covered — it counts blocked shots, which API-Football omits; API-Football elsewhere).
xG / 90
Expected goals per 90.
xA / 90
Expected assists per 90.
Big chances created / 90
Big chances created per 90. Zero when unmapped.
Big chances missed / 90
Big chances missed per 90, an indicator of clinical-finishing failures.
Progressive carries / 90
Carries that move the ball significantly toward the opponent's goal, per 90.
Take-ons attempted / 90
Take-ons attempted per 90.
Take-ons succeeded / 90
Take-ons succeeded per 90.
Take-on success
Take-ons won divided by take-ons attempted.
Touches / 90
Ball touches per 90.
Tackles won / 90
Tackles won per 90 (vs. tackles attempted). More meaningful signal than raw tackles.
Blocks / 90
Blocks of opponent shots or passes per 90.
Clearances / 90
Clearances per 90.
Ball recoveries / 90
Ball recoveries per 90.
Aerial duels won / 90
Aerial duels won per 90.
Aerial win rate
Aerial duels won divided by aerial duels contested.
Errors leading to shot / 90
Errors leading to a shot per 90. Coverage skewed defensive.
Understat
xG per shot
Total xG divided by total shots. Zero when no Understat coverage.
Goals − xG / 90
Goals minus xG per 90. Positive: clinical. Negative: wasteful.
FotMob
xGOT / 90
Expected goals on target per 90, post-shot xG including shot placement.
DefCon / 90
FotMob's composite defensive index, per 90.
Poss. won att. 3rd / 90
Possessions won in the attacking third per 90, a high-press signal.
Goals prevented / 90
Goalkeeper goals-prevented per 90, xG-saved differential (GK only).

Teams

API-Football
goals_for
Goals scored, total across the season.
goals_against
Goals conceded, total across the season.
points
League points accumulated.
win_pct
Share of matches won.
draw_pct
Share of matches drawn.
loss_pct
Share of matches lost.
clean_sheet_pct
Share of matches with zero goals conceded.
failed_to_score_pct
Share of matches the team failed to score.
btts_pct
Share of matches where both teams scored.
over_2_5_pct
Share of matches with three or more total goals.
form_score
Last-5-results score (0 to 100), weighted toward the most recent match.
Bootroom composite
possession_pct
Possession percentage (FBref squad `standard__poss`).
interceptions_per_match
Interceptions per match (FBref squad scope).
tackles_won_per_match
Tackles won per match (FBref squad scope).
shots_for_per_match
Shots taken per match (FBref squad scope).
shots_against_per_match
Shots conceded per match (FBref opponent scope).
sot_for_per_match
Shots on target per match (FBref squad scope).
sot_against_per_match
Shots on target conceded per match (FBref opponent scope).
Understat
xg_for_per_match
Team xG per match, summed from own shots.
xga_per_match
xG conceded per match, summed from opponent shots.
xg_difference_per_match
xG minus xGA per match. Positive: creating better chances than allowed.
xg_overperformance
Goals minus xG. Positive: clinical finishing or luck. Negative: wasteful.
xga_overperformance
xGA minus goals conceded. Positive: keeper bailing out. Negative: conceding better chances than xGA suggests.
FotMob
xpoints_per_match
Expected points per match (Justice-table simulation). Big-5 and UEFA cups (2024+) only.
xpoints_diff_per_match
xPts minus actual points per match. Positive: unlucky and deserved more. Negative: overperformed actual results.
xg_against_per_match
Opposition xG conceded per match. FotMob primary (2024+), Understat fallback (Big-5, 2014+), ASA for MLS.
xgot_against_per_match
Opposition shot-quality on target conceded per match. FotMob only — null for older / non-FotMob seasons.

Sources attributed to the upstream feed. Bootroom aggregates per (player or team, league, season) and computes percentiles against the active pool.

A data product is only as good as its data. We check ours against FBref, an independent reference, on every release. Below is how our season totals compare per league — the average difference per player, where lower is better and 0 means we match exactly.

LeaguePlayersMinutesGoalsShotsIntercep.Tackles
Premier League374±11.6±0.01±2.0±1.36±2.45
Bundesliga346±13.5±0.01±1.4±0.98±1.64
Primeira Liga338±13.6±0.02±2.2±2.22±2.99
Eredivisie333±15.5±0.01±0.7±1.05±1.27
Super Lig356±16.1±0.03±0.8±1.09±1.24
Championship539±16.5±0.01±1.4±1.30±1.67
La Liga383±17.6±0.03±2.2±1.46±2.91
Ligue 1341±18.4±0.01±1.4±0.95±1.75
Serie A406±20.9±0.01±1.6±1.05±2.07

Figures are the mean absolute difference per player (450+ minutes). Goals are near-exact everywhere; minutes land within a few matches; interceptions within about one. Tackles are checked against SofaScore — FBref doesn't publish a season tackles total — and SofaScore is itself validated against FBref's tackles-won. Leagues FBref does not cover aren't shown here; we hold those to our internal consistency checks instead.

League adjustment

Version 1.1 · calibrated 2026-07-01T13:55:45.429Z

What it is

Bootroom shows percentiles within a player's league. League adjustment rescales rate stats onto a Big-5-aggregate baseline, so a Championship or Eredivisie prospect can be read on the same axis as a Premier League player. The output is always a 1-SD band, never a single number, and the source-league quality tier is shown next to every adjusted value. Leagues without enough calibration data (tier C) are refused rather than guessed.

The model

Two layers. First, a per-league strength factor λ_L from cross-league UEFA cup results (Bradley-Terry-style goal-difference fit, home advantage removed at −0.3 goals) blended 60% / 40% with a ClubElo per-country prior. EPL is the anchor at λ=1.0. The Big-5 baseline λ is the geometric mean of the five Big-5 lambdas.

Second, a per-stat correction factor cstat, from→to fit on the transition dataset: every player who appeared in two different Big-5 leagues in consecutive seasons, ≥600 minutes in both. Per-pair coefficients are used when n ≥ 20 in the train split; otherwise the stat-level aggregate is the fallback.

adjusted = raw × (λ_target / λ_source) × cstat, from→to

League strength (λ vs EPL)

EPL is the 1.0 anchor. Big-5 leagues tier B; everything else tier C (uncalibrated).

LeagueλTier
Premier League1.000B
La Liga0.887B
Bundesliga0.877B
Serie A0.859B
Ligue 10.850B
Primeira Liga0.844B
Eredivisie0.780B
Jupiler Pro League0.773B
Süper Lig0.749B
Big-5 baseline (geometric mean)0.893
Per-stat aggregate correction (Big-5 cluster)

The fallback correction factor used when per-pair sample is < 20. Read: for a typical Big-5 source, this is the multiplier that the adjustment applies in addition to the λ ratio.

StatMeanSDN
Goals / 901.1130.763261
Assists / 90*1.1420.798236
Shots / 900.9640.503513
Key passes / 901.1080.596504
Tackles / 901.1050.498524
Interceptions / 901.0280.588491
G + A / 901.1270.729367

* Assists/90 is the noisiest stat in the calibration set (hold-out r=0.283). Adjustment is applied but the band is wider for this stat. In the UI, adjusted assists/90 carries a small asterisk to flag the wider uncertainty.

Per-pair Big-5 correction matrix (shots / 90)

Cell crow → col is the per-pair correction for shots/90 from the row league into the column league, with n in parentheses. Blank cells mean n < 20 in the train split; the aggregate (above) is used as fallback. Every allowlisted stat has its own such matrix in source — see src/lib/radar/league-adjustment.ts.

From → ToPremierLaBundesligaSerieLigue
Premier League·1.03 (29)1.15 (22)1.29 (31)
La Liga0.92 (33)·1.09 (25)
Bundesliga0.81 (42)·1.07 (22)
Serie A0.76 (44)·0.97 (26)
Ligue 10.82 (55)0.86 (24)0.92 (21)1.01 (32)·
Calibration sample
  • 1401 league transitions — Big-5↔Big-5 plus calibrated non-Big-5→Big-5 sources (≥600 min in both pre and post seasons, 2018–2025 window)
  • Train / hold-out: 1090 / 311
  • 1004 cross-league UEFA cup fixtures (2023–2025)
  • Allowlisted stats: 10
Hold-out backtest result
Overall Pearson r (adjusted)0.840
Raw-extrapolation baseline r0.837
Named-transition spot check16 / 18 HIT

Across our hold-out, the adjusted prediction correlates with post-transfer rate stats at r=0.848, vs r=0.842 for raw extrapolation. The model improves the marginal cases — extreme league gaps, defensive-vs-attacking rate stats, edge transitions — more than it improves the average case. Most transitions are close enough to identity that raw extrapolation already does well. We show the lift honestly rather than overselling it: the value of the model is precisely in the cases where raw extrapolation would mislead.

What we refuse to do
  • Publish a point estimate. The output is always a band.
  • Adjust a tier-C source league (Eredivisie, Primeira, Süper Lig, Jupiler Pro, MLS, Brasileirão, Liga MX). The toggle reports the source as uncalibrated and shows the raw percentile.
  • Adjust a blocklist stat. Pass-accuracy, dribble-success and aerial-win-rate invert under league pressure; clearances and blocks scale with team tactic. Adjustment would mislead, so the toggle leaves them raw with a small *.
  • Claim a counterfactual transfer. The baseline is Big-5 aggregate, not a specific league.
Source
  • ADR: docs/10-decisions/ADR-0005-league-level-adjustment.md
  • Calibration script: scripts/calibrate-league-adjustment.ts
  • Backtest: scripts/backtest-league-adjustment.ts
  • Production code: src/lib/radar/league-adjustment.ts