Home-grown expected goals (xG)
An expected-goals model built entirely from public NHL play-by-play, and better-calibrated than the leading public model in the high-danger tail.
What the model does
Every shot is scored for its chance of becoming a goal. The model reasons about where the shot was taken - its location, distance, and angle to the net - the speed and shape of the play that created it - a rush, a rebound, or a settled-zone look - and the game context it happened in, meaning strength state, score, and whether the net was empty. It learns these relationships from hundreds of thousands of past shots and their outcomes, all from the public NHL play-by-play feed, with no proprietary tracking data.
What log-loss measures
Log-loss is the standard scoreboard for probability predictions. It rewards being confident and right, and punishes being confident and wrong, so a model only scores well if its stated probabilities actually match how often goals happen. Lower is better. It is a stricter test than simple accuracy because it grades the calibration of every probability, not just the final yes/no call.
Model comparison: held-out 2024-25 season
| Metric | Ours | Public model |
|---|---|---|
| Log-loss | 0.2146 | 0.2185 |
| Brier score | 0.05820 | 0.05892 |
Confidence-interval verification (1,000 bootstrap resamples)
A single headline number can be a fluke. To check that the calibration edge is real and not sampling noise, we resampled the 2024-25 shots 1,000 times and re-measured each model's bias in its top decile of chances - the high-danger tail where over-prediction is most dangerous. The public model over-predicts goals there by a wide, statistically clear margin; ours is indistinguishable from zero; and the gap between them excludes zero comfortably.
| Quantity | Estimate | 95% CI | Excludes 0? |
|---|---|---|---|
| Public model top-decile bias | +8.28pp | [+7.58, +8.97] | Yes |
| Our top-decile bias | −0.31pp | [−1.03, +0.39] | No |
| Gap (public − ours) | +8.59pp | [+7.95, +9.25] | Yes |
Power-play calibration: why models disagree, and how we check it
Different xG models handle the power play differently. Some fit a separate model for each strength state and calibrate each one independently. Ours fits a single model across all game states and gives it strength as an input, so two shots from the same spot can still be valued differently on the power play than at 5-on-5 - but they are valued through the same learned relationships. That is a deliberate trade: pooling every strength state gives the model far more shots to learn from and keeps it steady on the thin states, at the cost of never letting the power play have its own independent fit. The man advantage is where that choice shows up most, because shot rates, shot quality, and the reliability of the underlying signal all shift the most from 5-on-5.
Rather than assert which approach is better, we tested it. Measured walk-forward - every season scored by a model trained only on the seasons before it, never on itself - we compared our total predicted expected goals against actual goals scored, league-wide, broken out by strength state. A ratio near 1.000 means the model isn't systematically over- or under-predicting scoring in that situation. Across the three most recent seasons, even strength comes in at 1.017, all situations at 1.032, and the power play at 1.057. Only even strength sits inside a ±3% tolerance. Out-of-sample we over-predict power-play goals by roughly six percent, and it is not a one-season wobble: the power-play ratio has sat at or above 1.000 in each of the last five seasons.
Discrimination (AUC): does the model rank shots correctly?
Discrimination asks a different question than calibration: how well does the model separate the shots that become goals from the ones that don't? The standard measure is AUC. On even-strength shot discrimination, a leading public model publishes 0.800 on its 2023-26 window. Measured walk-forward on that same window, ours is 0.772. We do not lead on discrimination, and we are not going to present it as though we do.
| Even-strength AUC (walk-forward: each season scored by a model trained only on earlier seasons) | Ours | Public model |
|---|---|---|
| 2023-26 (their published window) | 0.772 | 0.800 |
| 2025-26 season (most recent) | 0.776 | — |
| Five-season walk-forward (ours) | 0.768 | — |
Our five-season figure (0.768) is lower still, because it reaches back into seasons where every public model is weaker rather than curating to the best recent window. And on the power play, the situation where models disagree the most, we do not lead either: 0.672 against the public model's 0.695 on their window, with our interval running [0.665, 0.679] and never reaching their number. An earlier version of this page claimed a power-play lead. That claim compared their cross-validated figure against our model scored on seasons it had itself been trained on, which flattered us by about two hundredths of AUC on the power play and about one and a half at even strength. Measured strictly walk-forward, the lead does not exist. The one thing the correction does not show is decline: on this honest footing 2025-26 is our best even-strength season of the five, and on the power play it lands mid-pack rather than at the bottom.
Validation figures reflect the data through 2025-26 and are recomputed each season. Head-to-head benchmark tests (log-loss, AUC, bootstrap intervals) are dated snapshots of a specific held-out season, labeled where they appear.