How it works
Methodology
How each forecast is made, when it is saved, and how the model is tested. Written from the project's code and stored data.
Objective
Forecast the final score of every NBA game before it starts, turn that into a winner and a win probability, and grade every forecast against the result. The model uses basketball information only. Betting lines are not inputs; where they are stored, they serve only as an outside benchmark.
Data
Schedules, final scores and team and player box scores come from NBA Stats (stats.nba.com). Player availability comes from the official NBA injury report, which the league publishes as a PDF several times a day; the project parses and stores each report it uses. All data lives in a local PostgreSQL database. This website is built from a read-only export of it.
| Season | Final games | Box scores | Days with an injury report |
|---|---|---|---|
| 2021-22 | 1,317 | 1,317 | 210 |
| 2022-23 | 1,314 | 1,314 | 209 |
| 2023-24 | 1,312 | 1,312 | 204 |
| 2024-25 | 1,314 | 1,314 | 210 |
| 2025-26 | 1,314 | 1,314 | 207 |
| 2026-27 | 146 | 146 | 19 |
Model inputs
The model receives 21 inputs for each game, all known before tip-off. Box-score statistics from the game itself are never inputs; they are used only to build rolling averages for later games and to grade the forecast.
| Group | Inputs |
|---|---|
| Team strength | Elo rating for each team (margin-of-victory Elo, starting October 2021), the Elo gap including a fixed home-court bonus, each team's season-to-date scoring margin blended with last season early in the year, and games played. |
| Recent form | Offensive and defensive rating (points scored and allowed per 100 possessions) over each team's last 10 games, and the two teams' combined pace. |
| Availability | Points per game of each team's regular rotation listed Out or Doubtful on the official NBA injury report. |
| Rest | Days since each team's previous game and whether it is the second night of a back-to-back. |
| Playoff series | Home team's series wins, whether the game is an elimination game, and a series-pressure measure. |
A rotation player is one who played in at least two of the team's last ten games this season, averaging 15 or more minutes. Players listed Questionable are currently treated as available; the Notebook looks at how often they actually play.
Score forecasts
Two gradient-boosted regression models (scikit-learn, 150 shallow trees each) predict the home score and the away score. The forecast margin and total are derived from those two numbers. Training weights recent games more heavily (weights decay with a time constant of 550 days), and playoff games count three times as much because they make up only a small share of the history.
The production models are trained on every completed game since October 2021. A player-level model also exists in the project, but blending it in made forecasts worse in every test, so it does not affect the published scores.
Win probability
The win probability is a logistic curve applied to the forecast margin: larger projected margins map to higher probabilities. The curve used for live forecasts was fitted on an earlier, small set of graded games. The walk-forward forecasts used their own fitted curve. Refitting the live curve on the full record is planned.
The pipeline also assigns a High, Medium or Low confidence label based mostly on the size of the projected margin. That label is a rule of thumb, not a tested probability, so this site shows win probabilities instead.
Timing and storage
The daily pipeline saves each forecast with the time it was made. The first forecast saved for a game before tip-off becomes the official one; a rerun can replace it only within the last hour before tip-off. A forecast is never revised after the game.
Every game on this site has one forecast. Where the daily pipeline saved a forecast for a game, that is the one used (65 games). Every other game uses the walk-forward forecast described below (5,325 games). Both are graded the same way and counted together in every record. 168 forecasts from an older after-the-fact fill, which may have seen information from the games themselves, are left out entirely.
Evaluation
The main test is a walk-forward backtest. Each season from 2022-23 to 2026-27 was predicted by a model trained only on the seasons before it, through the same code path as the daily forecasts, with the injury report archived for that day (the 5:30 PM ET report) and no betting lines. That makes it an estimate of how the method does on games it has not seen. The current season is included as it is played: its games are predicted after the fact by a model trained on all completed seasons, which has not seen any game from this season.
Most of the record is therefore retrospective: those forecasts were produced after the games, by models that had not seen them, rather than published beforehand. The current production model is retrained on all history and is used for upcoming games.
Ten games in the source data list the same team as both home and away. They cannot be graded and are left out of every figure on this site.
Explaining a forecast
Game pages include an Inside the Forecast section. It always shows the inputs saved with the forecast. Where possible it also shows how each input moved the projected home margin, using SHAP values from the two score models (TreeSHAP, which is exact for tree models): an input's effect on the margin is its effect on the home score minus its effect on the away score, and the model's average output plus all effects equals the projected margin. These are points of margin, not percentage points of win probability, which comes from a separate curve.
An explanation is shown only when the model that made the forecast can be checked. Stored scores are whole numbers, so a model version counts as verified for a group of forecasts when at least 90% of them reproduce both stored scores exactly; each forecast in a verified group is then explained if its margin is within the rounding bound. Current status:
- production model saved 2026-11-07: 14 forecasts, verified (100% reproduce exactly), 14 explained.
- earlier production versions: 51 forecasts, the model files that made them no longer exist.
- walk-forward 2022-23: 1,314 forecasts, not verified (12% reproduce, about what a different model matches by chance).
- walk-forward 2023-24: 1,312 forecasts, not verified (17% reproduce, about what a different model matches by chance).
- walk-forward 2024-25: 1,314 forecasts, not verified (17% reproduce, about what a different model matches by chance).
- walk-forward 2025-26: 1,291 forecasts, not verified (19% reproduce, about what a different model matches by chance).
- walk-forward 2026-27: 104 forecasts, verified (94% reproduce exactly), 104 explained.
Walk-forward models are rebuilt in memory with the same training code for this check and are never saved. Forecasts that fail it keep their saved inputs, labelled as unexplained, rather than being explained with a model that did not make them. These explanations describe how the model used its inputs. They are not evidence of what caused a result.
From box scores to inputs
Game pages also include Built from the box scores, which traces each input back to the raw results and box-score stats it was calculated from:
- Elo is replayed from every final score since October 2021 with the production rating code, and shown as last season's finish, the carried-over start, and the change from each game.
- Season margin is points scored and allowed this season, mixed with last season's margin for the first 20 games.
- Offensive and defensive ratings are averages of the last 10 games' points per 100 possessions. They are split into Dean Oliver's four factors: shooting (eFG% = (FG + 0.5 × 3P) / FGA), turnovers (TOV / (FGA + 0.44 × FTA + TOV)), offensive rebounding (OREB / (OREB + opponent DREB)) and free throws (FTM / FGA). A defensive rating is the opponents' offensive rating, so it uses the opponents' four factors.
- Missing points lists the rotation players on the official injury report that reproduces the stored value, and rest shows the previous game.
The split uses one linear fit of a game's offensive rating on its four factors, over 12,212 team-games. It explains 94.7% of the game-to-game variation (typical miss 2.7 points per 100 possessions). Because a rating is an average and the fit is linear, a team's gap from the league average splits exactly into one part per factor plus an "everything else" remainder, which is shown rather than hidden.
| Factor | Rating points per 1 point of the factor | Typical swing, offense | Typical swing, defense |
|---|---|---|---|
| Shooting (eFG%) | +1.51 | ±4.1 | ±4.2 |
| Turnovers (TOV%) | −1.42 | ±2.0 | ±2.1 |
| Offensive rebounding (OREB%) | +0.45 | ±1.8 | ±1.7 |
| Free throws (FTM per FGA) | 0.29 per 0.01 | ±0.9 | ±0.8 |
| Everything else | ±1.4 | ±1.3 |
"Typical swing" is the standard deviation of each factor's part across every 10-game rating shown on a game page, in points per 100 possessions: how far that stat usually pushes a team's rating above or below average. Shooting matters most by a wide margin, then turnovers and offensive rebounding; free-throw rate moves ratings least. Offensive rebounds are missing from the source data for many 2024-25 and 2025-26 games; where any of the 10 games lacks them, rebounding is not split out and its effect stays in "everything else".
A piece of the breakdown is shown only when rebuilding it gives exactly the value the model received. Current status, over every team input on a game page:
- Elo: 1,054 of 1,054 reproduced.
- Season margin: 1,054 of 1,054 reproduced.
- Offensive rating: 1,048 of 1,054 reproduced.
- Defensive rating: 1,047 of 1,054 reproduced.
- Pace (per game): 513 of 527 reproduced.
- Missing points: 1,054 of 1,054 reproduced.
- Rest: 1,054 of 1,054 reproduced.
Inputs that don't reproduce were usually saved from data that has since changed, for example a forecast made before the previous night's game was recorded. They keep their stored value without a breakdown. The model itself never sees these raw stats, only the inputs built from them.
Metrics
- Winners picked: share of games where the team with the higher projected score won.
- Margin error: mean absolute difference between forecast and actual home margin.
- Total error: mean absolute difference between forecast and actual combined score.
- Brier score: mean of (home win probability − home win as 1 or 0)². Always forecasting 50% scores 0.25.
- Calibration: games grouped by the forecast's confidence in its pick, comparing the average forecast with the share of picks that won.
- Expected wins (team pages): the sum of a team's pregame win probabilities over a sample of games.
Limitations
- Most graded forecasts were produced after the games by models that had not seen them, not published in advance.
- Forecast margins are much narrower than real margins, so lopsided results are rarely forecast as lopsided.
- Questionable players are treated as available, and late scratches after the forecast is saved are not captured.
- A missing player is measured by his points per game, not his overall impact on the floor.
- The site shows what the model received, not how much each input moved a particular forecast.
- The site is rebuilt by hand. This edition was exported Nov 9, 2026, 12:54 AM ET.
Ongoing work
Ideas are tested with the same walk-forward backtest before they reach the live model. Current candidates: forecasting margin and total directly instead of two team scores, treating Questionable status as a probability, measuring missing players by on/off impact, and refitting the win-probability curve on the full record. The Notebook records each result.