Make a forecast
The model estimates team scores from recent form, efficiency, rest, and fatigue.
Reference forecasts · no official picks
No NBA market has cleared that standard, so the product shows a scorecard instead of bets.
The model estimates team scores from recent form, efficiency, rest, and fatigue.
A market must stay useful across separate historical samples—not merely look good in one slice.
The Picks page converts all of that work into BET, PASS, HOLD, or RESULT—plus the exact ticket when action is justified.
Edge: the model’s estimated advantage over the available line.
ROI: profit or loss for each unit risked.
Forward tested: recorded after a prediction was posted, not reconstructed with hindsight.
How the model is built, trained, and validated. Tables below are a snapshot from export_nba_backtest_charts.py, not a live recompute.
Ridge regression, predicting each game's home and away points directly. NBA scoring is high and roughly continuous (games average ~110-120 points per side), which is a better statistical fit for a regression than MLB's low-count Poisson/Negative Binomial run models - no count-model variant was tried here for that reason.
Ridge was picked by walk-forward backtesting every model in the registry and comparing winner accuracy. It edged out elastic_net by 0.3 points of accuracy (65.3% vs. 65.1%), well within noise, but elastic_net's extra hyperparameter has never been tuned for this data, so ridge is the more defensible default. Both clearly beat every tree ensemble tried, the same pattern MLB found (though for a different underlying reason - MLB's signal-to-noise is too low for a flexible model; NBA's team-level box-score features may simply not have enough nonlinear structure for trees to exploit over a regularized linear fit).
| Model | MAE (home) | MAE (away) | MAE (total) | MAE (margin) | Winner accuracy |
|---|---|---|---|---|---|
| elastic_net | 9.55 | 9.41 | 15.24 | 11.04 | 65.3% |
| ridge | 9.60 | 9.47 | 15.30 | 11.14 | 65.1% |
| linear | 9.63 | 9.54 | 15.38 | 11.20 | 64.2% |
| mlp | 11.88 | 11.74 | 20.46 | 11.46 | 64.1% |
| random_forest | 9.66 | 9.49 | 15.38 | 11.27 | 63.3% |
| extra_trees | 9.67 | 9.50 | 15.37 | 11.30 | 63.1% |
| gradient_boosting | 9.85 | 9.66 | 15.53 | 11.63 | 62.7% |
| knn | 9.93 | 9.73 | 15.67 | 11.61 | 61.5% |
MAE columns are mean absolute error in points.
Every feature is computed as of tip-off - rolling stats use only games strictly before the one being predicted. No probable-starter/injury features yet (no official NBA API for either), unlike MLB's starting-pitcher features.
Walk-forward training: every backtest prediction is made by a model trained only on seasons strictly before the one being predicted - the same discipline used for live recommendations.
No schedule backbone: unlike NFL/MLB, there's no advance season schedule source with future games (nba_api is historical-results only). Today's games for live recommendations come directly from the live-odds scrape instead.
Robustness testing: a free public historical-odds dataset made the same split-half + 1,000-trial permutation test NFL/MLB use possible here too. No market cleared it: spread showed negative ROI in both backtest halves, getting worse at higher thresholds (permutation p>=0.25 everywhere - indistinguishable from a no-skill model). Total's one low p-value (0.032 at edge>=10) flips sign between halves (+6.4% vs. -11.1%), the exact multiple-comparisons trap this methodology exists to catch. Moneyline's odds coverage drops to ~0% for two recent seasons in that dataset, leaving only one usable season - not enough for split-half, and that season alone doesn't clear significance either.
No tiers: every NBA recommendation is currently untiered, now backed by a real test rather than an absence of evidence. The path forward is accumulating settled bets in the live ledger and re-running this same validation against forward results.
ROI at each edge threshold, computed separately on the first half vs. second half of the backtest seasons. None of these are validated tiers - this is exactly what ruled all three out (see the sign-flips and consistently negative values).
| Threshold | First half ROI | Second half ROI |
|---|---|---|
| 1 | -6.8% | -4.4% |
| 2 | -4.8% | -4.3% |
| 3 | -5.0% | -4.6% |
| 4 | -5.9% | -4.9% |
| 5 | -8.0% | -4.9% |
| 6 | -9.8% | -4.5% |
| Threshold | First half ROI | Second half ROI |
|---|---|---|
| 2 | -2.1% | -7.1% |
| 4 | -0.2% | -9.2% |
| 6 | 1.3% | -11.6% |
| 8 | 3.7% | -14.3% |
| 10 | 6.4% | -11.1% |
| Threshold | First half ROI | Second half ROI |
|---|---|---|
| 0.02 | 0.1% | n/a |
| 0.04 | 2.0% | n/a |
| 0.06 | 5.3% | n/a |
| 0.08 | 4.2% | n/a |
| 0.1 | 7.2% | n/a |
Full-backtest-period ROI if you only bet when the model's edge clears a given threshold - moneyline/spread/total use different units (probability, points, points).
| Threshold | ROI | Bets |
|---|---|---|
| 1 | -5.6% | 3523 |
| 2 | -4.6% | 2776 |
| 3 | -4.8% | 2083 |
| 4 | -5.4% | 1524 |
| 5 | -6.5% | 1058 |
| 6 | -7.2% | 714 |
| Threshold | ROI | Bets |
|---|---|---|
| 2 | -4.5% | 3306 |
| 4 | -4.3% | 2393 |
| 6 |
| -4.0% |
| 1599 |
| 8 | -3.2% | 975 |
| 10 | 0.2% | 569 |
| Threshold | ROI | Bets |
|---|---|---|
| 0.02 | 0.1% | 442 |
| 0.04 | 2.0% | 391 |
| 0.06 | 5.3% | 335 |
| 0.08 | 4.2% | 287 |
| 0.1 | 7.2% | 252 |