Skip to content
← Blog

Compare Kalshi and Polymarket safely

dino.markets

Comparing Kalshi and Polymarket starts with the contract. Two listings can describe the same event in plain English while paying on different outcomes, observation windows, data sources, or boundaries. Putting their prices beside each other before checking those terms creates a precise-looking comparison of two different claims.

Dino matches related listings into one Market object and keeps settlement differences attached to that object. This tutorial uses the Python SDK to inspect one pair and calculate a price gap only after the contract checks are reviewed.

Pull a matched Market and its detail

Create a free API key and install the client. Then request an open market family. This example uses weather because its station and bucket differences are easy to see in the disclosure:

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"])

The list result supplies a stable Dino id plus the current delayed prices. client.market(id) adds normalized context, venue identities, depth, and settlement disclosure for a full comparison.

Turn the disclosure into a review checklist

The following function makes four checks visible before any price calculation:

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 answers the identity question: Dino has enough evidence that the listings represent the same event or underlying claim to serve them together. Full price coverage answers whether every outcome can be compared across both venues. Settlement parity and the served risk list show whether the matched outcomes pay under compatible terms, subject to the cancellation disclosure below.

A Market can be a valid cross-venue comparison while carrying a settlement caveat. In that case its signal remains spread, and the caveat belongs beside the prices. A confirmed Opportunity has a higher gate and comes from the separate /v2/arbitrage resource.

Read the contract in layers

Use the Market detail fields as a fixed review order:

CheckFieldWhat to verify
Identitymatch, title, category contextBoth venue listings refer to the same event or claim.
Outcomesoutcomes[].key, outcomes[].nameThe buckets cover the same real-world results.
Coveragecoverage, outcome pricesBoth venues have a real price wherever the comparison needs one.
Settlementsettlement.parity, settlement.risks[], settlement.kalshi_text, settlement.poly_textSettlement sources and cutoff boundaries are compatible or disclosed. Read each venue's rules text for cancellation wording.

The served settlement.risks[] deliberately omits internal cancel_* events. Read kalshi_text and poly_text for each venue's cancellation terms. Dino computes settlement.parity and signal from the complete internal risk set before filtering those events from the public risk list.

Weather markets show why each layer matters. Two daily-high contracts may name the same city while settling from different weather stations. Their temperature brackets may also be offset by one degree. Dino keeps each venue's actual buckets and reports overlaps instead of inventing a combined price.

Crypto threshold markets expose a different boundary issue. One venue can settle “at or above” a strike while the other uses “above.” A price gap at the exact threshold then refers to contracts with different winning sets, even when both titles mention the same asset and number.

Calculate price differences last

Once the disclosure has been reviewed, calculate the observed difference per fully priced outcome:

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",
    )

The result is a cross-venue spread at the Market's priced_at timestamp. It says how far apart the two observed prices are. Execution requires current order-book data, available size, fees, and compatible settlement terms. This is developer data, not trading or financial advice.

Use How Dino matches markets for the identity and firing gates, and the Market object reference for every field used above. The live boards show the same disclosure in a human-readable view.

More from the blog