- Python 97.4%
- Shell 2.6%
| src/tennis_pred | ||
| tests | ||
| .gitignore | ||
| download_tml_data.sh | ||
| pyproject.toml | ||
| README.md | ||
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 player1−player2 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— thetennis-predcommand.
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.