This commit is contained in:
Executable
+371
@@ -0,0 +1,371 @@
|
||||
"""
|
||||
Poisson Engine - Matematiksel Gol Modeli
|
||||
V9 Model için Poisson dağılımı ile gol olasılıkları hesaplar.
|
||||
|
||||
Özellikler:
|
||||
1. Exact score olasılıkları (0-0, 1-0, 1-1, 2-1, vb.)
|
||||
2. Over/Under olasılıkları (matematiksel)
|
||||
3. BTTS (Karşılıklı Gol) olasılıkları
|
||||
4. Expected Goals (xG) tahmini
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Dict, Tuple, Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
def poisson_prob(lam: float, k: int) -> float:
|
||||
"""
|
||||
Poisson olasılık formülü.
|
||||
P(X = k) = (λ^k * e^(-λ)) / k!
|
||||
"""
|
||||
if lam <= 0:
|
||||
return 1.0 if k == 0 else 0.0
|
||||
return (math.pow(lam, k) * math.exp(-lam)) / math.factorial(k)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoissonPrediction:
|
||||
"""Poisson tahmin sonuçları"""
|
||||
home_xg: float = 0.0 # Ev sahibi beklenen gol
|
||||
away_xg: float = 0.0 # Deplasman beklenen gol
|
||||
total_xg: float = 0.0 # Toplam beklenen gol
|
||||
|
||||
# Maç sonucu olasılıkları
|
||||
home_win_prob: float = 0.0
|
||||
draw_prob: float = 0.0
|
||||
away_win_prob: float = 0.0
|
||||
|
||||
# Alt/Üst olasılıkları
|
||||
over_15_prob: float = 0.0
|
||||
over_25_prob: float = 0.0
|
||||
over_35_prob: float = 0.0
|
||||
under_15_prob: float = 0.0
|
||||
under_25_prob: float = 0.0
|
||||
under_35_prob: float = 0.0
|
||||
|
||||
# BTTS
|
||||
btts_yes_prob: float = 0.0
|
||||
btts_no_prob: float = 0.0
|
||||
|
||||
# En olası skorlar
|
||||
most_likely_scores: list = field(default_factory=list)
|
||||
|
||||
|
||||
class PoissonEngine:
|
||||
"""
|
||||
Poisson dağılımı ile gol olasılıkları hesaplar.
|
||||
İstatistiksel bir yaklaşım - machine learning'den bağımsız.
|
||||
"""
|
||||
|
||||
# Lig bazlı ortalama gol verileri (varsayılan değerler)
|
||||
DEFAULT_HOME_XG = 1.45
|
||||
DEFAULT_AWAY_XG = 1.15
|
||||
DEFAULT_LEAGUE_AVG = 2.60
|
||||
|
||||
def __init__(self):
|
||||
self.max_goals = 7 # Hesaplama için maksimum gol sayısı
|
||||
|
||||
def calculate_xg(
|
||||
self,
|
||||
home_goals_avg: float,
|
||||
home_conceded_avg: float,
|
||||
away_goals_avg: float,
|
||||
away_conceded_avg: float,
|
||||
league_home_avg: float = None,
|
||||
league_away_avg: float = None,
|
||||
league_total_avg: float = None
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
Beklenen gol (xG) hesapla.
|
||||
|
||||
Attack strength * Defense weakness * League average
|
||||
"""
|
||||
# Varsayılan lig ortalamaları
|
||||
if league_home_avg is None:
|
||||
league_home_avg = self.DEFAULT_HOME_XG
|
||||
if league_away_avg is None:
|
||||
league_away_avg = self.DEFAULT_AWAY_XG
|
||||
if league_total_avg is None:
|
||||
league_total_avg = self.DEFAULT_LEAGUE_AVG
|
||||
|
||||
# Güç hesaplamaları
|
||||
# Ev sahibi saldırı gücü = Ev gol ortalaması / Lig ev gol ortalaması
|
||||
home_attack = home_goals_avg / league_home_avg if league_home_avg > 0 else 1.0
|
||||
# Deplasman savunma zayıflığı = Deplasman yenilen gol / Lig deplasman yenilen
|
||||
away_defense = away_conceded_avg / league_away_avg if league_away_avg > 0 else 1.0
|
||||
|
||||
# Deplasman saldırı gücü
|
||||
away_attack = away_goals_avg / league_away_avg if league_away_avg > 0 else 1.0
|
||||
# Ev sahibi savunma zayıflığı
|
||||
home_defense = home_conceded_avg / league_home_avg if league_home_avg > 0 else 1.0
|
||||
|
||||
# Expected Goals
|
||||
home_xg = home_attack * away_defense * league_home_avg
|
||||
away_xg = away_attack * home_defense * league_away_avg
|
||||
|
||||
# Aşırı değerleri sınırla
|
||||
home_xg = max(0.3, min(home_xg, 4.0))
|
||||
away_xg = max(0.2, min(away_xg, 3.5))
|
||||
|
||||
return home_xg, away_xg
|
||||
|
||||
def calculate_score_matrix(
|
||||
self,
|
||||
home_xg: float,
|
||||
away_xg: float
|
||||
) -> Dict[Tuple[int, int], float]:
|
||||
"""
|
||||
Tüm skor kombinasyonlarının olasılıklarını hesapla.
|
||||
|
||||
Returns:
|
||||
Dict[(home_goals, away_goals)] = probability
|
||||
"""
|
||||
matrix = {}
|
||||
|
||||
for home_goals in range(self.max_goals + 1):
|
||||
for away_goals in range(self.max_goals + 1):
|
||||
prob = poisson_prob(home_xg, home_goals) * poisson_prob(away_xg, away_goals)
|
||||
matrix[(home_goals, away_goals)] = prob
|
||||
|
||||
return matrix
|
||||
|
||||
def calculate_match_odds(
|
||||
self,
|
||||
home_xg: float,
|
||||
away_xg: float
|
||||
) -> Tuple[float, float, float]:
|
||||
"""
|
||||
1X2 olasılıklarını hesapla.
|
||||
|
||||
Returns:
|
||||
(home_win, draw, away_win) probabilities
|
||||
"""
|
||||
matrix = self.calculate_score_matrix(home_xg, away_xg)
|
||||
|
||||
home_win = 0.0
|
||||
draw = 0.0
|
||||
away_win = 0.0
|
||||
|
||||
for (h, a), prob in matrix.items():
|
||||
if h > a:
|
||||
home_win += prob
|
||||
elif h == a:
|
||||
draw += prob
|
||||
else:
|
||||
away_win += prob
|
||||
|
||||
# Normalize (toplam 1 olmalı)
|
||||
total = home_win + draw + away_win
|
||||
if total > 0:
|
||||
home_win /= total
|
||||
draw /= total
|
||||
away_win /= total
|
||||
|
||||
return home_win, draw, away_win
|
||||
|
||||
def calculate_over_under(
|
||||
self,
|
||||
home_xg: float,
|
||||
away_xg: float
|
||||
) -> Dict[str, float]:
|
||||
"""
|
||||
Alt/Üst olasılıklarını hesapla.
|
||||
"""
|
||||
matrix = self.calculate_score_matrix(home_xg, away_xg)
|
||||
|
||||
over_15 = 0.0
|
||||
over_25 = 0.0
|
||||
over_35 = 0.0
|
||||
|
||||
for (h, a), prob in matrix.items():
|
||||
total = h + a
|
||||
if total > 1.5:
|
||||
over_15 += prob
|
||||
if total > 2.5:
|
||||
over_25 += prob
|
||||
if total > 3.5:
|
||||
over_35 += prob
|
||||
|
||||
return {
|
||||
"over_15": over_15,
|
||||
"over_25": over_25,
|
||||
"over_35": over_35,
|
||||
"under_15": 1 - over_15,
|
||||
"under_25": 1 - over_25,
|
||||
"under_35": 1 - over_35,
|
||||
}
|
||||
|
||||
def calculate_btts(
|
||||
self,
|
||||
home_xg: float,
|
||||
away_xg: float
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
Karşılıklı Gol (Both Teams To Score) olasılığı.
|
||||
"""
|
||||
# P(Home scores at least 1) = 1 - P(Home scores 0)
|
||||
home_scores = 1 - poisson_prob(home_xg, 0)
|
||||
# P(Away scores at least 1) = 1 - P(Away scores 0)
|
||||
away_scores = 1 - poisson_prob(away_xg, 0)
|
||||
|
||||
# P(BTTS) = P(Home scores) * P(Away scores)
|
||||
btts_yes = home_scores * away_scores
|
||||
btts_no = 1 - btts_yes
|
||||
|
||||
return btts_yes, btts_no
|
||||
|
||||
def get_most_likely_scores(
|
||||
self,
|
||||
home_xg: float,
|
||||
away_xg: float,
|
||||
top_n: int = 5
|
||||
) -> list:
|
||||
"""
|
||||
En olası skorları getir.
|
||||
"""
|
||||
matrix = self.calculate_score_matrix(home_xg, away_xg)
|
||||
|
||||
# Olasılığa göre sırala
|
||||
sorted_scores = sorted(matrix.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
return [
|
||||
{"score": f"{h}-{a}", "probability": round(prob * 100, 1)}
|
||||
for (h, a), prob in sorted_scores[:top_n]
|
||||
]
|
||||
|
||||
def predict(
|
||||
self,
|
||||
home_goals_avg: float,
|
||||
home_conceded_avg: float,
|
||||
away_goals_avg: float,
|
||||
away_conceded_avg: float,
|
||||
league_home_avg: float = None,
|
||||
league_away_avg: float = None,
|
||||
league_total_avg: float = None
|
||||
) -> PoissonPrediction:
|
||||
"""
|
||||
Tam Poisson tahmini.
|
||||
"""
|
||||
prediction = PoissonPrediction()
|
||||
|
||||
# 1. xG hesapla
|
||||
home_xg, away_xg = self.calculate_xg(
|
||||
home_goals_avg, home_conceded_avg,
|
||||
away_goals_avg, away_conceded_avg,
|
||||
league_home_avg, league_away_avg, league_total_avg
|
||||
)
|
||||
|
||||
prediction.home_xg = round(home_xg, 2)
|
||||
prediction.away_xg = round(away_xg, 2)
|
||||
prediction.total_xg = round(home_xg + away_xg, 2)
|
||||
|
||||
# 2. Maç sonucu
|
||||
hw, d, aw = self.calculate_match_odds(home_xg, away_xg)
|
||||
prediction.home_win_prob = round(hw, 3)
|
||||
prediction.draw_prob = round(d, 3)
|
||||
prediction.away_win_prob = round(aw, 3)
|
||||
|
||||
# 3. Alt/Üst
|
||||
ou = self.calculate_over_under(home_xg, away_xg)
|
||||
prediction.over_15_prob = round(ou["over_15"], 3)
|
||||
prediction.over_25_prob = round(ou["over_25"], 3)
|
||||
prediction.over_35_prob = round(ou["over_35"], 3)
|
||||
prediction.under_15_prob = round(ou["under_15"], 3)
|
||||
prediction.under_25_prob = round(ou["under_25"], 3)
|
||||
prediction.under_35_prob = round(ou["under_35"], 3)
|
||||
|
||||
# 4. BTTS
|
||||
btts_yes, btts_no = self.calculate_btts(home_xg, away_xg)
|
||||
prediction.btts_yes_prob = round(btts_yes, 3)
|
||||
prediction.btts_no_prob = round(btts_no, 3)
|
||||
|
||||
# 5. En olası skorlar
|
||||
prediction.most_likely_scores = self.get_most_likely_scores(home_xg, away_xg)
|
||||
|
||||
return prediction
|
||||
|
||||
def get_features(
|
||||
self,
|
||||
home_goals_avg: float,
|
||||
home_conceded_avg: float,
|
||||
away_goals_avg: float,
|
||||
away_conceded_avg: float,
|
||||
league_home_avg: float = None,
|
||||
league_away_avg: float = None,
|
||||
league_total_avg: float = None
|
||||
) -> Dict[str, float]:
|
||||
"""
|
||||
Model için feature dict.
|
||||
"""
|
||||
pred = self.predict(
|
||||
home_goals_avg, home_conceded_avg,
|
||||
away_goals_avg, away_conceded_avg,
|
||||
league_home_avg, league_away_avg, league_total_avg
|
||||
)
|
||||
|
||||
return {
|
||||
"poisson_home_xg": pred.home_xg,
|
||||
"poisson_away_xg": pred.away_xg,
|
||||
"poisson_total_xg": pred.total_xg,
|
||||
"poisson_home_win": pred.home_win_prob,
|
||||
"poisson_draw": pred.draw_prob,
|
||||
"poisson_away_win": pred.away_win_prob,
|
||||
"poisson_over_15": pred.over_15_prob,
|
||||
"poisson_over_25": pred.over_25_prob,
|
||||
"poisson_over_35": pred.over_35_prob,
|
||||
"poisson_btts_yes": pred.btts_yes_prob,
|
||||
}
|
||||
|
||||
|
||||
# Singleton
|
||||
_engine_instance = None
|
||||
|
||||
def get_poisson_engine() -> PoissonEngine:
|
||||
"""Singleton pattern"""
|
||||
global _engine_instance
|
||||
if _engine_instance is None:
|
||||
_engine_instance = PoissonEngine()
|
||||
return _engine_instance
|
||||
|
||||
|
||||
# Test
|
||||
if __name__ == "__main__":
|
||||
engine = get_poisson_engine()
|
||||
|
||||
# Örnek: Güçlü ev sahibi vs zayıf deplasman
|
||||
print("=" * 60)
|
||||
print("POISSON ENGINE TEST")
|
||||
print("Galatasaray (ev) vs Antalyaspor (deplasman)")
|
||||
print("=" * 60)
|
||||
|
||||
pred = engine.predict(
|
||||
home_goals_avg=2.1, # GS ev ortalaması
|
||||
home_conceded_avg=0.8, # GS ev yenilen
|
||||
away_goals_avg=0.9, # Antalya deplasman gol
|
||||
away_conceded_avg=1.8, # Antalya deplasman yenilen
|
||||
league_home_avg=1.5,
|
||||
league_away_avg=1.1
|
||||
)
|
||||
|
||||
print(f"\n📊 Expected Goals:")
|
||||
print(f" Ev Sahibi xG: {pred.home_xg}")
|
||||
print(f" Deplasman xG: {pred.away_xg}")
|
||||
print(f" Toplam xG: {pred.total_xg}")
|
||||
|
||||
print(f"\n🎯 Maç Sonucu:")
|
||||
print(f" 1 (Ev): {pred.home_win_prob*100:.1f}%")
|
||||
print(f" X (Beraberlik): {pred.draw_prob*100:.1f}%")
|
||||
print(f" 2 (Deplasman): {pred.away_win_prob*100:.1f}%")
|
||||
|
||||
print(f"\n⚽ Alt/Üst:")
|
||||
print(f" 2.5 Üst: {pred.over_25_prob*100:.1f}%")
|
||||
print(f" 2.5 Alt: {pred.under_25_prob*100:.1f}%")
|
||||
|
||||
print(f"\n🤝 Karşılıklı Gol:")
|
||||
print(f" KG Var: {pred.btts_yes_prob*100:.1f}%")
|
||||
print(f" KG Yok: {pred.btts_no_prob*100:.1f}%")
|
||||
|
||||
print(f"\n📈 En Olası Skorlar:")
|
||||
for score_data in pred.most_likely_scores:
|
||||
print(f" {score_data['score']}: {score_data['probability']}%")
|
||||
Reference in New Issue
Block a user