How the Model Works
A transparent look at the math, features, and validation behind our NBA predictions. No hype — just the methodology.
What This Actually Is
Summary:
Predictium runs B13, a 3-head blend prediction system that generates NBA spread and total forecasts, plus player prop projections. Head A is an ElasticNet (α=0.1, L1=0.9) with 204 features for spreads and an ElasticNet symmetric split-score (α=0.5, L1=0.1) with 229 features for totals. Head B is a RAPM-only Ridge model (α=1.0) with 25 features — 21 base RAPM roster features plus 4 engineered composites — that captures player-level roster quality signal independently. The two heads are blended: 57% ElasticNet / 43% RAPM for spreads, 77% ElasticNet / 23% RAPM for totals. After blending, a B6 RAPM post-prediction calibration layer catches residual signal. The feature set includes team stats, exponentially-weighted moving averages, player tracking data, 16 travel features (distance, timezone crossings, cumulative load, jet lag, schedule density), 13 lineup disruption features, 3 matchup interaction features, and RAPM roster projections. Trained on 5 seasons of data, predictions refresh continuously.
Why it Works:
Sports prediction is hard. NBA games have inherent randomness that no model can eliminate. Our walk-forward R² is 0.2089 for spreads and 0.1247 for totals — meaning we explain about 12–21% of the variance in game outcomes. Those numbers are honest: they come from strict temporal validation where the model only ever trains on past data and predicts unseen future games. The 3-head blend architecture and B6 RAPM post-prediction calibration squeeze additional edge by combining independent prediction signals.
We don't need to predict everything — we just need to be slightly more accurate than the betting market's implied probabilities. A small, consistent edge compounded over hundreds of bets with proper bankroll management produces returns.
Model Architecture: 3-Head Blend
Independent prediction heads combined for maximum signal
Why three heads? Because different model architectures capture different signals — and blending independent predictions outperforms any single model:
- Head A — Spreads (204 features → ElasticNet): Relative team strength from team-level stats. Uses ElasticNet (α=0.1, L1=0.9) with aggressive L1 sparsity — most features get zeroed out, letting a smaller set of strong predictors dominate. Includes 21 team RAPM roster features, 13 lineup disruption features, and 3 matchup interaction features.
- Head A — Totals (229 features → Symmetric Split-Score ElasticNet): Instead of predicting the total directly, B13 predicts each team's score separately using one model trained on "canonical" data — every game contributes two rows by swapping home/away into offensive/opponent columns. This doubles the training signal. Uses Ridge-like regularization (L1=0.1) that keeps many weak features alive.
- Head B — RAPM Ridge (25 features): A completely independent prediction path using only player-level RAPM data. Ridge regression (α=1.0) on 21 base RAPM features (baseline/projected/delta for offense, defense, and net — plus differentials) and 4 engineered composites (quality diff, offense matchup, defense matchup, baseline quality diff). This head captures roster quality changes instantly — when a star is traded or injured, RAPM adjusts on day one while team-level rolling stats take 10+ games to reflect the change.
Blend weights (optimized via walk-forward grid search with bootstrap confidence intervals):
- Spreads: 57% Head A + 43% Head B → R² = 0.2089 (+6.4% vs Head A alone)
- Totals: 77% Head A + 23% Head B → R² = 0.1247 (+8.4% vs Head A alone)
3-Head Blend Architecture
HEAD A — SPREAD
ElasticNet (α=0.1, L1=0.9)
HEAD B — RAPM
Ridge (α=1.0)
HEAD A — TOTAL
ElasticNet (α=0.5, L1=0.1)
Blended Spread
R² = 0.2089
MAE ~11.0 pts
Blended Total
R² = 0.1247
MAE ~15.0 pts
Head B (RAPM) captures player-level roster quality instantly — trades and injuries are reflected on day one, while Head A rolling stats take 10+ games to adjust.
B13's Key Insights
Insight 1 — Opposite regularization: Spreads benefit from aggressive L1 sparsity (L1=0.9) that zeros out weak features. Totals benefit from Ridge-like regularization (L1=0.1) that keeps many weak features alive. Same ElasticNet framework, opposite tuning.
Insight 2 — Independent RAPM signal: RAPM features inside the ElasticNet get pruned (correlated with net_rtg, r=0.44). But as a separate head, RAPM captures independent signal — especially during roster disruption (trades, injuries). The 43% RAPM weight on spreads is nearly equal, reflecting how much independent information player-level ratings carry.
Feature Engineering: Up to 229 Features + 25 RAPM Head
What the model actually sees
Feature Breakdown:
| Category | Count | Examples |
|---|---|---|
| Team Ratings | ~40 | Offensive rating L5/L10/L15/L40, defensive rating, net rating |
| EWMA Features | 28 | Exponentially-weighted moving averages with h10/h20/h40 half-lives |
| Pace & Style | ~15 | Pace L5/L10/L40, expected pace, pace mismatch |
| Rest & Schedule | ~8 | Rest days, back-to-backs, rest advantage |
| Scoring Patterns | ~15 | Points per game, points allowed, game totals (various windows) |
| Matchup Features | ~8 | Home offense vs away defense, expected game total |
| Tracking Stats | 14 | Drives, paint touches, pace miles (from NBA advanced tracking) |
| Player Deltas | 4 | home_off_delta, home_def_delta, away_off_delta, away_def_delta |
| RAPM Roster (Head A) | 21 | Team RAPM offense/defense/pace projected from player ratings × minutes |
| RAPM Head B Features | 25 | 21 base RAPM + 4 engineered (quality diff, offense/defense matchup, baseline diff) — separate Ridge model blended with Head A |
| RAPM Features (totals) | ~30 | Baseline/projected/delta RAPM for offense, defense, and net — all included via symmetric stacking |
| Lineup Disruption | 13 | Missing star proxy, minutes disruption, replacement gap, top player out |
| Matchup Interactions | 3 | Harmonic game total, analytical expected total, combined expected pts |
| Misc | ~16 | Efficiency metrics, turnover rates, bench depth |
1. RAPM: Regularized Adjusted Plus-Minus
RAPM is the foundation of B13's player-level intelligence. It decomposes every player's impact into three components — Offense, Defense, and Pace — by solving a massive regularized regression over play-by-play data.
For each possession: points_scored = Σ(player_on_court × player_coefficient) + home_court + ε Regularized with Ridge (α=500) to prevent overfitting. Computed across 166 biweekly snapshots spanning 5 NBA seasons.
The α=500 regularization is critical: without it, role players with small sample sizes get wildly unstable ratings. Heavy regularization shrinks uncertain estimates toward zero while letting high-minute stars' true impact show through.
Why RAPM Matters for Prediction
- Trades are immediately captured. When Trae Young was traded to Washington, his individual RAPM ratings carried with him. The model didn't need to wait for team-level stats to adjust — it projected Washington's new team-level RAPM from the updated roster on day one.
- Injuries are decomposed. Losing a player doesn't just mean "team gets worse" — it means specific offensive, defensive, and pace impacts based on who absorbs those minutes.
- Context-independent. A player's RAPM rating reflects their impact regardless of teammates, isolating individual contribution from team effects.
2. Team RAPM Roster Features
Individual RAPM ratings become team-level features by projecting tonight's roster:
team_rapm_offense = Σ(player_rapm_offense × projected_minutes) / total_minutes team_rapm_defense = Σ(player_rapm_defense × projected_minutes) / total_minutes team_rapm_pace = Σ(player_rapm_pace × projected_minutes) / total_minutes
This produces 21 features for the spread model: home/away × offense/defense/pace × various aggregations (plus differentials). For totals, the symmetric split-score approach includes all RAPM features through canonical column mapping — each team's offensive and defensive RAPM projections become "off_" and "opp_" features, giving the model a richer view of the matchup.
3. EWMA Features (Adaptive Rolling Averages)
Standard rolling windows treat all games equally. EWMA gives recent games progressively more weight:
EWMA_today = α × today's_game + (1-α) × EWMA_yesterday Where α = 1 - exp(-ln(2) / halflife)
Three Half-Lives:
- h10: 50% weight from last 10 games (captures hot/cold streaks)
- h20: 50% weight from last 20 games (balances recency and stability)
- h40: 50% weight from last 40 games (stable long-term trends)
EWMA Weight Decay Over Time
How much weight does each game receive based on recency?
h10
50% weight in 10 games
h20
50% weight in 20 games
h40
50% weight in 40 games
4. Player Delta Features (Lineup Adjustments)
Four features capture how tonight's projected lineup differs from the baseline the model expects.
Key Concepts
- Baseline Rotation: The minute distribution the model "thinks" has been playing (50% L10 + 25% L15 + 15% L5 + 10% L40 blend).
- Scenario Rotation: Tonight's projected minutes based on injury reports and lineup confirmations.
- Delta: The difference. If a player is playing their usual minutes, delta = 0. If a starter is out, their minutes redistribute and the delta captures the net impact.
Example: Giannis OUT Impact
Off Impact: +4.2 | Def Impact: +2.8 | Baseline Minutes: 35
Total Impact
-1.1 pts on game total
Spread Impact
-5.1 pts vs spread
5. High-Impact Features (SHAP Analysis)
Not all features matter equally. SHAP analysis reveals which features drive predictions:
Top 10 Features by SHAP Importance
Which features have the most impact on predictions?
Higher SHAP values = greater impact on model predictions
The Continuous Pipeline
How predictions stay fresh
Continuous Pipeline Cycles
Rapid Update Cycle
Full Refresh Cycle
Why Selective Re-Prediction?
Full prediction runs take ~90 seconds. Selective re-runs (every 1 min) complete in ~10 seconds for only affected games. Full refresh every 5 minutes ensures all predictions stay current.
Continuous Improvement
We don't ship a model and walk away
B13 isn't a static artifact. It's the current output of a continuous improvement pipeline designed for rapid iteration:
- Feature caching: The full feature matrix (~20 minutes to build from raw data) is computed once and cached. New model configurations test against the cached features in seconds, not minutes.
- Sub-minute iteration: Once features are built, we can test dozens of model configs (hyperparameters, feature subsets, regularization strategies) in under a minute each.
- Walk-forward discipline: Every candidate model is evaluated with strict temporal validation before it goes live. No peeking at future data, ever.
- Incremental feature development: New features (like travel features in B4) are validated independently before being added to the production feature set.
We don't ship a model and walk away — we ship a model and keep improving it. B13 replaced B12 when rebuilding RAPM data (filling 135 games with zero coverage) revealed that an independent RAPM head carries significant signal — adding +6.4% spread R² and +8.4% total R² through a simple fixed blend. The next iteration is always in development.
Validation: How We Know It Works
The only test that matters
We use strict temporal validation — the model only sees data available before prediction time:
Walk-Forward Validation Structure
Strict temporal separation prevents data leakage
Training Period
Model cannot see beyond this point during training
Test Period (Held Out)
Data Flow Direction
R² Performance Across Seasons
Walk-forward validation on held-out test data
Blended R²: 0.2089
Stable across seasons. 57% ElasticNet + 43% RAPM blend. RAPM head captures roster changes instantly while rolling stats lag.
Blended R²: 0.1247
77% ElasticNet symmetric split-score + 23% RAPM Ridge. Totals are inherently harder — signal is distributed across many weak features.
Current Performance (Walk-Forward Validation):
| Metric | Value | Interpretation |
|---|---|---|
| Spread R² | 0.2089 | Explains 20.89% of spread variance |
| Total R² | 0.1247 | Explains 12.47% of total variance |
About These Numbers
These are honest walk-forward numbers. The model trains on prior seasons and predicts future games it has never seen. Many sports models report in-sample R² (training on and testing against the same data), which produces flattering but meaningless numbers. Our R² is lower because it's real. If someone shows you an NBA model with R² above 0.30 on walk-forward, ask to see their methodology.
Limitations: What This Model Is Not
Honest assessment
It's Not a Crystal Ball
R² of 0.12–0.21 means 79–88% of game variance is noise or unmeasured factors. This is a tool for systematic, probabilistic betting over many games — not for calling tonight's winner with certainty.
RAPM Has Limits
Player impact ratings are estimated from noisy play-by-play data. Role players with limited minutes have uncertain ratings (which is why we use α=500 regularization — it admits uncertainty). Rookie impact is especially hard to estimate before they accumulate minutes.
It's Blind to Intangibles
Locker room chemistry, coaching motivation, playoff implications, referee tendencies, tanking — these matter but are hard to quantify.
Totals Are Harder Than Spreads
Even with B13's total prediction (R² 0.1247 vs 0.2089 for spreads), totals remain harder. Game totals are more volatile and depend on in-game pace dynamics that are difficult to predict before tip-off. B13's Ridge-like regularization (L1=0.1) keeps many weak predictors alive, and the RAPM blend adds +8.4% — but totals signal remains inherently more distributed and harder to capture.
No fluff. No promises. Just the math.
Ready to Use the Model?
Get real-time predictions, edges, and player impact analysis for every NBA game.
Predictium provides analytical tools for informational purposes. All betting carries risk. Gamble responsibly.