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

72 lines
3.0 KiB
Python

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:
# 正相關:聯合機率向 max_joint_prob 靠攏
return independent_joint_prob + correlation_coeff * (max_joint_prob - independent_joint_prob)
else:
# 負相關:聯合機率向 min_joint_prob 靠攏
return independent_joint_prob + abs(correlation_coeff) * (min_joint_prob - independent_joint_prob)
@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
}