Polymarket Kalshi 套利:筛查两个预测市场之间的价差

在 Kalshi 与 Polymarket 之间筛查价差的交易台,需要同一事件的两边报价,以及这两份合约是否按相同条件赔付。Narwhal 会在一次响应中返回报价、各交易场所的规则文本、结算来源和成交量,以及任何规则标记。

在同一事件上读取两个交易场所

筛查从两个交易场所都定了价的同一事件开始。下面的请求读取 2026 年 12 月的美联储决议,并按每个结果返回各交易场所的报价。响应只保留加息 25 bps 这一结果,并省略了交易场所的规则文本,筛查会在下一步读取它。

curl,两个交易场所上的同一事件
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/predictions/scalar/usa-fed-2026-12"
响应,已精简
{
  "event_id": "usa-fed-2026-12",
  "question": "Federal Reserve rate decision, 9 December 2026",
  "status": "trading",
  "closes_at": "2026-12-09T18:59:00Z",
  "resolves_at": "2026-12-09T19:00:00Z",
  "as_of": "2026-11-03T03:09:00Z",
  "stale": false,
  "venues": [
    {
      "venue": "kalshi",
      "url": "https://kalshi.com/markets/kxfeddecision/kxfeddecision-26dec",
      "rules_url": "https://assets.kalshi.com/contract_terms/FEDDECISION.pdf",
      "settlement_source": "Federal Reserve",
      "weight": "0.0624",
      "volume_24h_usd": "32792.43",
      "open_interest_usd": "426866.37"
    },
    {
      "venue": "polymarket",
      "url": "https://polymarket.com/event/fed-decision-in-december-20260729232808632",
      "settlement_source": "Federal Reserve",
      "weight": "0.9376",
      "volume_24h_usd": "665452.98"
    }
  ],
  "outcomes": [
    {
      "key": "hike_25",
      "label": "Hike of 25 bps",
      "lower": "12.5",
      "upper": "37.5",
      "probability": "0.7185",
      "quotes": [
        {
          "venue": "kalshi",
          "probability": "0.7073",
          "market_ids": [
            "KXFEDDECISION-26DEC-H25"
          ]
        },
        {
          "venue": "polymarket",
          "probability": "0.7192",
          "market_ids": [
            "111662129652828266889360374468657435151479595863413519836161861837506140152890"
          ]
        }
      ]
    }
  ]
}

该事件只有一个 as_of、一个 closes_at 和一个 resolves_at,所以两边报价取自同一时刻。对于加息这一结果,Kalshi 报价 0.7073,Polymarket 报价 0.7192,价差为 0.0119。每个交易场所条目都带有 settlement_source 和 volume_24h_usd,Kalshi 还带有 open_interest_usd。weight 表示各交易场所在总体 probability 中所占的比重。

规则不同时,把价差当作两份合约来解读

价差只说明两个数字不同,并不说明两份合约的赔付条件相同。衰退问题就是响应自己做了标记的例子:该交易场所条目会带有 excluded_reason 和一条说明差异所在的 match 备注。

curl,一个规则存在差异的二元问题
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/predictions/binary/usa-recession-2026"
响应,已精简
{
  "event_id": "usa-recession-2026",
  "question": "US recession by end of 2026?",
  "as_of": "2026-11-03T03:09:00Z",
  "stale": false,
  "probability": "0.0550",
  "quotes": [
    {
      "venue": "kalshi",
      "probability": "0.0250",
      "market_ids": [
        "KXRECSSNBER-26"
      ]
    },
    {
      "venue": "polymarket",
      "probability": "0.0550",
      "market_ids": [
        "100379208559626151022751801118534484742123694725746262280150222742563282755057"
      ]
    }
  ],
  "venues": [
    {
      "venue": "kalshi",
      "url": "https://kalshi.com/markets/kxrecssnber/kxrecssnber-26",
      "rules_url": "https://assets.kalshi.com/contract_terms/NBERRECSS.pdf",
      "settlement_source": "Bureau of Economic Analysis",
      "excluded_reason": "rules",
      "match": {
        "differences": [
          "rules"
        ],
        "note": "Kalshi resolves on two negative GDP quarters only; Polymarket also counts an NBER recession call"
      },
      "volume_24h_usd": "125.68",
      "open_interest_usd": "17994.22"
    },
    {
      "venue": "polymarket",
      "url": "https://polymarket.com/event/us-recession-by-end-of-2026",
      "settlement_source": "Bureau of Economic Analysis; NBER",
      "weight": "1.0000",
      "volume_24h_usd": "4195.50"
    }
  ]
}

Kalshi 报价 0.0250,Polymarket 报价 0.0550,价差为 0.0300。总体 probability 为 0.0550,这只是 Polymarket 的报价,因为 Kalshi 因 rules 被排除。备注说明,Kalshi 只在 GDP 连续两个季度为负时才结算,而 Polymarket 还会把研究机构作出的衰退判定计入。两边的结算来源也不同。筛查应当把这个价差从候选清单中剔除,或标记为不同的合约。

筛查每个结果并写入一行日志

下面的脚本读取一份事件列表,对两个交易场所都有报价的每个结果,计算 Polymarket 减去 Kalshi 的价差,并为每个结果打印一行,附上交易台用来筛选的标记。

Python,跨多个事件的一次扫描
import csv, json, os, sys, urllib.request
from decimal import Decimal

def get(path):
    req = urllib.request.Request("https://api.narwhalapi.com" + path,
                                 headers={"Authorization": "Bearer " + os.environ["NARWHAL_API_KEY"]})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)

def scan(shape, event_id):
    e = get(f"/v1/predictions/{shape}/{event_id}")
    venues = {v["venue"]: v for v in e["venues"]}
    flags = [v["venue"] + ":" + ",".join(v.get("match", {}).get("differences", [])) for v in e["venues"] if v.get("excluded_reason")]
    sources = {v["settlement_source"] for v in e["venues"]}
    rows = e["outcomes"] if shape == "scalar" else [{"key": "yes", "quotes": e["quotes"]}]
    for row in rows:
        q = {x["venue"]: Decimal(x["probability"]) for x in row["quotes"]}
        if len(q) < 2:
            continue
        yield [e["as_of"], e["stale"], event_id, row["key"], q["kalshi"], q["polymarket"],
               q["polymarket"] - q["kalshi"], len(sources), ";".join(flags) or "none",
               venues["kalshi"]["volume_24h_usd"], venues["polymarket"]["volume_24h_usd"]]

out = csv.writer(sys.stdout)
out.writerow(["as_of", "stale", "event", "outcome", "kalshi", "polymarket", "gap", "sources", "flags", "k_vol_24h", "p_vol_24h"])
for shape, event_id in [("scalar", "usa-fed-2026-12"), ("binary", "usa-recession-2026")]:
    out.writerows(scan(shape, event_id))
产出
as_of,stale,event,outcome,kalshi,polymarket,gap,sources,flags,k_vol_24h,p_vol_24h
2026-11-03T03:09:00Z,False,usa-fed-2026-12,cut_50_plus,0.0049,0.0035,-0.0014,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,cut_25,0.0146,0.0055,-0.0091,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,hold,0.2586,0.2530,-0.0056,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,hike_25,0.7073,0.7192,0.0119,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,hike_50_plus,0.0146,0.0188,0.0042,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-recession-2026,yes,0.0250,0.0550,0.0300,2,kalshi:rules,125.68,4195.50

五个 Fed 行共用同一个结算来源且没有标记,因此在结算上可以比较。每个交易场所的五个报价加起来为 1.0000,所以价差之和为零。衰退行有两个结算来源,并带有 kalshi:rules 标记。规则措辞仍然需要人工或 diff 来核对:Kalshi 的文本称,会议取消时“维持不变”这一结果按 Yes 结算;而 Polymarket 的文本则把变动到不在清单上的水平向上取整到最接近的 25 bps,并且在下一次例会结束前仍无声明时,按“维持不变”区间结算。

每次扫描要记录什么

扫描只有在日后可以重放时才有用。请把输入与价差一并记录下来。

字段记录原因
as_of 和 stale两边报价的时间,以及事件数据是否为最新
event_id 和结果的 key所比较的确切合约对
两边报价和价差数值,以字符串或小数形式保存
每个报价的 market_ids每个数字背后的交易场所合约
excluded_reason 和 match价差为何属于不同的合约
各交易场所的 settlement_source结果是否由同一机构决定
volume_24h_usd, open_interest_usd报价背后有多少交易量支撑
每份 rules_text 的哈希值两次扫描之间的规则修改会被发现

Fed 的例子说明了为什么要记录规模。在这次扫描中,Kalshi 的 24 小时成交量是 32,792.43 美元,Polymarket 的是 665,452.98,同样的价差背后的深度相差悬殊。衰退行中 Kalshi 的成交量为 125.68。

出现价差是否意味着可以按这些价格成交?

API 并未这样表示,本页也不对收益作任何主张。报价是概率,不是订单。响应里没有费率表,没有订单簿深度,也没有交易台可以成交的价格。筛查只是把需要细读的事件清单缩小。

各交易场所的权重能告诉交易台什么?

weight 是各交易场所在总体 probability 中所占的份额,各权重之和为 1。在这次扫描中,Kalshi 为 0.0624,Polymarket 为 0.9376。把它们应用到维持不变的报价上得到 0.25335,而总体值为 0.2533。某个交易场所的权重很大,意味着总体值主要跟随该场所,所以比较报价的筛查应当使用各交易场所自己的报价,而不是总体值。

如果交易场所对结果的划分不同怎么办?

交易场所条目会在 match.differences 中报告 outcome_set,并在 match 备注中说明合并方式。此时 quotes 中的报价已经对齐到同一行。在按标签比较两份结果列表之前,请先阅读 match。针对单个问题的步骤,见指南对比 Polymarket 与 Kalshi。

各交易场所的收盘时间和结算时间是否不同?

事件只给出一个 closes_at 和一个 resolves_at。各交易场所关于时间的措辞写在 rules_text 中,所以关心这一点的筛查要比较两份文本。该事件的 Polymarket 文本把 FOMC 声明列为结算来源,并规定若未发布声明则回退到“维持不变”区间。

发布于。

后续步骤