← Methodology

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.

A note on correctness: the single most important safeguard is that the model only ever sees information that existed before the shot was taken. Score, in particular, is a live-updating field, so we feed the model a causal, forward-filled pre-shot score rather than the score after the shot resolved. That is what stops the model from quietly 'peeking' at the outcome it is trying to predict, and it is what makes the out-of-sample results below trustworthy.

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

Ours Public modellower is better →
Calibration error (ECE)~6× tighter
0.24pp
1.5pp
Top-decile over-predictionthe dangerous tail
0.8pp
8.3pp
MetricOursPublic model
Log-loss0.21460.2185
Brier score0.058200.05892
The head-to-head is the held-out 2024-25 season, with 119,627 shots matched one-to-one to the leading public benchmark. The public model is used only as a yardstick here - never as an input, a training target, or a feature in our model.

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.

QuantityEstimate95% CIExcludes 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.

The honest caveat: this measures calibration - whether a model's predictions track real-world scoring frequency on average - not discrimination, which is how well a model ranks individual shots by quality. The discrimination comparison, including the power-play head-to-head, is right below. The power-play calibration miss above is a known open problem for us, not a rounding artifact. We have confirmed that it is a genuine level shift rather than a change in the mix of shot types being taken, but we have not yet established why, and the fix is a retrain rather than a copy edit. We would rather publish the number while it is still wrong than quietly wait until it isn't.

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)OursPublic model
2023-26 (their published window)0.7720.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.

We also measured every arena's scorekeeper bias and A/B-tested adjusting our shot locations for it. It did not improve accuracy, so we do not apply it - and we do not hide behind it. Four pieces of fine print. First, the AUC comparison is not controlled: the public model's figure comes from cross-validation inside its own published window, while ours holds out entire future seasons, which is the harder test - and its shot locations are bias-adjusted where ours are not. We are not offering that as an excuse for the gap, only as a reason not to read the gap as exact. Second, the model benchmarked on AUC here and the model benchmarked on calibration earlier are two different public models, not one. Third, the intervals in this section come from 200 game-clustered resamples, not the 1,000 used in the head-to-head further up. Fourth, the season figures published elsewhere on this site come from a model fit on a fixed multi-season window that includes those seasons, so the walk-forward numbers here - not the fitted ones - are the honest guide to how accurate the model is on a season it has never seen. We publish the more conservative framing on purpose, including when it costs us the comparison.

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.