How to build an NBA player prop model in Python
Import real regular-season game logs, make an inspectable points forecast, evaluate it on later games, and compare it with today’s PrizePicks line. One downloadable Python script takes you from your first sample to saved results.
Published 2 November 2026 · PrizePicks API
1. Choose one prediction target
Start with one player’s full-game NBA points in regular-season games. The target is the final points total, not whether a historical PrizePicks line went over. That distinction lets you learn from loaded game-log seasons even when those games predate our observed PrizePicks lines. The line archive starts on 28 Oct 2026; older box scores do not create older offered lines.
The first model is deliberately transparent: predict the mean points from the last five earlier games in the same season. Compare it with a second baseline, the mean of all earlier games in that season. There are no hand-picked matchup multipliers or claims that five games is the optimal window.
2. Import real regular-season game logs
Create a Free API key, save it as PRIZEPICKS_API_KEY in your environment, then download the Python script linked above. It uses Python 3’s standard library, so no packages need installing. The /game-logs request specifies league=NBA, player, season_type=regular and limit=200. A season filter is optional. Read meta.stats_leagues and returned warnings to confirm coverage; a player with no loaded games may return an empty page.
python3 nba-player-prop-model.py --player "Jayson Tatum"
# With a paid key and an available historical season:
python3 nba-player-prop-model.py --player "Jayson Tatum" --season 2025-26The script follows meta.next_cursor while the response permits it and keeps every page’s metadata. If the API reports a plan limit, it keeps the returned sample and stops paging. Free’s ten-game limit can still produce a useful file and a few worked predictions; it is not a full-season evaluation. HTTP errors are left visible rather than being replaced with invented rows. For an unambiguous identity, use --player-id with the pl_ identifier from /players or a sp_ stats_player_id from game logs; the script sends player_id instead of player.
3. Clean points without turning missing values into zeros
The full game-log response places points under stats.points, the game key under stats_event_id, and the date under game_date. Keep only season_type=regular, exclude did_not_play, and deduplicate by game identifier. A zero-point appearance remains zero. A missing points key is unknown and is excluded, not filled with zero. Sort the accepted games from oldest to newest before deriving features.
# These are actual response fields, not guessed CSV column names.
points = game["stats"].get("points")
game_id = game["stats_event_id"]
game_date = game["game_date"]
season = game["season"]4. Work through one five-game points forecast
Here is a synthetic example to make the arithmetic inspectable. Suppose the five earlier points totals are 18, 24, 20, 30 and 23. Their sum is 115, so the rolling forecast is 23.0 points. If all earlier games that season sum to 442 points over 20 games, the season-average baseline is 22.1. These numbers illustrate the method; they are not an observed player record or a betting recommendation.
| Quantity | Worked value | When it is available |
|---|---|---|
| Previous five-game points mean | 115 / 5 = 23.0 | Before the target game, after those earlier games finish |
| Previous season-to-date mean | 442 / 20 = 22.1 | Before the target game |
| Target game final points | For example, 26 | Only after the target game; label, never a feature |
| Rolling absolute error | |26 − 23.0| = 3.0 | After the target game, for evaluation |
5. Evaluate in chronological walk-forward order
For each target game, calculate both forecasts using only records with a strictly earlier game_date in the same season. Exclude every game on the target date when completion times are unavailable. The target’s own points must not affect its forecast. Then move to the next game and repeat; the earlier result can now inform the later forecast.
The script reports mean absolute error (MAE) for both baselines over exactly the same evaluated games: add the absolute errors and divide by the evaluated-game count. Lower MAE means a smaller average points error on that window. The script withholds MAE when fewer than 20 worked forecasts are available, while still exporting their rows. This is a tutorial guard, not a scientifically sufficient sample-size rule. MAE does not mean higher over/under accuracy, calibrated probabilities or positive returns. If fewer than five earlier games are available, it prints no score for that game instead of manufacturing a prediction.
earlier = [g for g in history
if g["season"] == target_season and g["date"] < target_date]
forecast = sum(g["points"] for g in earlier[-5:]) / 5
absolute_error = abs(final_points - forecast)This is a retrospective walk-forward baseline using today’s stored box-score revisions. A later stat correction may change an older value. It is not proof of exactly which revision a live system knew that day. Archive daily inputs if that distinction matters. If you tune the five-game window or add features after seeing these errors, reserve later untouched dates for a separate final evaluation.
6. Compare the forecast with a current PrizePicks line
The script also requests /projections with league=NBA, stat=Points, tier=standard and view=full. It requires league=NBA, an exact player-name match after the API search, market.stat=Points, market.period=Full game, an available standard-tier selection, an active offer and a future event.starts_at. Half-game and quarter lines are excluded. It excludes today’s game logs when constructing this pregame feature. With a 23.0-point forecast and a current 24.5-point line, the difference is −1.5 points. That is a difference between a stat estimate and a threshold, not an under probability or an expected-value calculation.
Each comparison includes the projection identifier and observed_at. The script skips comparisons unless board pages report fresh, and skips offers observed more than ten minutes ago. That is a conservative tutorial filter, not a freshness guarantee. If the player is not on the board, the start time has passed, or the selected logs do not include enough earlier games in the upcoming season, the comparison list stays empty. A model trained on a prior season is not silently reused as a current-season forecast.
7. Save the result and decide what to improve
The script saves the returned input rows in nba-model-game-logs.json and writes nba-model-walk-forward.csv with one worked forecast per eligible game and nba-model-metadata.json with coverage, sample limits, current-line comparisons and the error summary. It never writes the API key. Every request sends X-User-Request describing this task; those summaries are retained for 35 days to improve the product. Keep personal information and secrets out of request context.
- Inspect the evaluated-game count and date range before interpreting the MAE. A handful of Free sample forecasts is a parser demonstration, not a trustworthy performance estimate.
- Use minutes from earlier games to investigate role changes, not the target game’s final minutes. Any extra injury, starter or matchup feature needs its own availability timestamp.
- Compare new features against both unchanged baselines on later untouched games. Keep the target definition and evaluation rows consistent.
- For a classifier against historical offered lines, use the separate graded training-data workflow. Do not infer historical PrizePicks thresholds from box-score averages.
What the paid plan actually changes
Developer costs $49/month and unlocks all loaded player-game seasons and pagination beyond the Free ten-game cap, plus the full available graded training set rather than its 100-row sample. It does not create missing leagues, guarantee enough rows for a robust model, or add PrizePicks lines before observation began. Try the useful sample first and upgrade only when its returned limits prevent the training window you need.
Continue with your data
Graded training data and point-in-time line features · Inspect your real sample · Compare plan limits