gnosisx commited on
Commit
4bbca57
·
verified ·
1 Parent(s): 7bdf0d0

Upload model_predictor.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. model_predictor.py +214 -0
model_predictor.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Model predictor that loads from Hugging Face and makes predictions
3
+ """
4
+ import joblib
5
+ import numpy as np
6
+ from typing import Dict, List
7
+ import requests
8
+ import os
9
+ from math import factorial
10
+
11
+ class EPLPredictor:
12
+ def __init__(self, use_local=False):
13
+ """Initialize predictor with models from HF or local"""
14
+ self.models = {}
15
+ self.model_repo = "gnosisx/epl-ensemble-1x2"
16
+ self.use_local = use_local
17
+
18
+ # Feature names for reference
19
+ self.feature_names = [
20
+ "xg_h_l5", "xga_h_l5", "xg_a_l5", "xga_a_l5",
21
+ "elo_diff", "home_adv", "rest_h", "rest_a",
22
+ "h2h_h_wins", "h2h_draws", "form_h", "form_a"
23
+ ]
24
+
25
+ self.load_models()
26
+
27
+ def load_models(self):
28
+ """Load models from Hugging Face or local files"""
29
+ if self.use_local:
30
+ # Load from local files
31
+ self.models['poisson_home'] = joblib.load('poisson_home.joblib')
32
+ self.models['poisson_away'] = joblib.load('poisson_away.joblib')
33
+ self.models['xgboost'] = joblib.load('xgb_1x2.joblib')
34
+ else:
35
+ # Download from Hugging Face
36
+ for model_name in ['poisson_home.joblib', 'poisson_away.joblib', 'xgb_1x2.joblib']:
37
+ url = f"https://huggingface.co/{self.model_repo}/resolve/main/{model_name}"
38
+ response = requests.get(url)
39
+ if response.status_code == 200:
40
+ # Save temporarily and load
41
+ temp_path = f"/tmp/{model_name}"
42
+ with open(temp_path, 'wb') as f:
43
+ f.write(response.content)
44
+
45
+ key = model_name.replace('.joblib', '')
46
+ self.models[key] = joblib.load(temp_path)
47
+ else:
48
+ raise Exception(f"Failed to download {model_name} from Hugging Face")
49
+
50
+ def build_features_from_odds(self, home_team: str, away_team: str,
51
+ best_odds: Dict) -> np.ndarray:
52
+ """Build features from current odds and team names"""
53
+ # Extract implied probabilities from odds
54
+ h_odds = best_odds.get('H', {}).get('odds', 2.0)
55
+ d_odds = best_odds.get('D', {}).get('odds', 3.5)
56
+ a_odds = best_odds.get('A', {}).get('odds', 3.0)
57
+
58
+ # Calculate implied probabilities
59
+ total = 1/h_odds + 1/d_odds + 1/a_odds
60
+ h_prob = (1/h_odds) / total
61
+ a_prob = (1/a_odds) / total
62
+
63
+ # Estimate features from odds
64
+ # These are approximations based on market sentiment
65
+ features = [
66
+ 1.8 * h_prob + 0.8, # xg_h_l5 - home expected goals
67
+ 1.2 * (1 - h_prob) + 0.5, # xga_h_l5 - home expected goals against
68
+ 1.5 * a_prob + 0.7, # xg_a_l5 - away expected goals
69
+ 1.3 * (1 - a_prob) + 0.6, # xga_a_l5 - away expected goals against
70
+ (h_prob - a_prob) * 200, # elo_diff - estimated from odds
71
+ 1.0, # home_adv - always 1 for home team
72
+ 6, # rest_h - default rest days
73
+ 6, # rest_a - default rest days
74
+ 2, # h2h_h_wins - default
75
+ 2, # h2h_draws - default
76
+ h_prob * 3, # form_h - estimated from odds
77
+ a_prob * 3 # form_a - estimated from odds
78
+ ]
79
+
80
+ return np.array(features).reshape(1, -1)
81
+
82
+ def poisson_to_outcome_probs(self, lambda_h: float, lambda_a: float,
83
+ max_goals: int = 10) -> Dict[str, float]:
84
+ """Convert Poisson parameters to outcome probabilities"""
85
+ prob_matrix = np.zeros((max_goals + 1, max_goals + 1))
86
+
87
+ for i in range(max_goals + 1):
88
+ for j in range(max_goals + 1):
89
+ prob_h = np.exp(-lambda_h) * (lambda_h ** i) / factorial(i)
90
+ prob_a = np.exp(-lambda_a) * (lambda_a ** j) / factorial(j)
91
+ prob_matrix[i, j] = prob_h * prob_a
92
+
93
+ # Calculate H/D/A probabilities
94
+ p_home = np.sum(np.triu(prob_matrix, 1))
95
+ p_draw = np.sum(np.diag(prob_matrix))
96
+ p_away = np.sum(np.tril(prob_matrix, -1))
97
+
98
+ # Also calculate over/under 2.5
99
+ over_25 = 0
100
+ for i in range(max_goals + 1):
101
+ for j in range(max_goals + 1):
102
+ if i + j > 2.5:
103
+ over_25 += prob_matrix[i, j]
104
+
105
+ # BTTS probability
106
+ btts = 1 - (prob_matrix[0, :].sum() + prob_matrix[:, 0].sum() - prob_matrix[0, 0])
107
+
108
+ return {
109
+ 'H': p_home,
110
+ 'D': p_draw,
111
+ 'A': p_away,
112
+ 'over25': over_25,
113
+ 'btts': btts
114
+ }
115
+
116
+ def predict(self, home_team: str, away_team: str, best_odds: Dict = None,
117
+ features: np.ndarray = None) -> Dict:
118
+ """Make predictions for a match"""
119
+ # Build or use provided features
120
+ if features is None:
121
+ features = self.build_features_from_odds(home_team, away_team, best_odds or {})
122
+
123
+ # 1. Poisson predictions
124
+ lambda_h = self.models['poisson_home'].predict(features)[0]
125
+ lambda_a = self.models['poisson_away'].predict(features)[0]
126
+ poisson_probs = self.poisson_to_outcome_probs(lambda_h, lambda_a)
127
+
128
+ # 2. XGBoost predictions
129
+ xgb_probs_array = self.models['xgboost'].predict_proba(features)[0]
130
+ xgb_probs = {
131
+ 'H': xgb_probs_array[0],
132
+ 'D': xgb_probs_array[1],
133
+ 'A': xgb_probs_array[2]
134
+ }
135
+
136
+ # 3. Ensemble (weighted average)
137
+ weights = {'poisson': 0.4, 'xgboost': 0.6}
138
+
139
+ ensemble_probs = {}
140
+ for outcome in ['H', 'D', 'A']:
141
+ ensemble_probs[outcome] = (
142
+ weights['poisson'] * poisson_probs[outcome] +
143
+ weights['xgboost'] * xgb_probs[outcome]
144
+ )
145
+
146
+ # Normalize
147
+ total = sum(ensemble_probs.values())
148
+ for k in ensemble_probs:
149
+ ensemble_probs[k] /= total
150
+
151
+ # Add other markets from Poisson
152
+ ensemble_probs['over25'] = poisson_probs['over25']
153
+ ensemble_probs['btts'] = poisson_probs['btts']
154
+
155
+ return {
156
+ 'ensemble': ensemble_probs,
157
+ 'poisson': poisson_probs,
158
+ 'xgboost': xgb_probs,
159
+ 'expected_goals': {
160
+ 'home': lambda_h,
161
+ 'away': lambda_a
162
+ }
163
+ }
164
+
165
+ def calculate_value(self, model_prob: float, odds: float,
166
+ kelly_fraction: float = 0.25) -> Dict:
167
+ """Calculate value bet metrics"""
168
+ implied_prob = 1 / odds
169
+ edge = ((model_prob - implied_prob) / implied_prob) * 100
170
+
171
+ if edge > 0:
172
+ # Kelly criterion
173
+ kelly = (model_prob * odds - 1) / (odds - 1)
174
+ adjusted_kelly = max(0, kelly * kelly_fraction)
175
+
176
+ return {
177
+ 'has_value': True,
178
+ 'edge': edge,
179
+ 'kelly_pct': adjusted_kelly * 100,
180
+ 'implied_prob': implied_prob,
181
+ 'model_prob': model_prob
182
+ }
183
+
184
+ return {
185
+ 'has_value': False,
186
+ 'edge': edge,
187
+ 'kelly_pct': 0,
188
+ 'implied_prob': implied_prob,
189
+ 'model_prob': model_prob
190
+ }
191
+
192
+
193
+ # Example usage
194
+ if __name__ == "__main__":
195
+ predictor = EPLPredictor(use_local=True)
196
+
197
+ # Example prediction
198
+ result = predictor.predict(
199
+ home_team="Liverpool",
200
+ away_team="Everton",
201
+ best_odds={
202
+ 'H': {'odds': 1.48},
203
+ 'D': {'odds': 5.0},
204
+ 'A': {'odds': 8.0}
205
+ }
206
+ )
207
+
208
+ print("Ensemble probabilities:")
209
+ for outcome, prob in result['ensemble'].items():
210
+ print(f" {outcome}: {prob:.1%}")
211
+
212
+ print(f"\nExpected goals:")
213
+ print(f" Home: {result['expected_goals']['home']:.2f}")
214
+ print(f" Away: {result['expected_goals']['away']:.2f}")