# Cross-Platform Price Monitor

Use this script to compare market pricing across multiple platforms.

### What this script does

* connects to Kalshi, Polymarket, and Outpoll
* subscribes to live order book data
* shows the current best bid in one terminal view

Start with [Setup & Helpers](/api/rest-api/python-examples/setup-and-helpers.md) if you still need the shared prerequisites.

{% code title="cross\_platform\_price\_monitor.py" %}

```python
#!/usr/bin/env python3
"""Compare best bids across Kalshi, Polymarket, and Outpoll in real time."""
import asyncio, json, time
import websockets
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
import base64

# ─── Configuration ───────────────────────────────────────────────────
# Kalshi (requires RSA key pair — see https://docs.kalshi.com)
KALSHI_KEY_ID = "your_kalshi_api_key_id"
KALSHI_PRIVATE_KEY_PATH = "path/to/kalshi_private_key.pem"
KALSHI_MARKET_TICKER = "KXBTC-25DEC31-T100000"  # example: BTC $100k by end 2025
KALSHI_WS = "wss://api.elections.kalshi.com/trade-api/ws/v2"

# Polymarket (no auth for market data)
POLYMARKET_ASSET_ID = "12345..."  # YES token asset_id from gamma-api.polymarket.com/markets
POLYMARKET_WS = "wss://ws-subscriptions-clob.polymarket.com/ws/market"

# Outpoll (no auth for order book)
OUTPOLL_EVENT_ID = "54ccea1a-16fd-469c-8018-84b375243e8a"
OUTPOLL_OUTCOME_ID = "b21f6fd8-b9d1-4b9f-bb79-ef141e3dcb76"  # YES runner
OUTPOLL_WS = "wss://order-book.outpoll.com/event/ws"

# ─── Shared state ────────────────────────────────────────────────────
prices = {
    "Kalshi": None,
    "Polymarket": None,
    "Outpoll": None,
}


def display():
    """Print the current best bids."""
    print("\033[H\033[J", end="")  # clear terminal
    print("═══ Cross-Platform Best Bid ═══\n")
    for platform, bid in prices.items():
        if bid is not None:
            print(f"  {platform:12s} Best Bid: ${bid:.2f}")
        else:
            print(f"  {platform:12s} Best Bid: waiting...")
    print(f"\n  Updated: {time.strftime('%H:%M:%S')}")


# ─── Kalshi ──────────────────────────────────────────────────────────
async def watch_kalshi():
    with open(KALSHI_PRIVATE_KEY_PATH, "rb") as f:
        private_key = serialization.load_pem_private_key(f.read(), password=None)

    # Sign handshake
    ts = str(int(time.time() * 1000))
    msg_to_sign = ts + "GET" + "/trade-api/ws/v2"
    sig = private_key.sign(
        msg_to_sign.encode(),
        padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH),
        hashes.SHA256(),
    )
    headers = {
        "KALSHI-ACCESS-KEY": KALSHI_KEY_ID,
        "KALSHI-ACCESS-SIGNATURE": base64.b64encode(sig).decode(),
        "KALSHI-ACCESS-TIMESTAMP": ts,
    }

    async with websockets.connect(KALSHI_WS, additional_headers=headers) as ws:
        # Subscribe to orderbook
        await ws.send(json.dumps({
            "id": 1,
            "cmd": "subscribe",
            "params": {"channels": ["orderbook_delta"], "market_ticker": KALSHI_MARKET_TICKER},
        }))

        yes_book = {}  # price_str -> size

        async for raw in ws:
            data = json.loads(raw)
            msg_type = data.get("type")

            if msg_type == "orderbook_snapshot":
                yes_book = {p: float(s) for p, s in data["msg"].get("yes_dollars_fp", [])}
            elif msg_type == "orderbook_delta" and data["msg"]["side"] == "yes":
                p = data["msg"]["price_dollars"]
                delta = float(data["msg"]["delta_fp"])
                yes_book[p] = yes_book.get(p, 0) + delta
                if yes_book[p] <= 0:
                    yes_book.pop(p, None)

            if yes_book:
                prices["Kalshi"] = float(max(yes_book, key=float))
                display()


# ─── Polymarket ──────────────────────────────────────────────────────
async def watch_polymarket():
    async with websockets.connect(POLYMARKET_WS) as ws:
        await ws.send(json.dumps({
            "assets_ids": [POLYMARKET_ASSET_ID],
            "type": "market",
            "custom_feature_enabled": True,
        }))

        # Heartbeat
        async def heartbeat():
            while True:
                await asyncio.sleep(10)
                try:
                    await ws.send("PING")
                except Exception:
                    break

        hb = asyncio.create_task(heartbeat())

        try:
            async for raw in ws:
                if raw == "PONG":
                    continue
                events = json.loads(raw)
                if not isinstance(events, list):
                    events = [events]

                for event in events:
                    if event.get("event_type") == "book":
                        bids = event.get("bids", [])
                        if bids:
                            prices["Polymarket"] = float(max(bids, key=lambda x: float(x["price"]))["price"])
                            display()

                    elif event.get("event_type") == "price_change":
                        for pc in event.get("price_changes", []):
                            if pc.get("best_bid"):
                                prices["Polymarket"] = float(pc["best_bid"])
                                display()
        finally:
            hb.cancel()


# ─── Outpoll ─────────────────────────────────────────────────────────
async def watch_outpoll():
    async with websockets.connect(OUTPOLL_WS) as ws:
        await ws.send(json.dumps({"t": "SUBSCRIBE", "e": OUTPOLL_EVENT_ID}))

        async for raw in ws:
            data = json.loads(raw)
            if data["t"] not in ("PRICES_DATA", "PRICES_DATA_DELTA"):
                continue
            for p in data["d"]:
                if p["o"] == OUTPOLL_OUTCOME_ID:
                    prices["Outpoll"] = p["b"]
                    display()


# ─── Main ────────────────────────────────────────────────────────────
async def main():
    display()
    await asyncio.gather(
        watch_kalshi(),
        watch_polymarket(),
        watch_outpoll(),
    )

asyncio.run(main())
```

{% endcode %}

### Example output

{% code title="terminal.txt" %}

```
═══ Cross-Platform Best Bid ═══

  Kalshi       Best Bid: $0.44
  Polymarket   Best Bid: $0.43
  Outpoll      Best Bid: $0.44

  Updated: 14:32:05
```

{% endcode %}

{% hint style="info" %}
For a meaningful cross-platform comparison, Kalshi and Polymarket IDs must point to the same underlying event. Use the [Kalshi API](https://docs.kalshi.com) and [Polymarket CLOB API](https://docs.polymarket.com) to resolve matching market identifiers.
{% endhint %}

### Related pages

* [Order Book](/api/rest-api/websockets/order-book.md)
* [WebSocket API Reference](/api/rest-api/websockets/websocket-api-reference.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.outpoll.com/api/rest-api/python-examples/cross-platform-price-monitor.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
