Predict ATP tennis match winners with a hybrid Elo + gradient-boosted model
  • Python 97.4%
  • Shell 2.6%
Find a file
2026-06-24 22:31:47 +02:00
src/tennis_pred Fix linter warnings 2026-06-24 22:31:47 +02:00
tests Fix linter warnings 2026-06-24 22:31:47 +02:00
.gitignore Create tennis-pred CLI train/evaluate/predict 2026-06-24 22:08:01 +02:00
download_tml_data.sh Create tennis-pred CLI train/evaluate/predict 2026-06-24 22:08:01 +02:00
pyproject.toml Fix linter warnings 2026-06-24 22:31:47 +02:00
README.md Create tennis-pred CLI train/evaluate/predict 2026-06-24 22:08:01 +02:00

tennis-pred

Predict the winner of an ATP tennis match from historical match data, using a hybrid model: surface-aware Elo ratings fed into a gradient-boosted classifier (scikit-learn HistGradientBoostingClassifier), with calibrated probabilities.

Data

Match data lives in tml-data/ (TennisMyLife / Jeff Sackmann format, one row per match). Download/refresh it with:

./download_tml_data.sh

Training uses ATP main-tour matches from 2000 onward (challenger files excluded).

Setup

uv sync

Usage

Train the model (loads data, computes Elo, builds features, trains, saves to artifacts/):

uv run tennis-pred train

Show test-set metrics vs. naive baselines:

uv run tennis-pred evaluate

Predict a matchup:

uv run tennis-pred predict "Novak Djokovic" "Carlos Alcaraz" --surface Clay --best-of 5
uv run tennis-pred predict "Jannik Sinner" "Daniil Medvedev" -s Hard -l M

Options: --surface/-s (Hard/Clay/Grass), --level/-l (G, M, 500, 250, A, F, D, O), --best-of/-b (3 or 5). Player names are fuzzy-matched.

How it works

  • data.py — loads/cleans the yearly ATP CSVs, drops walkovers.
  • elo.py — single chronological pass computing overall + per-surface Elo (decaying K-factor), head-to-head, and recent form, recorded before each match (no leakage).
  • features.py — reduces each match to symmetric player1player2 differences with a randomized side and binary target (avoids the winner-first label leak).
  • model.py — chronological train/validation/test split, gradient boosting, probability calibration, metrics vs. baselines, persistence.
  • registry.py — fuzzy player-name resolution + per-player latest-state snapshots for scoring hypothetical matchups.
  • cli.py — the tennis-pred command.

Tests

uv run pytest

Caveats

Predictions use each player's most recently observed state (Elo, rank, age, form). Players with little ATP main-tour history fall back toward default ratings and are flagged as low-confidence.