Spaces:
Sleeping
Sleeping
| import numpy as np | |
| class RuleBasedModel: | |
| """ | |
| A simple baseline model using Bradley-Terry curve for Elo differences. | |
| """ | |
| def __init__(self): | |
| self.classes_ = np.array([0, 1, 2]) | |
| def fit(self, X, y): | |
| pass # No training required for rule-based baseline | |
| def predict_proba(self, X): | |
| # Base Elo difference | |
| # We assume home advantage is roughly +50 Elo | |
| # Momentum diff is scaled and added to Elo diff | |
| # Apply injury impact: injury_impact is in [-0.1, 0]. Assume -0.1 = -100 Elo points. | |
| home_injury_penalty = X['injury_impact_home'] * 1000 | |
| away_injury_penalty = X['injury_impact_away'] * 1000 | |
| # Apply lineup strength: lineup_strength is in [0.8, 1.2]. | |
| # A 1.2 multiplier represents a much stronger squad than normal. | |
| # Let's say +0.1 = +100 Elo points. | |
| home_lineup_bonus = (X['lineup_strength_home'] - 1.0) * 1000 | |
| away_lineup_bonus = (X['lineup_strength_away'] - 1.0) * 1000 | |
| # Calculate modified Elo diff | |
| base_elo_diff = X['elo_diff'] + (X['home_advantage'] * 50) + (X['momentum_diff'] * 100) | |
| # Add home adjustments and subtract away adjustments | |
| modified_elo_diff = base_elo_diff + home_injury_penalty + home_lineup_bonus - away_injury_penalty - away_lineup_bonus | |
| # Expected win rate for home team (Bradley-Terry curve) | |
| home_expected = 1.0 / (1.0 + 10.0 ** (-modified_elo_diff / 400.0)) | |
| # Empirical draw probability in football is roughly 25-30% on evenly matched teams, | |
| # dropping off as teams become mismatched. | |
| prob_draw = 0.28 * np.exp(-(modified_elo_diff ** 2) / (2 * 400**2)) | |
| # The remainder is split between home and away based on the expected score | |
| remaining = 1.0 - prob_draw | |
| prob_home = remaining * home_expected | |
| prob_away = remaining * (1.0 - home_expected) | |
| # Blend with odds implied probability if available | |
| has_odds = (X['odds_implied_home_prob'] > 0) & (X['odds_implied_away_prob'] > 0) | |
| # If odds exist, we do a 50/50 blend between our modified model and bookmaker odds | |
| # Bookmakers don't explicitly give draw probability in the engineered features directly, | |
| # but we can deduce it as 1 - odds_home - odds_away | |
| odds_prob_home = X['odds_implied_home_prob'] | |
| odds_prob_away = X['odds_implied_away_prob'] | |
| odds_prob_draw = 1.0 - odds_prob_home - odds_prob_away | |
| # Ensure we don't have negative probabilities due to floating point inaccuracies | |
| odds_prob_draw = np.maximum(odds_prob_draw, 0.0) | |
| final_prob_home = np.where(has_odds, 0.5 * prob_home + 0.5 * odds_prob_home, prob_home) | |
| final_prob_away = np.where(has_odds, 0.5 * prob_away + 0.5 * odds_prob_away, prob_away) | |
| final_prob_draw = np.where(has_odds, 0.5 * prob_draw + 0.5 * odds_prob_draw, prob_draw) | |
| # Normalize just to be safe | |
| total = final_prob_home + final_prob_away + final_prob_draw | |
| final_prob_home /= total | |
| final_prob_away /= total | |
| final_prob_draw /= total | |
| return np.column_stack([final_prob_away, final_prob_draw, final_prob_home]) | |
| def predict(self, X): | |
| probs = self.predict_proba(X) | |
| return np.argmax(probs, axis=1) | |