Home Notes Research CV Projects Outside of Math

WNBA Analytics: Technical Summary

How the WNBA forecast is built: collecting and validating every game since 1997, storing it, rating and simulating it, publishing the results as a website, and keeping all of it up to date automatically.

View live forecast View on GitHub Methodology

At a Glance

PieceDetails
LanguagePython 3.11+, using only the standard library: the pipeline has no third-party dependencies.
DataEvery regular-season and playoff game since 1997 from the league's stats API, with team and player box scores, saved as CSV files in the repository.
StorageA SQLite database of 9 tables and 4 views, rebuilt from the raw files and validated on every build.
ModelsElo ratings, a margin model for point spreads, and a 20,000-run Monte Carlo simulation of the season and playoffs.
WebsiteA single static page with inline SVG charts, generated in Python and served by GitHub Pages.
AutomationA scheduled task runs the full update every morning and commits and pushes any new data.
InterfaceA wnba command-line tool for every stage.
Testing67 pytest tests covering fetching, the database build, the models, the website, and the git automation.

The Pipeline

1 · Fetch

Download new games and the schedule from the league's stats API.

2 · Build

Validate the raw data and load it into SQLite.

3 · Model

Update Elo ratings, predict upcoming games, and simulate the rest of the season.

4 · Publish

Save a forecast snapshot, regenerate the website, and push to GitHub.

Collecting the Data

A small HTTP client for the league's stats API (fetch.py) downloads team and player game logs one season at a time, with retries and a polite delay between requests. Raw responses are saved as CSV files and committed, so the database can be rebuilt offline. Already-downloaded seasons are skipped, and the current season is always refreshed.

The API is mostly clean, but the pipeline handles its quirks:

Storing and Validating It

The raw files are loaded into a SQLite database: franchises (tracked through relocations, so the Utah Starzz, San Antonio, and the Las Vegas Aces are one franchise), seasons, games, team and player box scores, the schedule, playoff seeds, and every game's Elo ratings, plus views for season standings, team and player stats, and head-to-head records.

The build refuses to load data that fails any of these checks:

The database is built in a temporary file and only swapped in at the end, so a failed build never replaces a good one.

Models

Every team is rated with an Elo system adapted from FiveThirtyEight's NBA model, including a margin-of-victory adjustment, regression toward the mean between seasons, and a home-court advantage estimated from the previous five seasons. Ratings turn into a win probability and a point spread for any matchup. The Monte Carlo simulation then plays out every remaining game 20,000 times, updating ratings inside each simulated season, and seeds and plays the playoffs in the league's current format.

The parameters were tuned on 1997–2018 and then tested, untouched, on 2019–2026: the model picks 67.5% of held-out games correctly with a log loss of 0.608. The full details are on the Methodology page.

Forecasts and Live Scoring

Each update checks whether any games were played since the last forecast. If so, it re-simulates the whole league (one result can change every team's playoff path) and appends a snapshot to the forecast history: each team's record, rating, and playoff, semifinal, finals, and title odds. Every snapshot also records the predicted win probability and spread for each upcoming game before it is played, so the model is later scored on games it truly had not seen.

The Website

website.py turns the database and forecast history into a single static HTML page served by GitHub Pages: title odds with their movement over time, upcoming games with spreads, the model's live record, its accuracy and calibration, and the decline in home-court advantage. Charts are drawn as inline SVG, so there is no JavaScript framework or build step, and the page has no timestamps, so it only changes when the data does.

Daily Automation

A scheduled task on my computer runs wnba update --push every morning. It fetches the new games, rebuilds the database, saves a forecast if anything changed, regenerates the website, and commits and pushes the result. Because it pushes to the repository on its own, it is deliberately cautious:

Command-Line Interface

Every stage is available through the wnba command:

wnba fetch          # download new games and the schedule
wnba build          # validate and load the database
wnba ratings        # current Elo ratings
wnba predict        # upcoming games: win probability and spread
wnba simulate       # Monte Carlo playoff and title odds
wnba backtest       # score past predictions (--tune repeats the grid search)
wnba update --push  # the full daily update

Limits and Next Steps

Elo only knows results. It can't see injuries, trades, or who is playing tonight, and it lags sudden changes in a team's strength. The natural next steps are adjusting ratings for player availability using the player data already in the database, a richer game model that includes rest and travel, and benchmarking against betting lines.