Monitor Kalshi and Polymarket with Python
Kalshi and Polymarket publish separate catalog and pricing models, each with its own identifiers. A useful cross-venue monitor has to establish that two listings refer to the same claim before it puts their prices on one row. Dino handles that matching step and returns one Market object with both venue prices, so the Python program can focus on filtering and display.
This tutorial builds a small terminal monitor with the official dino-markets package. It ranks open matched markets from the delayed REST snapshot available on Free. The final section adds the real-time stream for updates as they happen.
Install the client and create a key
Create a free API key and store it in an environment variable. Then install the Python package:
export DINO_API_KEY=sk_live_...
pip install dino-markets
The client reads DINO_API_KEY automatically. The first request pulls the open and live Market catalog, ordered by the widest observed cross-venue gap:
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 prices are held about two minutes behind live on every plan. That makes the result suitable for catalog browsing and research. It can also bootstrap a local view before stream updates arrive.
Show the two venue prices on each outcome
Every Market keeps the same outcomes[] shape across sports and non-sports categories. Each outcome carries both venue prices plus a cheaper field. A missing price is None, so filter it explicitly before calculating a difference:
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 is an observation across the Market's outcomes. It does not mean an executable arbitrage exists. Confirmed Opportunities live in the separate client.find_arbitrage() resource and carry selected legs, fees, size, and snapshot freshness.
Inspect settlement before using a row
The list response is lean. Pull one Market by its stable id when the monitor needs venue terms and settlement risks:
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"])
The id is Dino's stable identifier and is the right key for cached rows. The display title and venue identifiers can change when a listing is re-analysed. Re-pull detail when updated_at changes so the monitor picks up a revised identity or settlement disclosure.
Add real-time updates
Install the optional stream dependency and use the SDK's reconnecting helper:
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))
The helper mints a fresh short-lived ticket for each connection attempt and recovers recent publications after a brief disconnect. A Free key receives the curated sample channel. Every paid tier receives the full Market stream.
Keep the REST bootstrap in the program even after adding WebSocket updates. It gives the monitor a starting snapshot, while stream frames apply current price changes to those cached Market ids. The WebSocket reference documents recovery behavior, and the Market object reference covers every field used here.
The finished monitor reads matched data and displays price differences. It does not place orders on either venue. This is developer data, not trading or financial advice. Create a free key to run the REST version first.
More from the blog
Kalshi + Polymarket MCP for Claude and Cursor
Connect Claude or Cursor to Dino's Kalshi and Polymarket MCP server and inspect matched markets with settlement context.
Compare Kalshi and Polymarket safely
Compare Kalshi and Polymarket contracts with settlement risks and contract differences attached to every observed price.
Institutional money reached prediction markets. Commodities are the test
ICE and Tradeweb have backed Polymarket and Kalshi. Commodity liquidity will show whether institutional infrastructure becomes trading flow.