-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathelo_system.py
More file actions
170 lines (136 loc) · 6.95 KB
/
elo_system.py
File metadata and controls
170 lines (136 loc) · 6.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import math
from datetime import datetime
from typing import Dict, List, Tuple
class EloSystem:
def __init__(self, base_rating=1500, k_value=32, home_advantage=100, season_transition_rate=0.7):
self.base_rating = base_rating
self.k_value = k_value
self.home_advantage = home_advantage
self.season_transition_rate = season_transition_rate
self.team_ratings = {}
self.team_games_played = {}
self.season_start_date = None
def add_team(self, team_name, initial_rating=None):
if initial_rating is None:
initial_rating = self.base_rating
self.team_ratings[team_name] = initial_rating
self.team_games_played[team_name] = 0
def get_team_rating(self, team_name):
return self.team_ratings.get(team_name, self.base_rating)
def transition_to_new_season(self, season_start_date):
self.season_start_date = datetime.strptime(season_start_date, "%Y-%m-%d")
for team in self.team_ratings:
old_rating = self.team_ratings[team]
new_rating = old_rating * self.season_transition_rate + self.base_rating * (1 - self.season_transition_rate)
self.team_ratings[team] = new_rating
self.team_games_played[team] = 0
def get_expected_score(self, team1_rating, team2_rating, home_team, team1_name):
if home_team == team1_name:
rating_diff = team1_rating + self.home_advantage - team2_rating
else:
rating_diff = team1_rating - (team2_rating + self.home_advantage)
return 1 / (1 + 10 ** (-rating_diff / 400))
def determine_game_result(self, team1_score, team2_score):
if team1_score > team2_score:
return "team1", team1_score - team2_score
elif team2_score > team1_score:
return "team2", team2_score - team1_score
else:
return "tie", 0
def calculate_decisiveness_factor(self, margin, overtime=False): # placeholder, need better way to calculate this (probably with a neural network)
if overtime:
return 0.8
if margin >= 10:
return 1.3
elif margin >= 7:
return 1.2
elif margin >= 5:
return 1.1
elif margin >= 3:
return 1.0
else:
return 0.9
def get_dynamic_k_value(self, game_date, is_tournament=False): # placeholder, for same reason above
if not self.season_start_date:
return self.k_value
game_datetime = datetime.strptime(game_date, "%Y-%m-%d")
days_into_season = (game_datetime - self.season_start_date).days
if days_into_season < 30:
k_multiplier = 1.5
elif days_into_season < 90:
k_multiplier = 1.0
else:
k_multiplier = 0.8
if is_tournament:
k_multiplier *= 1.2
return self.k_value * k_multiplier
def update_ratings(self, team1_name, team2_name, team1_score, team2_score, game_date, home_team, overtime=False, is_tournament=False):
if team1_name not in self.team_ratings:
self.add_team(team1_name)
if team2_name not in self.team_ratings:
self.add_team(team2_name)
team1_rating = self.team_ratings[team1_name]
team2_rating = self.team_ratings[team2_name]
team1_expected = self.get_expected_score(team1_rating, team2_rating, home_team, team1_name)
team2_expected = 1 - team1_expected
winner, margin = self.determine_game_result(team1_score, team2_score)
if winner == "team1":
team1_actual, team2_actual = 1.0, 0.0
elif winner == "team2":
team1_actual, team2_actual = 0.0, 1.0
else:
team1_actual, team2_actual = 0.5, 0.5
k_value = self.get_dynamic_k_value(game_date, is_tournament)
decisiveness = self.calculate_decisiveness_factor(margin, overtime)
team1_change = k_value * decisiveness * (team1_actual - team1_expected)
team2_change = k_value * decisiveness * (team2_actual - team2_expected)
self.team_ratings[team1_name] += team1_change
self.team_ratings[team2_name] += team2_change
self.team_games_played[team1_name] += 1
self.team_games_played[team2_name] += 1
return {team1_name: self.team_ratings[team1_name], team2_name: self.team_ratings[team2_name]}
def process_scraper_games(self, games):
for game in games:
team1_score = int(game['team1score']) if str(game['team1score']).isdigit() else 0
team2_score = int(game['team2score']) if str(game['team2score']).isdigit() else 0
if isinstance(game['date'], datetime):
game_date = game['date'].strftime("%Y-%m-%d")
else:
game_date = str(game['date'])
home_team = game['team2']
self.update_ratings(
game['team1'], game['team2'], team1_score, team2_score,
game_date, home_team, False, False
)
def get_top_teams(self, n=25):
return sorted(self.team_ratings.items(), key=lambda x: x[1], reverse=True)[:n]
def run_with_scraper(self): # we can remove this when the web scraping is working
try:
from getGamesResults import getGames
games = getGames()
if games:
self.process_scraper_games([games])
print("Using real scraper data")
else:
self._use_test_data()
except:
self.test_data()
print(f"Total teams: {len(self.team_ratings)}")
top_teams = self.get_top_teams(10)
for i, (team, rating) in enumerate(top_teams, 1):
games_played = self.team_games_played.get(team, 0)
print(f"{i:2d}. {team:<15} {rating:6.1f} ({games_played} games)")
return top_teams
def test_data(self):
test_games = [
{"date": "2024-01-01", "team1": "Duke", "team2": "UNC", "team1score": "85", "team2score": "78"},
{"date": "2024-01-02", "team1": "Kentucky", "team2": "Louisville", "team1score": "92", "team2score": "88"},
{"date": "2024-01-03", "team1": "UNC", "team2": "Kentucky", "team1score": "76", "team2score": "82"},
{"date": "2024-01-04", "team1": "Duke", "team2": "Kentucky", "team1score": "88", "team2score": "85"},
{"date": "2024-01-05", "team1": "Louisville", "team2": "UNC", "team1score": "79", "team2score": "83"},
]
self.process_scraper_games(test_games)
print("Using test data (scraper failed)")
if __name__ == "__main__":
elo = EloSystem()
top_teams = elo.run_with_scraper()