Skip to content
← 博客

用 Python 监控 Kalshi 和 Polymarket

dino.markets

Kalshi 和 Polymarket 各自发布独立的目录和定价模型,并各有自己的标识符。一个有用的跨平台监控程序,必须先确认两条挂牌指向同一个主张,才能把它们的价格放进同一行。Dino 负责处理这一匹配步骤,返回一个同时包含两个平台价格的 Market 对象,因此 Python 程序可以专注于筛选和展示。

本教程使用官方的 dino-markets 包构建一个小型终端监控程序。它会对 Free 套餐提供的延迟 REST 快照中已开放的匹配市场进行排序。最后一节会加入实时数据流,实现事件发生时的即时更新。

安装客户端并创建密钥

创建一个免费 API 密钥,并将它保存到环境变量中。然后安装 Python 包:

export DINO_API_KEY=sk_live_...
pip install dino-markets

客户端会自动读取 DINO_API_KEY。第一个请求会拉取已开放且实时的 Market 目录,按观察到的最大跨平台价格差距排序:

from dino_markets import Dino

client = Dino()

catalog = client.markets(
    status="open,live",
    sort="-spread_pts",
    limit=25,
)

ranked = [
    market
    for market in catalog["markets"]
    if market["spread_pts"] is not None
]
if not ranked:
    raise SystemExit("No priced matched markets are available right now.")

print("as returned by the delayed REST snapshot")
for market in ranked:
    print(
        f'{market["spread_pts"]:>5.1f} pts  '
        f'{market["title"]:<42} '
        f'{market["category"]}/{market["market_type"]}'
    )

REST 价格在所有套餐中都比实时价格延迟约 2 分钟。这使得结果适合用于目录浏览和研究。它也可以在数据流更新到达之前,为本地视图提供一个起点。

在每个结果上展示两个平台的价格

每个 Market 在体育和非体育类别中都保持相同的 outcomes[] 结构。每个结果都带有两个平台的价格,以及一个 cheaper 字段。缺失的价格会是 None,因此在计算差值之前需要显式过滤掉它:

def priced_outcomes(market):
    return [
        outcome
        for outcome in market["outcomes"]
        if outcome["kalshi"] is not None
        and outcome["polymarket"] is not None
    ]

for market in ranked[:10]:
    print(f'\n{market["title"]}')
    for outcome in priced_outcomes(market):
        gap = abs(outcome["kalshi"] - outcome["polymarket"]) * 100
        print(
            f'  {outcome["name"]:<24} '
            f'Kalshi {outcome["kalshi"]:.2f}  '
            f'Polymarket {outcome["polymarket"]:.2f}  '
            f'gap {gap:.1f} pts'
        )

spread_pts 是对 Market 中各个结果的一种观测值,并不意味着存在可执行的套利。已确认的 Opportunity 位于单独的 client.find_arbitrage() 资源中,包含已选定的环节、费用、规模和快照新鲜度。

在使用某一行之前检查结算信息

列表响应是精简的。当监控程序需要平台条款和结算风险时,可以通过稳定的 id 拉取单个 Market:

selected = ranked[0]
detail = client.market(selected["id"])

print(detail["title"], detail["match"], detail["signal"])
for risk in detail.get("settlement", {}).get("risks", []):
    print(risk["severity"], risk["event"], risk["note"])

id 是 Dino 的稳定标识符,是缓存行的正确键。当某条挂牌被重新分析时,展示标题和平台标识符可能会发生变化。当 updated_at 发生变化时,重新拉取详情,让监控程序获取修订后的身份或结算披露信息。

添加实时更新

安装可选的流依赖项,并使用 SDK 的自动重连辅助函数:

pip install "dino-markets[stream]"
import asyncio
from dino_markets import Dino, watch

client = Dino()

def on_publication(channel, frame):
    if frame.get("type") != "price":
        return
    print(
        channel,
        frame["id"],
        frame.get("spread_pts"),
        frame.get("priced_at"),
    )

asyncio.run(watch(client, on_publication))

这个辅助函数会为每次连接尝试生成一张新的短期票据,并在短暂断连后恢复最近的发布内容。Free 密钥会收到精选的 sample 频道。每个付费套餐都会收到完整的 Market 数据流。

即使加入了 WebSocket 更新,也要在程序中保留 REST 引导逻辑。它为监控程序提供一个初始快照,而流帧则会把当前的价格变化应用到那些已缓存的 Market id 上。WebSocket 参考文档记录了恢复行为,Market 对象参考文档涵盖了这里用到的每个字段。

完成后的监控程序会读取匹配数据并展示价格差异。它不会在任何一个平台上下单。这是开发者数据,不构成交易或金融建议。创建一个免费密钥,先运行 REST 版本。

博客中的更多文章