NBA 自由球员估值模型
个人研究项目 · 2026 年 6 月至今
NBA 球队每年花超过 40 亿美元在球员薪资上,而最常被引用的估值框架 Hollinger 的 BORD$ 是不公开的。这个项目是一个开源、可复现的替代方案, 利用公开数据预测每位球员值工资帽的百分之几。
这个项目有意思的地方在于截断问题。CBA 从两个方向限制薪资:顶薪根据球龄和表现封顶在 工资帽的 25–35%,底薪也有最低线。一个实际上可能值 40% 的球员只能拿 35%, 所以顶薪的实际薪资是未知的。如果只用这些数据训练机器学习模型,模型只会学到顶薪的上限。 我把信用风险领域的 Grabit 框架(Sigrist & Hirnschall, 2019)适配过来: 被截断的行用逆 Mills 比的梯度把潜在预测推过观测值的边界;未被截断的行保持普通的平方误差。
难点在于判断哪些行算「被截断」,而且两边的判断逻辑不同。在上限这边, 不是每个拿顶薪的球员都被工资帽影响了,所以模型只在 P(顶薪) ≥ 0.52 时才用grabit做额外推升。 在下限这边逻辑反过来:好球员选择拿底薪不算被截断(甚至可以说是反过来的,但是情况太复杂暂时不处理),但潜在价值低于底薪线的确实被托住了。 最后还有一个「已知签约路径」阶段单独处理续约,因为续约的涨幅上限和签约方式有关(鸟权无视工资帽签约合同普遍更大,提前续约有不同的上限等)。 整体模型通过真实价值,cba下的价值,已知签约方式后的价值,三个阶段共同预测球员在市场上会拿到的合同。
双向截断
顶薪合同是右截断观测,老将底薪是左截断观测。两种情况下,实际薪资都是一个边界。 Grabit 用截断正态似然替换这些行上的平方误差,Stage 1 恢复出被边界遮住的真实身价。
后面两个阶段有一个清晰的分界线:这个信息在签约之前就知道,还是签完才知道? Stage 2 处理前者:在 P(顶薪) ≥ 0.52 时推向上限,然后用球龄和赛季就能算出的 CBA 地板和天花板做裁剪。Stage 3 处理后者:一旦知道球员是通过 Early Bird 签的, 或者是续约,CBA 就会施加事前特征无法预见的限制。 每个阶段都只会把预测往下调,最后再重新套一次合法范围。
grad = −σ · φ(z) / Φ(z) 其中 z = (pred − observed) / σ
latent → push (τ = 0.52) → clip[地板, 天花板]
→ 签约偏移 → 机制上限 → 续约上限 → 重新裁剪stage1 得到的真实价值 是核心市场估值;后面所有步骤都是 CBA 规则。 只有四种资格路径(Bird、薪资空间、Early Bird、Non-Bird)有签约偏移。 MLE、BAE 和底薪由合同金额本身决定,以它们为条件会泄漏目标变量。
结果
选择用的是 10 种随机种子下的 5 折 GroupKFold 汇总,按球员分组确保同一球员不会同时出现在训练和测试集。 前瞻表现单独用滚动起点法衡量(用 T 赛季之前的所有赛季训练,预测 T), 因为这才是推理管线真正在做的事情。
| 基线 XGBoost | Grabit v8.15x | |
|---|---|---|
| 选择 CV R²(n = 877) | 0.809 | 0.856 |
| 选择 CV MAE | $3.29M | $2.60M |
| CV R²(2024–26, n = 316) | 0.839 | 0.881 |
| 前瞻 R²(滚动起点) | 0.811 | 0.858 |
| 校准斜率 | 1.041 | 0.966 |
前瞻 R² 的 95% 置信区间为 [0.80, 0.90]。 基线阶梯提供参照:仅用场均上场时间可以达到 0.605, 完整模型的 0.856 说明其余二十项特征贡献了最后的 0.252。 15% 球员的确认分割(从未参与任何选择决策)得分 0.820, 对比选择池的 0.862。
在起作用的地方衡量干预效果
汇总指标会把每个定向干预的效果平均到它根本没碰过的行上, 所以 v8 在总体指标旁边维护了一个分区计分卡。 在顶薪截断区,69 行中 65 行改善,MAE 下降 $3.70M。 在底薪区,259 行的 MAE 下降 $0.75M。 Stage 3 移动了 13 个续约行,改善了 11 个,续约区 MAE 下降 $0.48M。 完整冠军模型依然通过了汇总选择检查:比仅裁剪模型 R² 高 +0.023, 签约偏移单独贡献了 +0.015。 每个干预都在它的算术能够改变预测的地方被衡量。
签约路径能修正什么
按 CBA 签约机制分组残差,能暴露出表现特征看不到的结构。 持有 Bird 权的球队可以超帽留人,支付一个特征无法捕捉的留队溢价。 底薪和中产特例的方向相反。
很自然的想法是:既然知道了机制,就把它建模进去恢复残差。这行不通。 机制本身就是球员特征的函数,所以把折叠诚实的 P(机制 | x) 喂给模型, CV R² 反而降了 0.0073。即使用泄漏的 oracle 直接告诉模型真实机制, 也只提升 +0.0137。残差缺口来自一个真正的双峰条件分布: 相同的输入下,球员要么拿到市场价的合同,要么接受底薪, 条件均值恰好落在两个峰之间。
解决思路是做一个区分:签约路径可以修正输出,但不能进入输入。 签约之后路径是事实,Stage 3 给四种由球队资格决定的路径各加一个常数偏移。 下表是修正后的结果。被修正的路径现在都在零的 $0.75M 以内, 而底薪仍然带着 +$1.65M,因为底薪由合同金额定义, 以它为条件会泄漏目标变量。
| 签约机制 | n | 残差偏差 | 是否修正 |
|---|---|---|---|
| Bird Rights | 305 | −$0.38M | 是 |
| 薪资空间 | 80 | −$0.43M | 是 |
| Early Bird | 53 | −$0.72M | 是 |
| Non-Bird | 14 | +$0.19M | 是 |
| MLE | 136 | +$0.52M | 否 |
| 底薪 | 259 | +$1.65M | 否 |
负偏差表示模型低估。先签后换归入 Bird Rights(发起方须持有 Bird 或 Early Bird 权)。 签约机制始终不进入特征集,所以这些残差可以作为诊断指标使用。
一个值得直说的修正
早期版本报告模型低估顶级球员最多 $6M,那是按实际薪资分箱读出的残差。 那个梯度是假象。按带噪声的目标变量分箱,即使校准完美的模型也会产生它, 因为分箱边界在噪声上做了选择。换成按预测值分箱, v8 冠军模型的校准斜率为 0.966,25%+ 区间残余偏差 +$1.47M。 折叠诚实的重新校准依然无法改善模型。
同一个陷阱还有第二种形式:比较两个模型时, 如果用其中一个模型自己的预测值来划分区间, 区间的样本构成会在两个模型之间漂移,制造出一个本不存在的差异。 这里的模型比较用的是两个模型都没有生成的东西来划分区间。
亮点
- 把 Grabit(梯度提升 Tobit)从信用风险建模适配到 NBA 薪资, 将双向截断与概率门控的顶薪推升和已知路径的续约上限组合起来, 每个阶段回答一个不同的 CBA 问题
- 端到端搭建了完整管线:抓取 Basketball Reference、Spotrac 和 nbarapm 的数据, 从 40+ 候选中通过消融筛选出 21 项特征, 以及一个四层评估协议分离结构估计和预测、选择和确认
- 建立了一个经过量化的否定性结论:签约机制的残差模式是真实存在的, 但无法通过特征恢复,任何修复的上限是实测的



