Elo ratings are the simplest feature that matters most in sports prediction. Originally designed for chess, Elo tracks relative team strength using only game results — no stats, no box scores, no subjective rankings.
In our backtests, adding Elo as a feature to our WP model improved trading profit by 2-3c per trade. That's huge.
The Core Formula
def expected_score(rating_a, rating_b):
"""Probability that team A beats team B."""
return 1.0 / (1.0 + 10 ** ((rating_b - rating_a) / 400))
def update_elo(rating_a, rating_b, a_won, k=20):
"""Update ratings after a game."""
expected = expected_score(rating_a, rating_b)
actual = 1.0 if a_won else 0.0
new_a = rating_a + k * (actual - expected)
new_b = rating_b + k * (expected - actual) # Zero-sum
return new_a, new_b
Every team starts at 1500. Win against a stronger opponent → big rating increase. Lose to a weaker one → big decrease. The 400 divisor controls sensitivity.
Home Court Advantage
Home teams win ~58% of NBA games. We account for this by adding a bonus to the home team's rating when computing expected score:
def expected_score_with_home(rating_home, rating_away, home_advantage=70):
return 1.0 / (1.0 + 10 ** ((rating_away - (rating_home + home_advantage)) / 400))
The home_advantage of 70 Elo points ≈ 58% win probability for equally-rated teams. This varies by sport: NBA ~70, NCAAMB ~100, NFL ~50.
K-Factor: How Fast Ratings Change
K controls how reactive ratings are to new results: - K=20: Standard. Good balance of stability and responsiveness. - K=32: More reactive. Better for college sports where rosters turn over. - K=10: Very stable. Better for leagues with long seasons (MLB).
Season Resets
Rosters change between seasons. Without a reset, a team that was great last year carries that rating even after losing key players. The fix:
def season_reset(ratings, regression=0.75):
"""Regress all ratings toward 1500 between seasons."""
return {team: 1500 + regression * (elo - 1500) for team, elo in ratings.items()}
regression=0.75 means each team keeps 75% of their distance from average. A 1700-rated team becomes 1650 after reset.
Using Elo for Prediction Markets
The Elo difference between two teams is the key feature:
elo_diff = elo_ratings[home_team] - elo_ratings[away_team]
In our WP model, elo_diff is one of 6-8 features predicting win probability. A positive diff means the home team is stronger. Combined with score differential, time remaining, and period, it produces fair probabilities that disagree with market prices — which is where the money is.
Results
Our Elo system tracks 900+ teams across 7 sports. Key stats: - NCAAMB: 907 teams, home advantage ~100 Elo points - NBA: 30 teams, extremely stable ratings (top teams clearly separated) - NFL: 32 teams, high K-factor due to small sample (17 games/season)
Build It Yourself
This article covers the concept. If you want the full working implementation: - Module 2 of our course builds the complete Elo system from scratch in a Jupyter notebook - Module 1 is free — start with the data pipeline, then build Elo on top
Part of the ZenHodl blog. We write about sports analytics, prediction markets, and building trading bots with Python.