How Our NHL Model Works: Poisson Goals and Advanced Stats
Hockey is a low-scoring, high-variance sport, and that shapes how we model it. Rather than predicting win/loss directly, our NHL model predicts goals β and lets the maths of a Poisson distribution turn those goal expectations into probabilities for every market we bet. Here is how it actually works, including a leak we found and fixed in an earlier version, because that story explains our whole approach to features.
Goals, not wins: the Poisson core
The core is an XGBoost-Poisson goals model. Gradient boosting (XGBoost) looks at both teams' form, matchup context and advanced stats and outputs a single number for each side: its expected goals, or lambda. From two lambdas β home and away β a Poisson framework computes the full probability distribution over every realistic scoreline, which in turn produces prices for three markets: moneyline, puck line Β±1.5, and totals (over/under).
Skip the hand-calculation.
Get real value bets flagged for you β 7-day free trialThe model is trained on roughly 10,400 NHL games from 2017 to 2025, sourced from the official NHL API, using 54 features per game. On held-out data it lands at a mean absolute error of about 1.37 goals for home teams and 1.36 for away teams β meaning the average game's actual goal count is a little over a goal off the model's expectation, which is in line with how much randomness a single hockey game genuinely contains.
One detail that matters for totals: training scores already include overtime and shootout goals, so we do not bolt on an artificial adjustment for extra time β the totals probabilities are built on the same scoring convention end to end. If you want the full mechanics of over/under pricing, we cover it separately in over/under and totals explained.
The Elo leak we found β and why honest features beat impressive ones
Team strength (Elo rating) is one of the model's inputs, but it has a history worth telling honestly. The first version of this model fed each historical training game the team's current Elo rating β the one calculated after years of subsequent results. That is a look-ahead leak: the model was effectively being shown a piece of the future every time it trained, and Elo's importance in the model was inflated because of it.
The fix, shipped as model v2, stores a pre-match Elo snapshot for every historical game, in chronological order, and trains only on the rating as it stood the moment before puck drop. The headline accuracy barely moved β but it became honest. And tellingly, Elo dropped out of the top-10 most important features once the leak was closed, because its old weight had simply been leak-inflated confidence, not real predictive power.
We treat this as a template lesson across every sport we model, not just hockey: every feature has to be knowable before the puck actually drops. If a number could only exist after the fact, it does not belong in training data no matter how good it makes the backtest look. You can read the full story of this fix in Hockey model v2: fixing a data leak, and for the general concept behind team-strength ratings, see Elo ratings explained.
Goalies and the lineup layer
Hockey is a sport where one player can single-handedly swing a scoreline more than almost any other team sport, and that player is the goaltender. The model factors in the confirmed or expected starting goaltender for each team along with that goalie's save statistics. In a sport where a single save percentage difference of a few points can be worth close to a full goal per game, knowing who is actually between the pipes β a workhorse starter on form versus a backup getting a rest night β is one of the single most game-shaping facts available before puck drop.
MoneyPuck priors: Corsi, xGF%, and the PDO luck detector
Beyond raw goals and wins, we bring in advanced shot-quality metrics from MoneyPuck, refreshed weekly and always used as a prior-season (N-1) feature so there is no leakage from the current, still-unfolding season into a game being predicted:
- CF% (Corsi For %) β the share of all shot attempts (on net, missed, and blocked) a team controls at even strength. It is a proxy for territorial and puck-possession dominance that goes beyond the final score.
- xGF% (expected-goals-for %) β the same idea but weighted by shot quality rather than just shot volume, so a team generating high-danger chances scores higher even if its raw shot count is modest.
- PDO β on-ice shooting percentage plus save percentage, which tends to hover around 100 for any team over a large enough sample.
PDO is the most interesting of the three because it behaves like a structural luck detector. A team sitting at a PDO of 103 has been finishing more of its chances and stopping more of the opponent's than is sustainable β it has been running hot, and the number tends to regress toward 100 over time. The betting market, watching the same recent win streak everyone else sees, often overrates exactly these teams. Feeding PDO into the model lets it lean the other way when the underlying process does not match the recent record.
This mirrors a pattern we have found across every sport we model: efficiency metrics consistently beat simple counting stats like goals-for or shots-for alone, because they capture the process behind the result rather than just the result itself.
Playoffs and other context
Playoff hockey is a different animal from an October Tuesday. We identify playoff games directly from the NHL's own game-ID format, and once a game is flagged as playoff, a set of series-context features kicks in: which game number it is within the series, each team's current series score, and whether either team is facing a must-win elimination game. That lets the model treat a Game 7 elimination differently from a February back-to-back on the road β the stakes, desperation and coaching decisions genuinely differ, and the model's inputs now reflect that.
From probability to bet
A model probability is not a bet by itself. Every candidate goes through the same value-betting discipline we apply across every sport we cover. First, a de-vigged Pinnacle price β Pinnacle's odds with the bookmaker's margin mathematically stripped out β acts as the truth anchor; if there is no Pinnacle line available, there is no bet, full stop. If you have not seen how that stripping works, devigging explained walks through the maths with worked examples.
For moneyline and puck line, our model's implied probability is not allowed to exceed Pinnacle's de-vigged implied probability by more than roughly 10%; for totals, the tolerance is wider, around 20%, because totals lines move for structural reasons beyond pure probability. On totals specifically, we also always bet the line closest to the model's actual expected total (home lambda plus away lambda) rather than whichever line happens to show the highest raw EV on paper β calibration is weakest out on the extreme lines, and chasing max-EV there tends to just chase noise.
Once a bet clears the anchor check, it needs a minimum edge of 5% expected value, capped at 18% β anything above that threshold is a near-certain sign of a data artifact rather than genuine mispricing. Odds are capped at 3.50, since long-shot markets are the hardest to calibrate precisely. Stakes are sized with fractional Kelly at 25%, capped at 5% of bankroll on any single bet, which keeps variance survivable even across a cold stretch.
- Pinnacle anchor required β no de-vigged Pinnacle line, no bet, on any market.
- Anchor tolerance β model implied probability can't exceed Pinnacle's de-vigged implied by more than ~10% (moneyline/puck line) or ~20% (totals).
- Totals line choice β closest to model expected total (home lambda + away lambda), not the highest raw EV.
- EV floor / cap β minimum 5% expected value, capped at 18%.
- Max odds β 3.50, to limit exposure to poorly calibrated long shots.
- Staking β fractional Kelly at 25%, capped at 5% of bankroll per bet.
How we know it works
We hold ourselves to different validation standards depending on the market, because a single metric does not tell the whole story everywhere. For moneyline, we track closing line value (CLV) β did we consistently get a better price than the market's final, most-informed number β because that metric is reliable for straightforward win/loss markets. For totals and puck line, we lean on realised ROI and calibration instead, because CLV can be genuinely misleading on handicap-style markets: we have measured stretches where CLV was positive on a market that was still losing money in real terms. Trusting only CLV there would have hidden a real problem.
On pure prediction accuracy, the goal model's mean absolute error of roughly 1.37/1.36 goals (home/away) is the number we track over time as new seasons complete and the model is retrained.
None of this promises profit on any single bet or any single night β hockey's variance is real, and a well-calibrated model still loses plenty of individual bets. What it aims to do is find a genuine, sustained statistical edge over hundreds of bets, built on features the model could actually know before the puck dropped. If you want to see the model's current output, our NHL hub shows live ratings and matchups, our model page explains the full methodology across every sport, and our track record shows the settled results, wins and losses included.