Educational/Not betting advice. For modeling and research purposes only.
GP
GametimePicks
v0.4
methodology

How the model works

Transparency over performance. The model is intentionally explainable — no deep learning, no black boxes — so the reasoning behind every lean is auditable.

Flow

NBA data
nba_api
Market line
The Odds API
Projection
weighted avg
Model probability
normal CDF
Edge
model − implied
Confidence
tiered
Tracked result
settled

01 · Projection

For each player and market (PTS / REB / AST), the model produces a projection by blending three rolling windows plus a home/away adjustment:

projection = 0.45·last5 + 0.35·last10 + 0.20·season
  + 0.30 · ( split_avg − base )

The split adjustment uses the player's home or away average depending on tonight's matchup. Weights are deliberately simple and tuned for explainability rather than peak fit.

02 · Implied probability

Sportsbooks publish American odds with vig built in. We strip vig using two-sided proportional de-vigging:

p_raw_over = 100 / (odds_over + 100)    (when odds > 0)
p_raw_over = −odds / (−odds + 100) (when odds < 0)
p_implied_over = p_raw_over / (p_raw_over + p_raw_under)

03 · Model probability

We model the player's stat as a normal distribution centered at the projection, with σ derived from recent dispersion:

P(over) = 1 − Φ ( (line − projection) / σ )

Φ is the standard normal CDF. σ is the population standard deviation of the player's last-N games on the same stat, with a floor of 1.0 to prevent degenerate cases.

04 · Edge

edge_pp = ( P_model − P_implied ) × 100

Reported in percentage points. The lean (Over / Under) is whichever side has positive edge.

05 · Confidence tiers

Tiers are assigned by edge magnitude AND data-quality sanity check.

  • Highedge ≥ 5pp AND ≥ 8 recent games of data
  • Mediumedge ≥ 2.5pp AND ≥ 5 recent games
  • Low / No Playanything below the medium threshold

Data sources

GametimePicks runs on a multi-source provider system. Each external service is accessed through a common adapter interface, so the pipeline can fail over from one source to the next without breaking.

  • nba_api — official NBA.com Stats endpoints (Tier 1, no key)
  • The Odds API — compliant sportsbook odds (Tier 1, free tier 500 req/mo)
  • demo — bundled fallback (Tier 1, always works)
  • balldontlie · espn · opticodds · sportsdata(Tier 2/3 — scaffolded, opt-in)

The system never scrapes sportsbook websites or reverse-engineers mobile apps. Every key lives in environment variables; nothing is hardcoded.

Demo, Live, Hybrid

Demo

Both NBA and odds providers are running on bundled fallback data. Useful for offline development and recruiter previews. Numbers are realistic but not from tonight's slate.

Live

Real NBA data via nba_api and live sportsbook odds via The Odds API. Generated from tonight's actual schedule and current lines.

Hybrid

One source is live, the other fell back to demo. Usually means odds came through but a stats endpoint hiccupped, or vice versa. The data-source badge shows which is which.

Limitations

  • No injury / minutes adjustment. The model treats minutes as constant. A late-scratch starter substantially changes projection inputs but isn't reflected until the next pipeline run.
  • No back-to-back / rest adjustment. Travel and fatigue impact production. Not currently modeled.
  • Lines move. The board reflects odds at pipeline time. By the time you read it, lines have likely shifted.
  • No causal claims. A positive edge is correlation between recent stats and the line, not a guarantee.

Manual news overrides

Phase 7B-1 introduces a free, compliant news layer: a local JSON file at pipeline/manual_overrides/news_signals.json. The operator manually adds entries when verifiable news appears (official injury reports, beat reporters, team accounts). Each signal carries a source URL, timestamp, and modelAction directive (e.g. remove_from_board, flag_risk).

The pipeline reads this file on every run, filters expired signals, and attaches relevant ones to each lean. Signals appear on prop cards with the source label, update type, and a verification link.

What this does not do: we do not scrape Twitter/X, do not auto-ingest from any social platform, and do not invent player statuses. If no signal exists for a player, the UI says “No active manual signals” rather than asserting they are healthy. See docs/news_overrides.md in the repo for the operator workflow.