00The problem
A max contract hides what a player is worth
The CBA bounds every salary from both sides. A max contract is capped at 25, 30 or 35% of the cap depending on service and awards, and nothing can be signed below the veteran minimum. A player worth 40% of the cap is paid 35%, so the recorded salary is a bound, not a price. Train an ordinary regression on those rows and it learns to predict the bound.
The chart holds every negotiated first-year contract the model trains on. Height is what the player was paid; position across is the Stage 1 latent value, what the model thinks an open market with no CBA would pay. In the middle the two agree and the points follow the diagonal. At the top, max deals stack on their tier ceilings while the latent keeps going. At the bottom, minimum deals stack on the floor, and their latents split: about half sit below it, held up by the rule, and the rest spread to the right, players the market would have paid more who took the minimum anyway. That second group is not censoring, and it comes back under Limits.
The 68 max-zone rows as a table
| Player | Season | Paid | Latent |
|---|---|---|---|
| Kawhi Leonard | 2018–19 | 30.0% | 46.8% |
| Kyrie Irving | 2018–19 | 29.1% | 42.5% |
| Giannis Antetokounmpo | 2020–21 | 35.0% | 39.8% |
| Kemba Walker | 2018–19 | 30.0% | 39.8% |
| Joel Embiid | 2022–23 | 35.0% | 39.4% |
| Nikola Jokić | 2022–23 | 35.0% | 38.6% |
| Jimmy Butler | 2018–19 | 30.0% | 37.6% |
| Klay Thompson | 2018–19 | 30.0% | 37.0% |
| Anthony Davis | 2019–20 | 30.0% | 36.6% |
| James Harden | 2018–19 | 35.0% | 36.2% |
| Kevin Durant | 2018–19 | 34.1% | 36.1% |
| Devin Booker | 2023–24 | 35.0% | 35.7% |
| Jaylen Brown | 2023–24 | 35.0% | 35.7% |
| Karl-Anthony Towns | 2018–19 | 25.0% | 35.1% |
| Damian Lillard | 2020–21 | 35.0% | 34.2% |
| Jayson Tatum | 2024–25 | 35.0% | 34.1% |
| Evan Mobley | 2024–25 | 30.0% | 34.1% |
| Tobias Harris | 2018–19 | 30.0% | 33.8% |
| Anthony Davis | 2024–25 | 35.0% | 33.8% |
| Tyrese Haliburton | 2023–24 | 30.0% | 33.5% |
| Luka Dončić | 2021–22 | 30.0% | 33.2% |
| Jayson Tatum | 2020–21 | 25.0% | 33.0% |
| Trae Young | 2021–22 | 30.0% | 33.0% |
| Bradley Beal | 2021–22 | 35.0% | 32.9% |
| Cade Cunningham | 2024–25 | 30.0% | 32.8% |
| Lauri Markkanen | 2023–24 | 30.0% | 32.7% |
| Jimmy Butler | 2022–23 | 33.2% | 32.6% |
| Zach LaVine | 2021–22 | 30.0% | 32.5% |
| Anthony Edwards | 2023–24 | 30.0% | 32.5% |
| Karl-Anthony Towns | 2023–24 | 35.0% | 31.7% |
| Zion Williamson | 2022–23 | 25.0% | 31.6% |
| Devin Booker | 2018–19 | 25.0% | 31.2% |
| Bam Adebayo | 2025–26 | 30.0% | 31.2% |
| Bradley Beal | 2020–21 | 30.0% | 30.9% |
| Domantas Sabonis | 2023–24 | 27.9% | 30.8% |
| Pascal Siakam | 2019–20 | 28.0% | 30.6% |
| Donovan Mitchell | 2024–25 | 30.0% | 30.5% |
| Jamal Murray | 2024–25 | 30.0% | 30.0% |
| Khris Middleton | 2018–19 | 28.0% | 30.0% |
| Franz Wagner | 2024–25 | 25.0% | 30.0% |
| Fred VanVleet | 2022–23 | 30.0% | 29.9% |
| Ja Morant | 2022–23 | 25.0% | 29.1% |
| Ben Simmons | 2019–20 | 28.0% | 29.0% |
| D'Angelo Russell | 2018–19 | 25.0% | 28.4% |
| Jamal Murray | 2019–20 | 25.0% | 27.6% |
| Tyrese Maxey | 2023–24 | 25.0% | 27.6% |
| LaMelo Ball | 2023–24 | 25.0% | 27.3% |
| Brandon Ingram | 2019–20 | 25.0% | 27.3% |
| Shai Gilgeous-Alexander | 2021–22 | 25.0% | 27.1% |
| Bam Adebayo | 2020–21 | 25.0% | 27.0% |
| Desmond Bane | 2023–24 | 24.2% | 27.0% |
| Donovan Mitchell | 2020–21 | 25.0% | 27.0% |
| Austin Reaves | 2025–26 | 25.0% | 26.7% |
| Darius Garland | 2022–23 | 25.0% | 26.2% |
| De'Aaron Fox | 2025–26 | 30.0% | 26.2% |
| Chet Holmgren | 2025–26 | 25.0% | 26.2% |
| Deandre Ayton | 2021–22 | 25.0% | 26.1% |
| Jalen Suggs | 2024–25 | 22.6% | 25.8% |
| Scottie Barnes | 2024–25 | 25.0% | 25.4% |
| De'Aaron Fox | 2020–21 | 25.0% | 25.3% |
| Jaren Jackson Jr. | 2021–22 | 23.4% | 25.1% |
| Paolo Banchero | 2025–26 | 25.0% | 24.8% |
| Immanuel Quickley | 2023–24 | 23.1% | 24.7% |
| Jalen Williams | 2025–26 | 25.0% | 23.5% |
| Jaren Jackson Jr. | 2025–26 | 29.7% | 22.9% |
| Myles Turner | 2021–22 | 28.0% | 22.3% |
| Michael Porter Jr. | 2021–22 | 25.0% | 22.1% |
| Trae Young | 2025–26 | 30.0% | 16.4% |
01U1 · Data
Most salary rows are not market prices
- Basketball Referencesalaries, height, awards, playoff minutes
- Spotraccap hits, signing mechanism, dated contract spans, waivers
- nbarapm.comDARKO, LEBRON, LAKER, usage and box-score rates
- The CBAcap by season, max tiers, minimum scale, raise limits
Salaries come from Basketball Reference and Spotrac cap hits, the impact metrics DARKO, LEBRON and LAKER from nbarapm.com, and the bounds from the CBA itself. Stats lag salary by one season on purpose: a contract signed in July is priced on the season that just ended.
Years two and later of a contract are CBA escalators, rookie deals are slotted by draft position, and partial-season pay depends on the signing date. None of those is a price the market set, so each filter below removes one of them. Thresholds are shares of the cap, never dollar figures, because the minimum and the max move with the cap.
- Salaries × impact metricsjoined on player and season3,113
- First contract year onlyyears 2+ are 5–8% CBA escalators1,808 −1,305
- Rookie scale removedslotted by draft position, not priced1,556 −252
- Prorated pay removedunder 1.2% of the cap: 10-day and mid-season deals1,297 −259
- Provable mislabels removedpaid above the tier max, so a later year of an older deal1,291 −6
- Continuations removedspan, escalator step and FA list all agree1,172 −119
- Dated contract spansa dated deal that started earlier covers the row949 −223
02U2 · Features
21 features, kept by ablation from more than 40
A feature must exist before the contract is signed and must not be derived from the salary. Each one entered through the same gate: rerun cross-validation on identical folds with and without it, and require the fold-by-fold improvement to clear t > 2.
The bars show how far each feature moves a prediction on average, as mean absolute SHAP in dollars over the training contracts. Minutes per game dominates on its own; the market trajectory and the Kalman quality estimate are the two engineered features that come next.
Role & workload
- Minutes per game$3.11M
- Availability, 3 years$0.66M
- Usage rate$0.55M
- Playoff minutes swing$0.41M
- Assist rate$0.10M
Market history
- Market trajectory$2.77M
- Recently waived$0.18M
- Minutes × waived$0.08M
Stage 1 linear terms
- P(max) term$0.54M
- Buyout term$0.15M
Performance
- Kalman quality$0.99M
- DARKO DPM$0.66M
- DARKO offence − defence$0.36M
- LAKER$0.22M
- LAKER offence − defence$0.16M
- LEBRON$0.14M
- LEBRON offence − defence$0.12M
Age & body
- Age$0.71M
- Age²$0.25M
- Height$0.22M
Pedigree & era
- Award score$0.18M
- Draft pick$0.06M
- CBA era$0.02M
Tried and rejected
Redundantalready carried by a kept feature
- BPM, TS+, 3-point rates< +0.001
- Assist-to-turnover+0.0001
- All-NBA count+0.0001
- Lottery / top-5 pick< +0.001
- Injury reports+0.0002
- Experience years−0.0004
Team proxypermuting within a team keeps the effect
- Win %, made playoffs+0.0004
- Playoff games playedteam signal
- Team cap space+0.0008
- Franchise value+0.0001
Reads the targetderived from the salary it predicts
- Vet-minimum / MLE-range flagsleak
- P(Minimum | x), P(Bird | x)−0.0073
- Agent portfolio−0.002 to −0.005
No signalfailed the paired gate
- Contract year+0.0009
- Stayed with team+0.0006
- Height × age+0.0004
- Minutes × impact−0.0018
- Spotrac injury burden−0.0024
02bU2 · Two Kalman filters
One filter for how good he is, one for what the market last said
Quality. DARKO, LEBRON and LAKER are three noisy gauges of one hidden quantity. A Kalman filter fuses them per player, season by season, weighting each gauge by its measurement noise and adding an age drift: improvement through the early twenties, decline from 25 that steepens after 28. Every parameter comes from the metrics, never from salary. A moving average over the same gauges recovers only half the gain, so the weighting and the drift are doing the work.
Market. The second filter anchors at the last price the market actually set for a player, an in-season signing included, and walks forward through the seasons in between. A first-pass model prices those seasons, clipped into the legal range. At inference that takes two passes: the base model prices the intermediate seasons, the filter runs, then the final model trains on its output.
A second-round pick who became a star. The quality estimate rises a step behind the gauges, and the market trajectory re-anchors at both of his negotiated deals.
Weight per update ∝ 1 / noise
- DARKO50%R 0.138
- LEBRON34%R 0.203
- LAKER16%R 0.447
03U3 · Stage 1 · Grabit
A loss that knows which salaries are bounds
Stage 1 is XGBoost, 500 trees of depth 4, with the loss replaced on censored rows. Grabit (Sigrist & Hirnschall, 2019) comes from credit-risk modelling and uses a censored-normal likelihood. For a max contract the observed salary is only a lower bound on the latent value, so the gradient never drags the prediction down to it. For a minimum deal the observation is an upper bound, and the gradient is mirrored.
Move the prediction. Squared error always pulls it back to the observed salary. Grabit pushes while the prediction sits at or below the bound and lets go above it; σ = 2% of the cap sets how wide that hand-off is.
The trees are not the whole of Stage 1. Two linear terms enter as the base margin, so the trees fit what is left around them: one for players bought out of money they were still owed, one for players likely to sign a max. Each coefficient is a censored-likelihood fit on out-of-fold predictions inside every training slice.
- Squared error
- +3.0 pts
- back toward the observed salary
- Grabit
- +3.9 pts
- the bound is a floor on his worth
- Not every row at a bound is bound by it, so censoring is gated on a baseline model agreeing.
- Max side: the baseline must reach 55% of the player’s ceiling. That keeps declining stars on old max deals, which the market would not repeat, from being treated as underpaid.
- Floor side: the baseline must be at most twice the observed pay. A good veteran on a minimum made a choice; only a player priced below the minimum hit a constraint.
max side: pull = +σ·φ(z)/Φ(z) z = (pred − obs)/σ floor side: pull = −σ·φ(z)/Φ(z) z = (obs − pred)/σ otherwise: pull = obs − pred
In the export’s fit, 57 max rows and 137 floor rows are censored. Every other row keeps plain squared error.
Two linear terms in the base margin
trees(x)P(max) term +6.2 ptsbuyout term 0.0 ptslatent = trees(x) + 6.2 pts
latent = trees(x)
+ β_w · owed · kf
+ β_max · P(max) · max(ceiling − kf, 0)- Buyout term: a player bought out of money he was owed gives up a share of his market trajectory kf, because the contract that anchors kf overstates his price. A split on a waiver flag applied a near-uniform discount, too weak for bought-out stars. Plain waivers stay with the trees.
- P(max) term: lifts a likely-max player part of the way from his market trajectory to his ceiling. A training row’s P(max) comes from a classifier that never saw that player, because route labels come from the target. Waived rows get P(max) = 0.
- The classifier reads 36 columns stated explicitly and frozen. When it inherited the regression features, a column with no route signal flipped borderline rows from seed to seed.
04U4 · Stage 2 · CBA bounds
Stage 2 is a clipper circuit
Stage 2 maps the latent value to a legal price: nothing above the player’s tier ceiling, nothing below the league floor. Both bounds are knowable before the market opens, the ceiling from service years and awards, the floor from season and experience. The clip never reads the observed salary.
Until v6.3.0 Stage 2 also pushed likely-max players toward the ceiling after the fit, behind a P(max) ≥ 0.52 gate, because a clip can only cap from above. The P(max) term in Stage 1 replaced it. The lift is now fitted, the censored loss and the trees see it, and a knife-edge gate no longer decides who gets it.
latent 24.0%inside the bounds24.0%
price = clip(latent, floor, ceiling)
- Ceiling: 25% of the cap with 0–6 years of service, 30% with 7–9, 35% with 10 or more. An All-NBA, MVP or DPOY award lifts a young player to 30% (Rose rule) or a 7–9 year veteran to 35% (supermax).
- A veteran’s max is also at least 105% of his previous salary, so the ceiling carries a no-decrease floor. An audit of every row paid above its ceiling brought the count from 13 to zero.
- Floor: the veteran minimum for the season and service bucket, recovered from the data’s own mass points rather than maintained CBA tables.
What replacing the push changed
MAE by zone, $M
- Max zone n 572.12 → 2.02
- Floor zone n 2241.84 → 1.77
- Everyone else n 4763.02 → 2.97
Named rows, error in $M
- James Harden 2022+$11.6M → +$3.9M
- Devin Booker 2019−$6.8M → −$2.6M
- D’Angelo Russell 2019−$2.8M → −$1.1M
- DeMar DeRozan 2024+$14.8M → +$16.3M
05U5 · Stage 3 · Told route
What the signing route adds, once the deal is done
Some limits exist only once you know how a deal was signed. An extension’s first year is capped at 140% of prior salary (120% before the 2023 CBA) or the same multiple of the league’s average salary, whichever is greater. Early Bird and Non-Bird deals have their own ceilings. A team holding a player’s Bird rights pays a retention premium that no performance feature can see.
Stage 3 applies these in a fixed order: a per-route offset, the mechanism cap, the extension cap, then the [floor, ceiling] clip again. Every cap can only lower a prediction, and the final clip means no correction can price a contract the CBA would not allow.
Own team re-signs him over the cap. Gets the largest offset: a retention premium the features cannot see.
What is left after Stage 3
Eligibility routes · offset applied
- Bird Rights n 307−$1.89M → −$0.34M
- Cap Space n 77−$1.22M → −$0.38M
- Early Bird n 53−$1.67M → −$0.54M
- Non-Bird n 14+$0.67M → +$0.49M
Exception routes · left alone, the label reads the price
- MLE n 139+$0.39M
- Minimum n 265+$1.63M
06Evaluation
Selection and forecasting are scored separately
Decisions use repeated grouped CV: five folds of players, never rows, so one player’s contracts never sit on both sides of a split. Each of ten seeds deals the players by a fixed hash of its own partition, so adding a row never moves anyone else. Deltas are paired by fold, because fold-to-fold spread dwarfs seed-to-seed spread.
Forecasting is measured on its own by rolling origin, because that is what inference does: train on every season before T, predict T. A 15% confirmation split of players stays out of every selection decision.
A · Selection: repeated grouped CV
B · Forecasting: rolling origin
What each part of the pipeline buys
- Stage 1 + clipv6.0.0 arm0.8303
- + push, route not toldv6.0.0 arm0.8280 −0.0023
- + extension capv6.0.0 arm0.8357 +0.0077
- + route offsets, mechanism capv6.0.00.8509 +0.0152
- + buyout termv6.2.00.8612 +0.0103
- P(max) term replaces the pushv6.3.10.8679 +0.0067
- Forward R², rolling origin
- 0.859
- CV MAE
- $2.50M
- Calibration slope
- 0.978
A correction worth stating plainly
An earlier version of this work said the model underpaid stars by up to $6M, read from residuals binned by actual salary. That gradient appears even for a perfectly calibrated model: binning on a noisy target selects on the noise, so the top bin is full of players whose salary came in high. Binned by prediction, it disappears.
The same trap has a second form when comparing two models. Cutting segments on either model’s predictions lets the band composition shift between them, so segments are assigned by something neither model produced.
- under 2% of cap+$1.76M
- 8–15% of cap−$2.60M
- 25% and above−$5.71M
Looks like the model underpays stars by almost $6M.
07Limits
What the model cannot see
The remaining error is mostly not a modelling problem. It is information the public data does not hold.
Signing mechanism cannot be a feature
A fold-honest P(mechanism | x) lowers CV R² by 0.0073, and even a leaky oracle given the realized mechanism gains only +0.0137. The trees already extract what the features say about it. A route can correct an output after the deal; it cannot enter as an input before it.
The target is bimodal
With the same inputs, a mid-market player either signs a market deal or takes a minimum. The conditional mean sits correctly between the two modes and is wrong for both. A distributional output, P(minimum) beside a market value, would answer the real question better, and R² could not measure it.
Information outside the box score
Team cap room that summer, positional supply, how the last contract ended, and tracking data are all invisible to it. Rookie max extensions are priced on potential the stats do not show yet. Five route-mixture designs tried to recover this error from the existing features. Only the extension survived, and only because it applies a legal ceiling.
In short
- Adapted Grabit (gradient tree-boosted Tobit) from credit-risk modeling to NBA salaries, then composed its two-sided censoring with fitted P(max) and buyout terms and a told-route extension cap so each stage answers one distinct CBA question
- Built the full pipeline end to end, scraping Basketball Reference, Spotrac and nbarapm, engineering 21 features from 40+ candidates by ablation, and a four-layer evaluation protocol that separates structural estimation from forecasting and selection from confirmation
- Established a characterized negative result: the residual pattern by signing mechanism is real but unrecoverable, with the ceiling on any fix measured empirically
Every prediction on the board breaks down into per-feature dollar attributions, and the what-if tab reruns the trees in your browser.
Open the valuation board →Diagnostics from the export
Export v6.3.1, generated 2026-10-01. Interactive figures read the same files as the board.



