世界杯刚开幕,市场就给了我们一点小小的震撼教育:强队不是不会翻车,只是翻车姿势往往比我们想象得更有创意。
比如西班牙 0:0 佛得角。赛前如果只看实力、排名、阵容深度,大多数人的直觉都会很朴素:西班牙应该能拿下吧?但足球最有意思,也最折磨交易者的地方就在这里。你以为你买的是“强队胜”,实际上你买的是一整段 90 分钟的命运连续剧。前 15 分钟没进,赔率动一下;半小时还是 0:0,市场开始皱眉;到了 70 分钟还没动静,原本看起来稳稳的强队胜,突然就不那么稳了。
这时候会遇到一个很经典的矛盾:套利很好,预测很难。
真正的套利,理论上应该是无风险的。比如找到一组互斥且完备的合约,无论最后发生什么,组合都能兑付,并且买入成本低于兑付金额。听起来很美好,像交易员版本的午睡。但问题也在这里:这样的机会太少,出现时往往很快消失,容量也未必够。
预测则是另一回事。你可以判断强队大概率赢,可以分析阵容、状态、赛程、伤病,也可以看赔率隐含概率,但比赛不会因为我们分析得很认真就配合演出。足球尤其如此,一个早早的红牌、一个门柱、一次离谱的 VAR,都可能让原本漂亮的逻辑变成赛后复盘里的沉默三秒。
所以问题来了:如果纯套利太稀缺,纯预测又太随机,我们能不能站在两者中间?市场本身已经给了我们一些前提概率,比如强队胜的价格、0:0 的价格、0:1 的价格。这些价格不是凭空来的,它们反映了市场对不同路径的集体定价。那我们能不能利用这些前提概率,再加上一点自己的数学模型,构造一个不完美、但更有保护性的组合,去撬开一个“路径收敛”的窗口?
这就是这个想法的起点。
它不是传统的无风险套利,也不是单纯拍脑袋预测。它更像是一种折中:先承认市场给出的主概率有参考价值,再用低概率路径去保护主判断里最脆弱的部分,同时用一个简单模型检查价格是不是还能接受。
假设一场比赛里,巴西明显强于海地。市场给出的巴西胜价格是 0.89,说明巴西赢是一个很强的主路径。但我们不直接只买巴西胜,而是同时观察两个保护路径:0:0 和 0:1。因为对于一个强队胜的判断来说,最难受的早期剧本往往不是所有结果,而是比赛迟迟打不开,或者弱队偷一个。
于是初始组合变成:
text
巴西胜 Yes
0:0 Yes
0:1 Yes
假设价格是:
text
巴西胜 = 0.89
0:0 = 0.016
0:1 = 0.011
三条腿合计成本:
text
C = 0.89 + 0.016 + 0.011 = 0.917
这组东西并不是完备事件。它没有覆盖 1:1、0:2、2:2,也没有覆盖所有奇怪剧本。所以如果持有到终场,它当然可能亏,而且可能亏得很直接。但它有一个很重要的特点:它把市场已经给出的三个概率拼在一起,形成了一个围绕主胜的路径篮子。
如果最终巴西赢,组合兑付 1。如果最终 0:0 或 0:1,也兑付 1。也就是说,它买入的是:
text
巴西胜 ∪ 0:0 ∪ 0:1
只要这些路径之一发生,到期兑付就是 1。成本是 0.917,覆盖路径内有 0.083 的空间。但真正的关键不在终场,而在中途。
如果巴西早早进球,比分来到 1:0,那么 0:0 和 0:1 这两条保护腿基本归零,但巴西胜价格会上升。只要此时巴西胜的可卖价格超过初始总成本,再加上一点目标利润,就可以平掉主胜,把这次组合提前收敛。
公式很简单:
text
止盈条件 = 巴西胜 bid >= 初始总成本 C + 目标利润
比如目标利润设为 0.02:
text
触发价 = 0.917 + 0.02 = 0.937
如果 1:0 之后巴西胜 bid 到了 0.95,那么:
text
锁定利润 = 0.95 - 0.917 = 0.033
这时候我们并不是靠终局开奖赚钱,而是靠比赛路径进入了一个有利分支,把组合提前卖掉。这个过程有点像市场给你开了一扇小窗,你别站在窗边写诗,先把窗户钻过去再说。
但这里还有一个问题:三条腿看起来便宜,就一定值得买吗?不一定。市场价格只是第一层信息。我们还需要一个自己的参考尺子,哪怕它很朴素。
这里我用了最基础的泊松进球模型。
足球比分可以粗略看成两个队在 90 分钟内各自进球的随机过程。假设主队 90 分钟预期进球数是 λ_home,客队预期进球数是 λ_away,那么主队进 i 个球、客队进 j 个球的概率可以写成:
text
P(i, j) = Pois(i; λ_home) × Pois(j; λ_away)
其中:
text
Pois(k; λ) = e^(-λ) × λ^k / k!
它当然很粗糙。足球不是两个独立的抽奖机器,进球后战术会变,红牌会变,心态也会变。但它的好处是简单、透明,至少能让我们不要完全凭感觉下手。
在赛前,泊松模型可以先给我们一个初始参考。我们可以手动给出 λ_home 和 λ_away,也可以让程序从市场的精确比分盘口里反推。比如 0:0、0:1、1:0、1:1、2:0、2:1、3:0 这些比分盘口,每一个都相当于市场给出的一个概率点。程序要找一组 λ_home 和 λ_away,让泊松模型算出来的比分概率,尽量接近这些市场价格。
换句话说,市场在说:“我觉得这些比分大概值这些钱。” 泊松模型在旁边小声说:“那我试着拟合一下,看看这套价格背后隐含的是怎样的进球强度。”
策略里可以配置:
python
CALIBRATE_LAMBDA_FROM_MARKET = True
MODEL_SCORE_SAMPLES = "0-0,0-1,1-0,1-1,2-0,2-1,3-0"
这些比分只用于建模,不参与下单。真正交易的腿仍然是:
text
目标队胜
0:0
0:1
代码里用网格搜索拟合 λ。它不复杂,但很直观:
python
def fit_lambdas_from_score_markets(quotes, model_score_legs, event_state=None):
samples = []
live_score = event_state.get("score_tuple") if event_state else None
minute = event_state.get("elapsed") if event_state else None
is_live = bool(live_score and minute not in [None, ""])
for leg in model_score_legs:
q = quotes.get(leg["name"])
p_market = quote_probability(q)
if p_market is None:
continue
target_h, target_a = parse_score_text(leg["score"])
if is_live:
current_h, current_a = live_score
if current_h > target_h or current_a > target_a:
continue
samples.append((target_h - current_h, target_a - current_a, p_market, leg["score"]))
else:
samples.append((target_h, target_a, p_market, leg["score"]))
if len(samples) < 2:
return None
best = None
for ih in range(5, 501, 5):
lh = ih / 100.0
for ia in range(5, 501, 5):
la = ia / 100.0
err = 0.0
for add_h, add_a, p_market, _score in samples:
p_model = poisson_pmf(add_h, lh) * poisson_pmf(add_a, la)
err += (p_model - p_market) ** 2
if best is None or err < best["err"]:
best = {"lambda_home": lh, "lambda_away": la, "err": err, "samples": samples}
if is_live:
ratio = max(0.01, max(0.0, 90.0 - float(minute)) / 90.0)
best["lambda_home"] = best["lambda_home"] / ratio
best["lambda_away"] = best["lambda_away"] / ratio
best["source"] = "live_score_markets"
else:
best["source"] = "pre_match_score_markets"
return best
赛前,这个 λ 只是初始校准。因为比赛还没开始,没有实时路径可更新,比分默认还是 0:0,剩余时间就是 90 分钟。这个阶段我们用它做入场过滤:
text
模型覆盖概率 = P(目标队胜) + P(0:0) + P(0:1)
然后要求:
text
模型覆盖概率 - 市场成本 >= 安全边际
只有当市场价格足够便宜,并且模型也认为这个篮子有一点优势时,才允许开仓。
真正有意思的是比赛开始之后。
比赛一旦开始,泊松模型就不再是静态的。假设比赛到了第 30 分钟,当前比分还是 0:0,那么剩余时间只有 60 分钟,未来的进球强度要按剩余时间缩小:
text
λ_home_remaining = λ_home × (90 - t) / 90
λ_away_remaining = λ_away × (90 - t) / 90
如果当前比分已经是 1:0,那 0:0 和 0:1 就不可能再发生。此时模型要从当前比分出发,只计算剩余时间里还会进多少球。终场 2:0、2:1、3:0、1:1 这些仍然可能发生的比分,才有资格参与新的估计。
这就是实时泊松更新的关键:不是每分钟机械地把赛前 λ 缩小,而是结合当前比分、剩余时间和仍然可能发生的精确比分盘口,重新估计接下来这场比赛还可能怎么走。
最终策略变成三层判断。
第一层是市场成本:
text
win_ask + 0:0_ask + 0:1_ask <= 最大允许成本
第二层是泊松过滤:
text
模型覆盖概率 - 市场成本 >= 安全边际
第三层是路径收敛:
text
当前组合 bid 价值 >= 初始成本 + 目标利润
只有第一层和第二层同时满足,才考虑入场。入场之后,不再靠模型幻想终局,而是用真实盘口 bid 判断能不能平仓。模型负责提高开仓质量,盘口负责决定能不能兑现。
合约查找也尽量做得简单。Polymarket 的世界杯市场 slug 很有规律。比如一场比赛的 event slug 是:
python
EVENT_SLUG = "fifwc-aut-jor-2026-06-17"
如果我们保护 Jordan 胜,对应的胜利合约后缀是:
python
WIN_SUFFIX = "jor"
那么交易所需的三个合约可以直接拼出来:
python
def yes_symbol(slug):
return slug + "_USDC.Yes"
def build_legs():
legs = [
{
"name": "win",
"slug": EVENT_SLUG + "-" + WIN_SUFFIX,
"symbol": yes_symbol(EVENT_SLUG + "-" + WIN_SUFFIX),
"kind": "win",
}
]
for score in parse_scores(PROTECT_SCORES):
legs.append(
{
"name": "score_" + score.replace("-", "_"),
"slug": EVENT_SLUG + "-exact-score-" + score,
"symbol": yes_symbol(EVENT_SLUG + "-exact-score-" + score),
"kind": "score",
"score": score,
}
)
return legs
这里尽量不要用球队名去模糊搜索。比如搜 Jordan,很容易搜出 Michael B. Jordan、Jordan Pickford、Jordan Spieth,最后足球策略还没开始,娱乐圈、英格兰门将和高尔夫先坐一桌了。用 event slug 拼合约,干净很多。
比赛中还需要实时比分。第一版可以直接从 Polymarket Gamma event 里取:
python
def get_event_state():
data = get_json(GAMMA_BASE + "/events", slug=EVENT_SLUG)
e = data[0]
return {
"title": e.get("title"),
"score": e.get("score"),
"score_tuple": parse_score(e.get("score")),
"elapsed": e.get("elapsed"),
"period": e.get("period"),
"live": bool(e.get("live")),
"ended": bool(e.get("ended")),
"start_time": e.get("startTime"),
}
组合当前可卖价值用真实盘口 bid:
python
def basket_bid_value(legs, quotes):
total = 0.0
for leg in legs:
pos = positions.get(leg["name"], {})
amount = float(pos.get("amount", 0))
if amount <= 0:
continue
q = quotes.get(leg["name"])
if not q or q["bid"] is None:
continue
total += amount * q["bid"]
return total
止盈判断:
python
cost = current_position_cost()
value = basket_bid_value(legs, quotes)
target = cost + TARGET_PROFIT * SHARES
if value >= target:
close_all()
这就是最小版本。它没有假装自己已经解决了足球预测,也没有宣称可以稳定套利。它只是尝试把“套利”和“预测”之间那条灰色地带拿出来研究一下。
套利的美好在于确定性,但确定性很稀缺。预测的诱惑在于空间大,但随机性也大。这个策略做的事情,是把市场给出的预测价格当作材料,用一个高概率主判断,加上两个低概率保护路径,再用泊松模型做一层粗过滤,尝试构造一个可以在比赛过程中提前收敛的窗口。
风险必须说清楚。这个组合不是完备事件,不是无风险套利。它适合强弱差距较大、主路径足够清晰、保护路径价格足够低的比赛。如果比赛本身五五开,或者保护腿已经很贵,硬做就没有意义。更麻烦的是,它没有覆盖所有不利路径,比如 1:1、0:2、2:2,都可能让组合出现明显亏损。所以必须设置仓位上限、最大亏损、止损规则,不能因为“看起来像套利”就把它当成套利。
泊松模型也不是魔法。它只是一个非常朴素的数学底座,帮我们从“我感觉这个价格不错”,往前走一步,变成“至少用一个透明模型检查过这个价格”。它会错,市场也会错,我们自己更会错。交易里最诚实的部分,大概就是承认这些都会错,然后尽量让错误不要一次把桌子掀了。
这次尝试很浅。它更像是在问一个问题:当纯套利机会越来越少,纯预测又太难时,我们能不能借助市场已经给出的概率,构造一些更有结构的交易?不是幻想消灭风险,而是把风险拆开,看清楚它从哪几条路过来。
也许这不是答案,但它是一个挺值得继续挖的小洞。预测负责告诉我们哪条主路可能更宽,套利思想提醒我们不要只看方向,还要看组合结构。两者之间,或许还有很多可以探索的空间。
策略源码: Polymarket 足球路径收敛策略
- 1




