NBA Free Agent Valuation Model
Personal research project · Jun 2026 – Present
NBA teams commit over $4 billion a year to player salaries, and the most cited valuation framework, Hollinger’s BORD$, is proprietary. This is an open, reproducible alternative that predicts what a player is worth as a percentage of the salary cap, built only from public data.
The interesting problem is censoring. The Collective Bargaining Agreement bounds a salary from both directions: a max contract is capped at 25–35% of the cap depending on experience, and no contract may fall below the veteran minimum. A player worth 40% is paid 35%, so the recorded salary is a ceiling. Train an ordinary model on those rows and it learns to predict the bound. I adapted the Grabit framework (Sigrist & Hirnschall, 2019) from default prediction. Bounded rows get inverse-Mills-ratio gradients that push the latent prediction past the observed bound; unbounded rows keep ordinary squared error.
The subtlety is deciding what counts as censored, and the two sides answer it differently. On the ceiling, not every max-salary player is underpaid, so v8 gates the additional max push on P(max) ≥ 0.52. On the floor the economics invert: a good player choosing a minimum is not censored, while a latent value below the legal minimum is genuinely held at a bound. A final told-route stage handles extensions separately, because their raise limit is contract-specific. The stages stay explicit so latent market value, general CBA bounds, and the realized signing route never get conflated.
Censoring, on both sides
A max contract is a right-censored observation and a veteran minimum is a left-censored one. In each case the recorded salary is a bound. Grabit replaces squared error on those rows with a censored-normal likelihood, and Stage 1 recovers the latent worth that bound was hiding.
What follows divides cleanly on one question. Is this fact knowable before the deal, or only once the route is told? Stage 2 handles the first kind, a max push toward the ceiling where P(max) ≥ 0.52, then the general CBA floor and ceiling, both computable from service years and season alone. Stage 3 handles the second. Once you know a player signed via Early Bird, or as an extension, the CBA imposes limits that no ex-ante feature could have anticipated. Each of those layers only ever lowers a prediction, and the band is re-applied afterwards so nothing escapes its legal bounds.
grad = −σ · φ(z) / Φ(z) where z = (pred − observed) / σ
latent → push (τ = 0.52) → clip[floor, ceiling]
→ signing offset → mechanism cap → extension cap → re-clipThe latent stays the headline market value; everything after it is the CBA. Only the four eligibility routes (Bird, Cap Space, Early Bird, Non-Bird) receive a signing offset. MLE, BAE, and Minimum are determined by the contract amount itself, so conditioning on them would read the target.
Results
Selection uses pooled 5-fold GroupKFold over 10 seeds, grouped so a player never appears in both train and test. Forecasting is measured separately by rolling origin (train on every season before T, score T), because that is what the inference pipeline actually does.
| Baseline XGBoost | Grabit v8.14x | |
|---|---|---|
| Selection CV R² (n = 877) | 0.809 | 0.856 |
| Selection CV MAE | $3.29M | $2.60M |
| CV R² (2024–26, n = 316) | 0.839 | 0.881 |
| Forward R² (rolling origin) | 0.811 | 0.858 |
| Calibration slope | 1.041 | 0.966 |
Forward R² carries a 95% interval of [0.80, 0.90]. A baseline ladder keeps the absolute number honest: minutes per game alone reaches 0.605 against the full model’s 0.856, so the other twenty features buy the last 0.252. A 15%-of-players confirmation split, held out of every selection decision, scores 0.820 against 0.862 on the selection pool.
Judging a targeted intervention where it acts
A pooled score averages each targeted intervention over rows it never touches, so v8 keeps a per-zone scorecard beside the headline metric. In the max-censored zone, 65 of 69 rows improve and MAE falls by $3.70M. In the floor zone, MAE falls by $0.75M across 259 rows. Stage 3 moves 13 extension rows, improves 11, and lowers extension-zone MAE by $0.48M. The full champion still clears the pooled selection check: +0.023 R² over the clip-only arm, while the signing offset alone contributes +0.015. Each intervention is measured where its arithmetic can actually change a prediction.
What a told route can fix
Grouping errors by the CBA mechanism a contract was signed under exposes structure the performance features cannot see. Teams holding Bird Rights may exceed the cap to keep their own player, and pay a retention premium with no feature behind it. Minimum and mid-level deals run the other way.
The tempting inference, model the mechanism and recover the error, does not work. Mechanism is itself a function of the same player features, so a fold-honest P(mechanism | x) lowers CV R² by 0.0073. Even a leaky oracle handing the model the realized mechanism gains only +0.0137. The residual gap is the spread of a genuinely bimodal conditional: given the same inputs a player either lands a market deal or takes a minimum, and the conditional mean sits correctly between the two modes.
The resolution is a distinction. A signing route may correct an output without entering the input. Once a deal is done its route is a fact, and Stage 3 adds a per-route constant to the four routes whose eligibility is decided by the team. The table below is what that leaves. The corrected routes now sit within $0.75M of zero, while Minimum still carries +$1.65M because it is not corrected: a minimum deal is defined by its amount, and conditioning on it would read the target.
| Signing mechanism | n | Residual bias | Offset applied |
|---|---|---|---|
| Bird Rights | 305 | −$0.38M | yes |
| Cap Space | 80 | −$0.43M | yes |
| Early Bird | 53 | −$0.72M | yes |
| Non-Bird | 14 | +$0.19M | yes |
| MLE | 136 | +$0.52M | no |
| Minimum | 259 | +$1.65M | no |
Negative bias means the model underprices. Sign & Trade is reclassified as Bird Rights (the originating team must hold Bird or Early Bird rights). Mechanism is held out of the feature set, so these residuals stay usable as a diagnostic.
A correction worth stating plainly
An earlier version of this work reported that the model underpaid stars by up to $6M, read off residuals binned by actual salary. That gradient is an artifact. Binning by a noisy target produces it even for a perfectly calibrated model, because the bin edges select on the noise. Binned by predicted value instead, the v8 champion’s calibration slope is 0.966, with +$1.47M bias remaining in the 25%+ band. Fold-honest recalibration still fails to improve the model.
The same trap has a second form, met while comparing two models: cutting segments on either model’s own predictions lets band composition shift between them and manufactures a difference that was never there. Model comparisons here assign rows to segments using something neither model produced.
Highlights
- Adapted Grabit (gradient tree-boosted Tobit) from credit-risk modeling to NBA salaries, then composed its two-sided censoring with a probability-gated max push 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



