賠率看起來像機率,但不是。十進位賠率 2.00 隱含 50%,可是一個市場裡所有結果的隱含機率加起來會超過 100%,多出來的部分就是莊家的抽水(margin)。要判斷賠率是不是好的預測,得先把抽水去掉,再拿去掉之後的機率和實際結果比。這篇用一個公開資料集(CC BY 4.0)示範整個流程,大約 20 行 pandas。
資料集來自研究 Well Calibrated, Unevenly Priced:2024 年 11 月到 2026 年 7 月之間已結算的 18,238 個兩結果市場,每個市場兩邊各一列,共 36,476 筆。欄位只有五個:
| 欄位 | 意思 |
|---|---|
event_month |
比賽月份 |
sport |
運動項目 |
market_family |
市場類型(勝負、讓分、大小分、球員) |
market_implied_probability |
去掉抽水後的隱含機率(比例法) |
outcome |
1 = 發生,0 = 沒發生 |
抽水的大小依市場類型不同。每日莊家抽水指數 量到的 2026 年 7–9 月平均值:兩結果勝負 4.85%、讓分 5.33%、大小分 5.97%、三結果(1X2)7.55%、球員市場 7.68%。量測方法見 方法說明。
import pandas as pd
df = pd.read_csv("betbetter_market_calibration.csv")
bins = [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1.0]
df["band"] = pd.cut(df["market_implied_probability"], bins, right=False)
table = (df.groupby("band", observed=True)
.agg(n=("outcome", "size"),
implied=("market_implied_probability", "mean"),
actual=("outcome", "mean")))
table["gap_pts"] = (table["actual"] - table["implied"]) * 100
print(table.round(3))
研究報告裡兩個端點箱的結果,可以拿來核對你跑出的數字:
| 箱 | n | 平均隱含 | 實際 | 差(百分點) |
|---|---|---|---|---|
| 10–20% | 2,144 | 17.5% | 18.8% | +1.4 |
| 80–90% | 2,157 | 82.5% | 80.9% | −1.6 |
每一箱的差都在 2 個百分點以內:去掉抽水的賠率是相當好的預測。但差不是零,而且方向一致——低於 50% 的結果發生得比價格說的多一點,熱門發生得少一點。這就是文獻裡的「熱門—冷門偏誤」(Griffith 1949 起),在 2026 年的線上市場仍看得到。
import numpy as np
p, y = df["market_implied_probability"].values, df["outcome"].values
brier = np.mean((p - y) ** 2)
rng = np.random.default_rng(0)
boot = [np.mean((p[i] - y[i]) ** 2)
for i in (rng.integers(0, len(p), len(p)) for _ in range(1000))]
print(round(brier, 4), np.percentile(boot, [2.5, 97.5]).round(4))
把同一段程式套到你自己的模型機率上,就能和市場比 Brier 分數。要拿到已經去掉抽水的模型機率,可以用 免 key 的免費 API,有 Python、R 等 13 種語言的用戶端。
本文為教學用途的統計說明,作者 Bet Better Pty Ltd(墨爾本)。不是投注建議。未滿 18 歲請勿投注。