添加乘法no vig

This commit is contained in:
2025-10-25 19:49:26 +08:00
parent 79a94925cf
commit 4a8b1945e7

View File

@@ -17,7 +17,7 @@ def moneyline_to_prob(moneyline_odds: int) -> float:
def prob_to_moneyline(probability: float) -> int: def prob_to_moneyline(probability: float) -> int:
"""将概率转换为 Moneyline 赔率 (四舍五入到最接近的整数).""" """将概率转换为 Moneyline 赔率 (四舍五入到最接近的整数)."""
if not 0 < probability < 1: if not 0 < probability < 1:
# 概率为 0 或 1 对应无限或 -100 的 Moneyline 赔率这里简化处理实际中极少遇到精确的 0 或 1 # 概率为 0 或 1 对应无限或 -100 的 Moneyline 赔率,这里简化处理,实际中极少遇到精确的 0 或 1
if math.isclose(probability, 0): if math.isclose(probability, 0):
return float("inf") return float("inf")
if math.isclose(probability, 1): if math.isclose(probability, 1):
@@ -38,6 +38,46 @@ def prob_to_moneyline(probability: float) -> int:
return round(-100 / (1 / probability - 1), 2) return round(-100 / (1 / probability - 1), 2)
def calculate_no_vig_moneyline_multir(moneyline_odds_list: list[int]) -> list[int]:
"""
通过乘法法(乘法归一法,Multiplicative Rescaling)对任意赔率组计算去除vig(取消庄家水钱)后的moneyline赔率。
具体步骤:
1. 将各moneyline赔率转换为隐含概率(带vig)。
2. 将所有隐含概率加总,得到带vig的总和sum_p,通常 >1。
3. 对每个概率除以总和,得到去vig的无水概率。
4. 将该去vig概率再换算回moneyline赔率。
示例:
输入: [+120, -150]
步骤:
implied_probs = [100/220, 150/250] = [0.4545, 0.6]
sum_p = 1.0545
novig_probs = [0.4545/1.0545, 0.6/1.0545]
回转moneyline
输出: 去vig后的moneyline列表
参数:
moneyline_odds_list (list[int]): 原始moneyline赔率列表
返回:
list[int]: 对应的去vig后moneyline赔率列表
"""
if not moneyline_odds_list:
return []
# 步骤1: 计算带vig的隐含概率
implied_probabilities = [moneyline_to_prob(odds) for odds in moneyline_odds_list]
# 步骤2: 计算总概率,理论上>1表示有vig
prob_total = sum(implied_probabilities)
# 步骤3: 每个概率除以总和,得到去vig的概率(归一化)
no_vig_probabilities = [prob / prob_total for prob in implied_probabilities]
# 步骤4: 概率转回moneyline赔率
no_vig_moneyline_odds = [prob_to_moneyline(p_novig) for p_novig in no_vig_probabilities]
return no_vig_moneyline_odds
def calculate_no_vig_moneyline_power(moneyline_odds_list: list[int]) -> list[int]: def calculate_no_vig_moneyline_power(moneyline_odds_list: list[int]) -> list[int]:
""" """
使用 Power Method (根据提供的文献描述) 计算无 vigorish 的 Moneyline 赔率。 使用 Power Method (根据提供的文献描述) 计算无 vigorish 的 Moneyline 赔率。
@@ -55,13 +95,13 @@ def calculate_no_vig_moneyline_power(moneyline_odds_list: list[int]) -> list[int
# 1. 将 Moneyline 赔率转换为隐含概率 (pi) # 1. 将 Moneyline 赔率转换为隐含概率 (pi)
implied_probabilities = [moneyline_to_prob(odds) for odds in moneyline_odds_list] implied_probabilities = [moneyline_to_prob(odds) for odds in moneyline_odds_list]
# 确保所有隐含概率都大于 0否则无法进行幂运算或取对数 (数值求解时可能涉及) # 确保所有隐含概率都大于 0,否则无法进行幂运算或取对数 (数值求解时可能涉及)
if any(p <= 0 for p in implied_probabilities): if any(p <= 0 for p in implied_probabilities):
raise ValueError("All implied probabilities must be positive.") raise ValueError("All implied probabilities must be positive.")
total_implied_probability = sum(implied_probabilities) total_implied_probability = sum(implied_probabilities)
# 如果总概率 <= 1说明没有 vig 或 vig 极少直接返回原始赔率 # 如果总概率 <= 1,说明没有 vig 或 vig 极少,直接返回原始赔率
if total_implied_probability <= 1: if total_implied_probability <= 1:
print( print(
"Warning: Input odds already have little or no vig. Returning original odds." "Warning: Input odds already have little or no vig. Returning original odds."
@@ -72,20 +112,20 @@ def calculate_no_vig_moneyline_power(moneyline_odds_list: list[int]) -> list[int
# 我们要找到 k 使得 sum(pi^k) = 1 # 我们要找到 k 使得 sum(pi^k) = 1
# 由于 sum(pi) > 1 且 pi < 1, 我们需要 k > 1 才能让 pi^k < pi, 从而降低总和至 1。 # 由于 sum(pi) > 1 且 pi < 1, 我们需要 k > 1 才能让 pi^k < pi, 从而降低总和至 1。
def sum_pi_pow_k_minus_1(k): def sum_pi_pow_k_minus_1(k):
# fsolve 传入的 k 是一个数组我们需要取其第一个元素 # fsolve 传入的 k 是一个数组,我们需要取其第一个元素
k_val = k[0] if isinstance(k, (list, tuple)) else k k_val = k[0] if isinstance(k, (list, tuple)) else k
# 计算 sum(pi^k) # 计算 sum(pi^k)
sum_val = sum(p**k_val for p in implied_probabilities) sum_val = sum(p**k_val for p in implied_probabilities)
return sum_val - 1 # 我们的目标是让这个函数等于 0 return sum_val - 1 # 我们的目标是让这个函数等于 0
# 3. 寻找 k 使得 f(k) = 0 # 3. 寻找 k 使得 f(k) = 0
# 我们知道当 k=1 时总和是 total_implied_probability (>1)。 # 我们知道当 k=1 时,总和是 total_implied_probability (>1)。
# 当 k 增大时sum(pi^k) 会减小。所以根 k 应该大于 1。 # 当 k 增大时,sum(pi^k) 会减小。所以根 k 应该大于 1。
# 提供一个合理的初始猜测值给 fsolve例如 1.1 或 1.5 # 提供一个合理的初始猜测值给 fsolve,例如 1.1 或 1.5
initial_k_guess = [1.1] # fsolve 期望一个数组作为初始猜测 initial_k_guess = [1.1] # fsolve 期望一个数组作为初始猜测
# 使用 fsolve 寻找 k # 使用 fsolve 寻找 k
# fsolve 返回一个数组即使只有一个解 # fsolve 返回一个数组,即使只有一个解
k_solution = fsolve(sum_pi_pow_k_minus_1, initial_k_guess) k_solution = fsolve(sum_pi_pow_k_minus_1, initial_k_guess)
# 提取求解到的 k 值 # 提取求解到的 k 值
@@ -94,15 +134,15 @@ def calculate_no_vig_moneyline_power(moneyline_odds_list: list[int]) -> list[int
# 4. 计算无 Vig 概率 pi_novig = pi^k # 4. 计算无 Vig 概率 pi_novig = pi^k
no_vig_probabilities = [p**k for p in implied_probabilities] no_vig_probabilities = [p**k for p in implied_probabilities]
# 由于浮点数精度和数值求解的限制最终的概率之和可能不严格等于 1。 # 由于浮点数精度和数值求解的限制,最终的概率之和可能不严格等于 1。
# 虽然理论上由 k 的定义保证总和为 1但实践中检查一下是有益的。 # 虽然理论上由 k 的定义保证总和为 1,但实践中检查一下是有益的。
final_sum_check = sum(no_vig_probabilities) final_sum_check = sum(no_vig_probabilities)
if not math.isclose(final_sum_check, 1.0, abs_tol=1e-9): if not math.isclose(final_sum_check, 1.0, abs_tol=1e-9):
print( print(
f"Warning: Final no-vig probabilities sum to {final_sum_check:.6f}, expected 1.0. Sum may need slight re-normalization." f"Warning: Final no-vig probabilities sum to {final_sum_check:.6f}, expected 1.0. Sum may need slight re-normalization."
) )
# 理论上 Power Method 的定义保证了总和为 1但如果因为数值误差偏离较多 # 理论上 Power Method 的定义保证了总和为 1,但如果因为数值误差偏离较多,
# 可以选择在这里进行最后的比例调整但严格遵循方法定义是不需要的。 # 可以选择在这里进行最后的比例调整,但严格遵循方法定义是不需要的。
# 5. 将无 Vig 概率转换回 Moneyline 赔率 # 5. 将无 Vig 概率转换回 Moneyline 赔率
no_vig_moneyline_odds = [ no_vig_moneyline_odds = [