draftkings apiDashboard ↗

INDEPENDENT DEVELOPER REFERENCE

Build a baseline NBA points prop model

Build a chronological NBA points baseline from recorded box scores, score it on later games and compare with Pick6 targets you recorded yourself.

Editorial review: · Not a live coverage report

This is a data engineering tutorial. It does not produce picks, and nothing here shows an edge, a win rate or a profit. No wagering.

What you will build

A baseline that predicts a player's points from the average of their own earlier games, scored on later games. It is deliberately simple. A rolling mean tells you what any later model must beat, and it runs on the small statistics samples available today.

Independent ESPN statistics cover NBA, NFL and NHL as limited retained game samples, not full seasons. As of 2026-11-03 those samples were captured after the games were played, so they do not show what was known at game time. Recorded Pick6 lines began around 2026-11-03, so this tutorial does not backtest against past seasons of lines. It cannot, because those lines were not recorded.

The finished script
export DRAFTKINGS_API_KEY=...
python3 nba-points-baseline.py --season 2027 --min-games 5 --max-pages 5

Pull NBA box scores

NBA seasons use their ending year: season=2027 is 2026-27 and season=2026 is 2025-26. Rows are JSON only, with limit up to 200 and cursor pagination through pagination.next. Use completed=true so in-progress games stay out.

One page of completed games
curl --fail-with-body "https://draftkingsapi.com/api/v1/stats/player-games?sport=NBA&season=2027&completed=true&limit=200" \
  -H "Authorization: Bearer $DRAFTKINGS_API_KEY"
A row, trimmed
{
  "data": {
    "athlete_id": "ESPN_ID",
    "name": "Player Name",
    "category": "boxscore",
    "did_not_play": false,
    "stats": [
      { "key": "minutes", "label": "MIN", "scope": "game", "display": "14", "numeric": 14 },
      { "key": "points", "label": "PTS", "scope": "game", "display": "3", "numeric": 3 }
    ]
  },
  "game": { "starts_at": "START_TIME", "completed": true, "event_id": "ESPN_EVENT" }
}

Check available and coverage in the response. An empty matching page returns available: false, which means no stored rows and does not mean the games did not happen.

Extract the target

For NBA rows, category is boxscore and the target is the stat with key: "points". Read its numeric value and require scope to be game. Skip rows where did_not_play is true or numeric is null rather than scoring them as zero.

Python
def points(row):
    if row["data"].get("did_not_play"):
        return None
    for stat in row["data"]["stats"]:
        if stat["key"] == "points" and stat["scope"] == "game":
            return stat["numeric"]  # None when not a scalar
    return None

Order by game time, then roll

Pagination order follows a cursor built from event and athlete IDs, not the calendar. Sort each athlete's games by game.starts_at before computing anything. The prediction for a game uses only earlier games.

No current game in its own feature
from collections import defaultdict
history = defaultdict(list)
rows.sort(key=lambda r: r["game"]["starts_at"])
for row in rows:
    value = points(row)
    if value is None:
        continue
    earlier = history[row["data"]["athlete_id"]]
    prediction = sum(earlier[-5:]) / len(earlier[-5:]) if len(earlier) >= MIN_GAMES else None
    # record (prediction, value) here, before appending this game
    earlier.append(value)

Appending after predicting is what prevents leakage from the current game. Athletes with fewer than --min-games earlier games get no prediction, and the report counts them.

Score it on later games

Use a date cutoff. Fit nothing on games after it, and report mean absolute error on games after it. MAE reads in points, so it is easy to explain. Report the sample size next to it every time.

  • Print the number of athletes and games scored, and how many were skipped and why.
  • A small test window gives a noisy number. Do not quote it as model accuracy in general.
  • Compare against a trivial baseline such as the player's previous game. A model that cannot beat it has not added anything.

The Python walkthrough covers caching, the cutoff and each piece of the script.

Compare to recorded Pick6 targets

Comparison needs a target you recorded before the game. A Pick6 row read today says nothing about a target last week. Record NBA Points rows, then compare after games finish. Fresh NBA rows depend on the slate, and the table on status shows capture age.

Skips stale rows by default
python3 pick6-board.py --sport NBA --stat Points --limit 25 --max-pages 2
  • Rows are market rows. One athlete can have several targets, so choose a rule, such as a row with both More and Less, and state it.
  • There is no verified mapping from Pick6 entity_id to ESPN athlete_id. If you match on name, refuse ambiguous matches and report the rest.
  • Pick6 rows carry standings multipliers such as 1.5x. A standings multiplier is not decimal odds, a probability or an established entry payout.
  • A difference between your prediction and a target is a number to inspect. It is not a recommendation, and a small sample cannot show an edge.

Try it

Check the recorded data

Questions

Can I backtest this model against past NBA seasons of lines?

No. Lines were not recorded for past seasons. You can score predictions against box scores in retained samples and compare to Pick6 targets only for rows you recorded before the game.

Which season value should I use for the NBA?

NBA uses the ending year. Use 2027 for the 2026-27 season and 2026 for 2025-26. A valid season does not promise full coverage, so check the available field.

Will this model make money?

This tutorial does not show that and is not betting advice. It builds a baseline for prediction error on a small sample. No edge, win rate or profit is established.

Why start with a rolling mean?

It is easy to verify, avoids leakage when ordered correctly and sets the bar for anything more complex. Current samples are too small to support an elaborate model.

Related resources

Independent service operated by DD Inc., not affiliated with or endorsed by DraftKings. Technical access is not a redistribution license. No wagering: this service does not place, recommend or settle entries or bets. Check sources and status for actual capture context.