72 lines
3.0 KiB
Python
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
|
|
}
|