← Back to blog

Elo Ratings for Sports Betting: A Complete Python Guide

2026-03-24 elo python model tutorial beginner

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.

Want to build this yourself?

The ZenHodl course teaches you to build a complete prediction market bot in 6 notebooks.