Files
2026FIFAWorldCup/platform/backend/app/analytics/sgp_engine.py

72 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import List, Dict
import math
class SGPCorrelationEngine:
"""
同場串關 (Same Game Parlay) 關聯性與價值探測引擎
"""
@staticmethod
def calculate_joint_probability(prob_A: float, prob_B: float, correlation_coeff: float) -> float:
"""
計算兩個事件的聯合機率 (考慮相關係數)。
使用簡化的二元正態分佈/Copula近似邏輯。
:param prob_A: 事件 A 獨立發生的真實機率
:param prob_B: 事件 B 獨立發生的真實機率
:param correlation_coeff: 相關係數 (-1.0 到 1.0)
"""
if not (-1.0 <= correlation_coeff <= 1.0):
raise ValueError("相關係數必須介於 -1.0 與 1.0 之間")
# 獨立發生的聯合機率
independent_joint_prob = prob_A * prob_B
# 理論最大與最小邊界
max_joint_prob = min(prob_A, prob_B)
min_joint_prob = max(0.0, prob_A + prob_B - 1.0)
if correlation_coeff == 0:
return independent_joint_prob
elif correlation_coeff > 0:
# 正相關:使用 FréchetHoeffding 上界與獨立機率的非線性加權,避免極端值的線性失真
return independent_joint_prob + (max_joint_prob - independent_joint_prob) * (correlation_coeff ** 1.5)
else:
# 負相關:使用 FréchetHoeffding 下界與獨立機率的非線性加權
return independent_joint_prob - (independent_joint_prob - min_joint_prob) * (abs(correlation_coeff) ** 1.5)
@staticmethod
def find_sgp_value(events: List[Dict], bookmaker_sgp_odds: float) -> Dict:
"""
評估 SGP 注單是否具備正期望值。
events 範例: [{'prob': 0.6}, {'prob': 0.4}] 且需自帶兩兩相關係數矩陣 (此處簡化為平均相關性)
"""
if len(events) < 2:
raise ValueError("SGP 必須至少包含兩個事件")
# 假設外部特徵工程已經給出了這組事件的平均正相關係數 (例如 0.4)
# 實務上會透過更複雜的 Monte Carlo 計算,此為展示核心邏輯
avg_correlation = events[0].get('correlation_with_others', 0.0)
current_joint_prob = events[0]['prob']
for i in range(1, len(events)):
current_joint_prob = SGPCorrelationEngine.calculate_joint_probability(
current_joint_prob,
events[i]['prob'],
avg_correlation
)
# 計算莊家隱含機率
implied_prob = 1.0 / bookmaker_sgp_odds
# 計算 EV
ev_percentage = (current_joint_prob * bookmaker_sgp_odds) - 1.0
is_profitable = ev_percentage > 0.05 # 設定 5% 的 EV 門檻
return {
"true_joint_probability": round(current_joint_prob, 4),
"bookmaker_implied_probability": round(implied_prob, 4),
"ev_percentage": round(ev_percentage, 4),
"is_profitable_sgp": is_profitable,
"fair_odds": round(1.0 / current_joint_prob, 2) if current_joint_prob > 0 else 0
}