Skip to content
← 博客

安全地比较 Kalshi 和 Polymarket

dino.markets

比较 Kalshi 和 Polymarket,要从合约开始。两条挂牌可能用日常语言描述同一个事件,却在不同的结果、观察窗口、数据来源或边界条件上结算。如果在检查这些条款之前,就把两者的价格放在一起比较,得到的只是一个看似精确、实则针对两个不同主张的比较。

Dino 会把相关挂牌匹配进一个 Market 对象,并把结算差异一并保留在这个对象上。本教程使用 Python SDK 查看一对匹配结果,只有在完成合约检查之后,才计算价格差距。

拉取一个匹配的 Market 及其详情

创建一个免费 API 密钥并安装客户端,然后请求一个已开放的市场系列。本示例使用天气类别,因为它在披露信息中的站点和分组差异很容易看出来:

export DINO_API_KEY=sk_live_...
pip install dino-markets
from dino_markets import Dino

client = Dino()
catalog = client.markets(
    category="weather",
    market_type="weather_high",
    status="open,live",
    limit=20,
)

summary = next(
    (
        market
        for market in catalog["markets"]
        if market["coverage"]["kalshi"]
        and market["coverage"]["polymarket"]
    ),
    None,
)
if summary is None:
    raise SystemExit("No two-venue weather comparison is available right now.")

market = client.market(summary["id"])

列表结果提供一个稳定的 Dino id,以及当前的延迟价格。client.market(id) 会为完整比较添加标准化上下文、平台身份、市场深度和结算披露信息。

把披露信息变成审查清单

下面这个函数会在计算任何价格之前,展示四项检查:

def comparison_checks(market):
    settlement = market.get("settlement") or {}
    risks = settlement.get("risks") or []
    served_blocking = [
        risk
        for risk in risks
        if risk.get("severity") == "high"
    ]
    fully_priced = bool(market["outcomes"]) and all(
        outcome.get("kalshi") is not None
        and outcome.get("polymarket") is not None
        for outcome in market["outcomes"]
    )

    return {
        "entity_match_served": market["match"] == "high_confidence",
        "full_price_coverage": fully_priced,
        "settlement_parity": settlement.get("parity") is True,
        "served_blocking_risks": served_blocking,
    }

checks = comparison_checks(market)
print(checks)

match 回答的是身份问题:Dino 是否有足够证据证明这些挂牌代表同一个事件或背后的主张,从而可以把它们放在一起提供。完整价格覆盖回答的是,每个结果是否都能在两个平台之间进行比较。结算一致性和已提供的风险列表则说明,匹配的结果是否在相容的条款下结算付款,具体还要参照下面的取消披露信息。

一个 Market 可以是有效的跨平台比较,同时又带有结算方面的保留说明。在这种情况下,它的 signal 会保持为 spread,这条保留说明应该和价格放在一起展示。已确认的 Opportunity 有更高的门槛,来自单独的 /v2/arbitrage 资源。

分层阅读合约条款

把 Market 详情字段当作一个固定的审查顺序:

检查项字段需要验证的内容
身份matchtitle、类别上下文两个平台的挂牌指向同一个事件或主张。
结果outcomes[].keyoutcomes[].name分组覆盖的是同一组真实世界的结果。
覆盖范围coverage、结果价格在比较需要的每个地方,两个平台都有真实价格。
结算settlement.paritysettlement.risks[]settlement.kalshi_textsettlement.poly_text结算来源和截止边界相容或已被披露。阅读各平台的规则文本,了解取消条款的具体措辞。

对外提供的 settlement.risks[] 会有意省略内部的 cancel_* 事件。请阅读 kalshi_textpoly_text,了解各平台的取消条款。Dino 会先根据完整的内部风险集合计算 settlement.paritysignal,然后再从公开的风险列表中过滤掉这些事件。

天气类市场说明了为什么每一层都很重要。两份每日最高气温合约可能标注的是同一个城市,却依据不同的气象站结算。它们的温度区间也可能相差 1 度。Dino 会保留每个平台实际的分组,报告重叠部分,而不是凭空生成一个合并价格。

加密货币阈值类市场则暴露出另一种边界问题。一个平台可能按"等于或高于"某个阈值结算,另一个平台则使用"高于"。这样一来,恰好在阈值上出现的价格差距,指向的其实是获胜条件不同的合约,即使两边的标题提到的是同一种资产和同一个数字。

最后再计算价格差异

在审查完披露信息之后,针对每个价格完整的结果,计算观测到的差异:

for outcome in market["outcomes"]:
    kalshi = outcome.get("kalshi")
    polymarket = outcome.get("polymarket")
    if kalshi is None or polymarket is None:
        continue

    gap_pts = abs(kalshi - polymarket) * 100
    print(
        outcome["name"],
        f"Kalshi {kalshi:.2f}",
        f"Polymarket {polymarket:.2f}",
        f"gap {gap_pts:.1f} pts",
    )

结果是 Market 的 priced_at 时间戳上的一个跨平台价差,说明两个观测价格之间相差多远。真正执行交易还需要当前的订单簿数据、可用规模、费用,以及相容的结算条款。这是开发者数据,不构成交易或金融建议。

身份匹配和触发条件的判定标准,请参阅 Dino 如何匹配市场。上面用到的每个字段,请参阅 Market 对象参考文档实时看板以人类可读的形式展示相同的披露信息。

博客中的更多文章