72 lines
3.1 KiB
Python
72 lines
3.1 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:
|
||
# 正相關:使用 Fréchet–Hoeffding 上界與獨立機率的非線性加權,避免極端值的線性失真
|
||
return independent_joint_prob + (max_joint_prob - independent_joint_prob) * (correlation_coeff ** 1.5)
|
||
else:
|
||
# 負相關:使用 Fréchet–Hoeffding 下界與獨立機率的非線性加權
|
||
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
|
||
}
|