πŸ“‰ Backtest: Simplified Model on Real NBA Data

11 seasons Β· 13,903 games Β· 4,615 bets simulated

⚠️ Honest disclaimer up front

This backtest uses a simplified model (opening spread + cumulative season win % as the only inputs). It does NOT replicate the live analysis approach we use on game pages, which includes current form, injury news, head-to-head matchup, pitcher/lineup data, and qualitative judgement.

This is the model we COULD build with what's in a free historical odds dataset. The live analyses are richer, but we have no way to backtest them retrospectively because we'd need to know what news existed BEFORE each game.

Bottom line: the result below is the test of a naive baseline, not the test of our actual approach. But the lessons it teaches are real.

Final bankroll after 4,615 bets
$49.03
Started $1,000 Β· Net profit βˆ’$950.97 Β· ROI βˆ’95.1%

πŸ“Š Headline Stats

Games considered
13,903
Bets placed
4,615
Wins
2,309
Losses
2,212
Pushes
94
Win rate
51.07%
Break-even at βˆ’110
52.38%
Max drawdown
96.3%
Total wagered
$27,797
Avg CLV
βˆ’4.68 pts

πŸ“‰ Bankroll Trajectory

Bankroll over 4,615 bets. Starting line at $1,000. Bankroll dropped below $500 within the first 500 bets and never recovered.

πŸ”¬ What the Model Did

Data source

github.com/FinnedAI/sportsbookreview-scraper β€” pre-scraped NBA games 2011–2021 with opening/closing spreads, opening/closing totals, closing moneylines, per-quarter scores.

The model (intentionally simple)

For each game (chronologically):
  market_prob = Ξ¦(βˆ’opening_spread / 11) // Gaussian conversion, Οƒ=11
  record_signal = (home_win_pct βˆ’ away_win_pct) Γ— 0.10
  our_prob = market_prob + record_signal
  fair_market = market_prob / 1.0225 // remove half-vig
  edge = our_prob βˆ’ fair_market
  
  IF edge β‰₯ 2% AND EV > 0:
    stake = min(0.02 Γ— bankroll, 0.25 Γ— Kelly Γ— bankroll)
    BET on spread at βˆ’110
    Resolve via actual game margin

Sizing + gates

Edge thresholdβ‰₯ 2% over fair market
EV gateEV per $1 stake must be positive
SizingQuarter Kelly, capped at 2% of bankroll
Bet oddsStandard βˆ’110 (spread market)
Vig assumption4.5% (single-side half = 2.25% adjustment)

πŸ“ˆ Why It Failed β€” The CLV Smoking Gun

Win rate of 51.07% versus break-even of 52.38% means we lost roughly 1.3 percentage points per bet to vig. At 4,615 bets, that compounds catastrophically.

But the leading indicator is CLV (Closing Line Value):

Avg CLV
βˆ’4.68 pts
Negative CLV bets
2,161
Neutral CLV bets
812
Positive CLV bets
1,642
Translation: When we took a bet, the closing line moved AWAY from us on average. Sharp money agreed with our pick less often than they agreed with the other side. The market was processing information faster than our simple record-based model.

This is the signal of an unsharp bettor. The CLV told us within the first 500 bets that the strategy was broken β€” long before bankroll hit zero.

πŸŽ“ The 5 Lessons This Backtest Teaches

Lesson 1 β€” A simple model is a money pit. Adding "team record" as a signal to a market-derived prior produced a strategy that lost 95% of bankroll over 4,615 bets. The market already prices in records. Layering them back in just adds noise.
Lesson 2 β€” Win rate at 51% is meaningfully losing. 51% sounds "close to 50/50" but you need 52.4% to break even on βˆ’110 bets. The 1.4-point gap is the vig.
Lesson 3 β€” CLV is the leading indicator. Within ~500 bets we had clear negative CLV. We didn't need to wait for the full bankroll loss to know the strategy was broken. Track CLV in real time on live bets.
Lesson 4 β€” Quarter Kelly + 2% cap saved us from ruin. Even with a losing model, sizing prevented total wipeout in early bets. Bankroll ended at $49, not $0. Full Kelly would have busted within months.
Lesson 5 β€” Our LIVE analyses include nuance this backtest doesn't. Form trends, injuries, matchup tactics, lineup news β€” those aren't in the historical dataset. The simplified model failing here doesn't prove our live picks fail. But it DOES prove that simple statistical models aren't enough on their own.

πŸ“Š Per-Season Breakdown

SeasonGames
20111,074 (lockout-shortened)
20121,314
20131,319
20141,311
20151,316
20161,309
20171,312
20181,312
20191,143 (COVID-shortened)
20201,171 (COVID-shortened)
20211,322
Total13,903

πŸ“š What Comes Next

The backtest validates everything the methodology page warned about:

For our live picks:

  1. Track CLV on every actual bet placed
  2. If avg CLV stays negative across 30+ live bets, the live analysis isn't beating the market either β€” recalibrate
  3. If avg CLV is positive, the qualitative inputs (current form, injuries, matchup) ARE adding value above the simple model
  4. This is the test we need to actually run with real money or paper trades over the coming months
This backtest is the floor. Our live analysis should beat it. If our live analysis just matches it, we're no better than a record-based model β€” and that loses money. Track CLV religiously.

πŸ“¦ Reproduce This

Backtest script lives at /bets/learn/backtest-script.js.

To re-run: clone github.com/FinnedAI/sportsbookreview-scraper, set NBA_DATA env to the path of data/nba_archive_10Y.json, run with Node.js.