The chances on the league page come from a model with six parts: player projections that combine stat forecasts with draft-market information, updating from this season's games, a model of who actually plays, adjustments for each game's opponent and missing teammates, a weekly engine that turns player totals into category and matchup chances, and a simulation of the rest of the season and the playoffs. Every part is checked the same way: its rules are fixed in advance, its numbers are estimated from one past NBA season, and it is judged on other seasons it never saw. Each part is included only because it made the predictions more accurate on all of them. This paper describes each part, the math behind it, and the evidence for it.
Each week two teams compare totals in nine categories: points, rebounds, assists, steals, blocks, three-pointers, turnovers (fewer is better), field-goal percentage and free-throw percentage. Winning at least 5 of the 9 wins the week. The top 8 teams by category record reach a three-round playoff in March, and the top three are paid $150, $90 and $60.
To give a chance for any of this, the model needs to know how much each player produces per game, how many games each player will actually play and start, how uncertain both are, and how those uncertainties add up across a roster, a week and a season.
Figure 1 shows how the parts connect. Each step feeds the next; the output of step 5 is the weekly chance shown on the league page, and step 6 repeats steps 4–5 for every remaining week to produce season-long numbers.
Every part of the model is held to the same standard.
For any chance the model gives, the main score is the Brier score: the average squared gap between the chance and the result.
\(p_i\) is the chance given for matchup \(i\) (between 0 and 1), \(y_i\) is 1 if that team won and 0 if it lost, and \(N\) is the number of matchups. Saying 0.8 for a team that wins costs \((0.8-1)^2 = 0.04\); saying 0.8 for a team that loses costs \(0.64\). Always answering 0.5 scores 0.25, so lower than 0.25 means better than guessing.
Two further checks apply. Calibration: predictions are grouped into ranges (0–10%, 10–20%, …), and in each range with at least 200 predictions the average predicted chance must be within 4 percentage points of how often it happened. Not luck: for small improvements, the matchups are resampled 2,000 times at random (with repeats allowed) and the difference in error is recomputed each time; the middle 95% of those differences must sit entirely below zero. When several ideas are tested at once, the bar rises to the middle 99%, so that the chance of any one passing by luck stays around 5%.
Player projections are scored with a related measure, the CRPS, which rewards forecasts that are both close to the truth and honest about how uncertain they are. Games-played predictions are scored by squared error.
Per-game projections start from ESPN's forecast combined with the player's previous seasons. On their own these miss things that drafters know: an injury that isn't fully healed, a new role on a new team, a coach's plans. That knowledge shows up in ADP (average draft position, where a player typically goes in drafts). The model moves each player's projected value toward the value implied by their ADP:
\(v_{\text{proj}}\) is the value from the stat projection and \(v_{\text{market}}\) is the value of the player the projections rank at that ADP spot. \(\beta\) is how much to trust the market. Because the market is right more often in some seasons than others, \(\beta\) is not a single fixed number: each simulated season draws its own \(\beta\) from a range estimated across past seasons. The adjustment applies only to players with a real ADP; others keep their projection.
EvidencePlayer value predictions with market information had 25% less error on a season held out from every fitting step (7.02 → 5.29). Projection error (CRPS) is 7% lower than projections without market information, averaged over three seasons each left out of its own fit (1.564 → 1.457).
As the season goes on, each player's expected per-game stats blend the projection with what they've actually done:
\(n\) is games played this season. \(K\) is the number of games at which real stats and the projection get equal weight, estimated per stat from past seasons: 9.5 games for points, 13.6 for rebounds, about 24 for steals and blocks. Steals and blocks are noisier from game to game, so real stats need longer to become trustworthy. The model's uncertainty about each rate also shrinks as \(n\) grows.
EvidenceFor rest-of-season points, the blend had 37% less error than the projection alone and 30% less than this season's average alone, on a season not used to estimate \(K\).
Traded players. After a trade, a player's role usually changes, so games with the old team say less about the future. The model finds trades from the NBA schedule (the games a player played stop matching one team's schedule and start matching another's) and counts each pre-trade game at a fraction \(w\) of a normal game:
\(x_g\) is the stat in game \(g\), and \(n_{\text{before}}\), \(n_{\text{after}}\) count games before and after the trade. \(w = 0.2\) was estimated from 2024-25. For players who were never traded nothing changes.
EvidencePer-game error for traded players fell on both test seasons and on 2021-22, with the 99% range entirely below zero. On 2024-25's 36 traded players, the points projection ten games after the trade was closer to what they actually did for 25 of them. Ignoring pre-trade games completely also helped, but not by enough to pass.
Share of games played. Each player's expected share of games blends the preseason estimate with this season's record, using the same formula as §4.2 with \(K = 34\) team games.
Current absences. For a player who is out (listed out by ESPN, or has missed 2 or more games in a row), the model uses how long absences actually last. Across 3,329 absences in three seasons, \(S(m)\) is the share that lasted at least \(m\) games (Figure 2):
With \(S(4) = 0.259\) and \(S(5) = 0.218\), a player who has missed 4 games misses the next one with chance \(0.218 / 0.259 = 84\%\). Absences that have already lasted a while tend to keep going.
The next few games. A healthy player who just played is more likely to play the next game than their season average suggests, and this fades over the following games:
\(\pi\) is the player's usual share of games played and \(\rho = 0.361\), estimated from 2024-25.
Starting. Only 10 players can start per day. On days with more than 10 available players, the model simulates which 10 a manager would start, by position eligibility and value, and counts only those games.
EvidenceFor players currently out, the availability component halves the error of the preseason estimate (0.148 → 0.074) and beats it in every one of three test seasons; for all players it cuts error by 25%.
Opponent and pace. Each game is scaled by what that game's opponent has allowed this season, compared with what the same players usually produce:
\(A_o\) is the total of a stat that players have produced against opponent \(o\), \(E_o\) is what those players were expected to produce, and \(n_o\) is the number of games \(o\) has played. The weight \(n_o/(n_o+K)\) grows from 0 as the season goes on (Figure 4), so a few lopsided games barely count. \(K\) is 20 games for assists, steals and turnovers and 40 for the other stats. Field-goal percentage is adjusted the same way, as a change in makes per attempt; free-throw percentage is not adjusted.
Teammates out. When a teammate who has been playing misses 2 or more games in a row, the players still available get a little more of the team's production:
240 is the player-minutes in one game (5 players × 48 minutes). \(\beta_s\) is estimated per stat: 0.082 for points and 0.075 for assists. With a 30-minute player out, \(x = 0.125\), so a teammate's expected points rise by about 1%.
EvidenceTogether the two adjustments lower matchup error by 0.2–0.4% on both test seasons, with every day of the week no worse. The effect is small because opponent records carry little weight early in the season.
For each team, the engine adds up every remaining game of every player (steps 2–4), keeping both the known results so far this week and the uncertainty in the rest. That gives an expected total \(\mu\) and a variance \(\sigma^2\) in each category. The chance team A wins a counting category is
\(\Phi\) converts a gap measured in standard deviations into a probability: a gap of 0 gives 50%, a gap of one standard deviation gives 84%. \(\kappa = 1.041\) slightly widens the uncertainty so the chances aren't overconfident. For FG% and FT% the same formula is applied to each team's makes above league average per attempt.
The nine category chances then become one matchup chance. Categories are not independent: a strong week usually lifts points, threes and assists together, which makes lopsided results like 7–2 more common than independence would predict. The model adds this extra spread to the number of categories won:
\(\sum_c p_c\) is the expected number of categories won and \(\sum_c p_c(1-p_c)\) is how much that number would vary if categories were independent. \(\psi = 0.35\) adds 35% more variance, the amount estimated from 2024-25. Winning needs 5 of 9, so the cut-off is 4.5.
EvidenceAbout 5% less matchup error than the simpler engine on both test seasons (0.130 → 0.123 and 0.130 → 0.124), with calibration within 2.3 percentage points in every range. A stricter replay that gives the model only what was known on each day (projections plus games played so far) scores the current engine at 0.124 and 0.123, with category chances within 2 percentage points of reality in every range, so the result holds without any hindsight.
The rest of the season is simulated on the league's real schedule: the current week uses the weekly engine above, and each future week uses each team's projected weekly totals and their uncertainty. Uncertainty in a team's weekly total comes from three places:
\(g_i\) is player \(i\)'s expected games that week, \(\mu_i\) and \(\sigma_i^2\) their per-game average and game-to-game variance. The first term is game-to-game noise. The second is uncertainty in how many games they play (\(\gamma = 0.46\), measured). The third: teammates on the same NBA team share a schedule, so their games played rise and fall together (\(\rho_{tm} = 0.25\), measured at 0.245).
Each simulation plays out every remaining week, seeds the top 8 by category record into the playoff bracket, and plays the three playoff rounds using each roster's real games in those weeks. NBA teams with poor records tend to rest players in March, so for the playoff weeks each player's expected games are scaled by their team's record from the previous season:
\(a\) and \(b\) come from how much players' participation dropped in past March windows compared with the rest of the season (0.777 is the usual share of games played). This rule improved predictions of March games in all six seasons it was tested on, but the improvement has not yet been large enough to be statistically confirmed, so it is labelled unvalidated and will be re-tested on this season's March.
Repeating the simulation thousands of times gives each team's chance of each finish, and expected winnings:
EvidenceThe widening factor used for future weeks (\(\kappa = 1.18\)) lowered weekly prediction error on an unseen season (0.2106 → 0.2095), and teams given 75–90% then won about as often as predicted. Season-long results will be scored at the end of 2026-27.
Figure 7 collects the evidence above. Each bar compares the model with and without one component, on that component's own error measure, so bars show how much each part helps but are not directly comparable to each other.
Several plausible effects were tested and are not in the model, either because they made predictions worse or because the data showed no effect.
| Effect | Finding |
|---|---|
| Players in the same game, and nights when the whole league runs hot or cold | ExcludedPlayers facing each other do move together slightly (about 0.03 for points), but it made no difference to matchup chances. |
| Back-to-back games | ExcludedPlayers play the second night of a back-to-back about 95% as often as other games, and assists, steals and blocks dip slightly. Both adjustments helped every season, but too little to pass the 99% bar. |
| Scaling projections down early in the season | ExcludedThere is no early-season effect: players play about 3–8% fewer minutes than ESPN projects in every week of the season, which the projections already absorb. |
| Opponent strength in future weeks | ExcludedHelped at every horizon (1–4 weeks ahead) in every season, but narrowly missed the 99% bar. |
| Re-estimating the weekly engine's two settings on ten times more data | ConfirmedThe current values (\(\kappa = 1.041\), \(\psi = 0.35\)) did slightly better on unseen seasons than the re-estimated ones. |
| Teammates taking each other's stats | No effectOnce games played are accounted for, teammates' stats don't rise and fall together; only their games played do, which §4.6 includes. |
| Letting the market weight be re-estimated for each season separately | ExcludedLess accurate overall than drawing it from a range estimated across seasons. |
| An outside projection system (DARKO) as an extra input | ExcludedMore accurate for single stats, less accurate for overall player value in 2 of 3 seasons. |
| Separating ESPN's points-league ranking from its category ranking | ExcludedFailed when tested forward in time (earlier seasons predicting later ones). |
| Fitted weights for past seasons and an age curve | ExcludedNo improvement over standard weights. |
| Wider ranges for surprise seasons | ExcludedMade typical ranges less accurate while barely helping extreme ones. |
| Correcting games-played projections toward the average | ExcludedOver-corrected; overall error rose. |
| Modelling other managers' draft habits | No effectChanged title chances by less than half a percentage point. |
| Simulating every single day of the season | No effectThe weekly engine already matches real weeks; the daily starting limit is handled directly (§4.3). |
| Assumed settings (injury replacement, learning speed, games per week) | ConfirmedValues measured from real data were close enough to the assumed ones that no prediction changed. |