Skip to content

Repository files navigation

Polyticker

Raw data collector for Polymarket BTC up/down 5-minute prediction markets. Captures all WebSocket events needed to replay markets for backtesting.

What it collects

Every 5-minute interval, Polyticker captures:

  • Chainlink oracle prices — the on-chain BTC/USD reference used for market resolution
  • Binance BTC/USDT prices — exchange price feed via Polymarket's RTDS
  • CLOB market events — order book changes, trades, and resolution events
  • Event metadata — full Gamma API response with market parameters

All data is stored as raw WebSocket payloads in JSONL format. Nothing is normalized or derived — you get exactly what the APIs send.

Quickstart

Requires Python 3.12+ and uv.

git clone https://github.com/adriandlam/polyticker
cd polyticker
uv sync
uv run python main.py

The collector waits for the next 5-minute boundary, then starts recording. Data is written to data/btc-updown-5m/. Press Ctrl+C to stop.

How it works

  1. RTDS WebSocket connects once and stays connected, buffering Chainlink and Binance price events in memory
  2. Each interval, a Gamma API call fetches event metadata (token IDs, market parameters)
  3. A Market Channel WebSocket opens per interval, streaming CLOB events directly to disk
  4. At interval end (+30s grace for resolution), the RTDS buffer is flushed to disk and meta.json is written
  5. Connection gaps are tracked — meta.json flags whether the interval has complete data

Tests

uv run pytest tests/ -v

Data Schema

All raw data is stored as verbatim WebSocket payloads. Folder names are Unix seconds (interval start epoch). Each message has a source timestamp field (Unix ms) for chronological ordering.

Data structure

Data is stored in R2 as flat per-interval archives:

btc-updown-5m/
├── 1771982700.tar.gz        # flattened archive for one interval
├── 1771982700.meta.json     # collection completeness (sidecar)
├── 1771983000.tar.gz        # next interval (300s later)
├── 1771983000.meta.json
└── ...                      # ~288 intervals per day

Each .tar.gz contains (flattened, no subdirectories):

  • event.json — Gamma API event response
  • chainlink.jsonl — Chainlink oracle ticks (RTDS)
  • binance.jsonl — Binance BTC price ticks (RTDS)
  • market.jsonl — CLOB market channel events

event.json

Full Gamma API response, stored verbatim. Never rewritten.

Source: GET https://gamma-api.polymarket.com/events?slug={ticker}

Key fields:

Field Use
ticker Extract interval epoch: int(ticker.split("-")[-1])
markets[0].eventStartTime Interval start
markets[0].endDate Interval end
markets[0].clobTokenIds Token IDs for WS subscription
markets[0].outcomePrices Initial implied probabilities
markets[0].feeType Fee tier
markets[0].makerBaseFee / takerBaseFee Fees in bps

meta.json

Written at end of each interval. Reports collection health.

{
  "interval_epoch": 1771982700,
  "complete": true,
  "rtds_gaps": [],
  "market_channel_gaps": [],
  "collected_at": "2026-02-25T01:30:30Z"
}

complete is true when both RTDS and Market Channel had zero connection gaps during the interval.

chainlink.jsonl

Raw RTDS payloads for Chainlink BTC/USD. Resolution source of truth — Polymarket uses Chainlink to determine up/down outcome.

{"topic":"crypto_prices_chainlink","type":"update","payload":{"symbol":"btc/usd","price":"96220.30","timestamp":1771982700123},"timestamp":1771982700130}

Source: RTDS crypto_prices_chainlink, filter btc/usd.

binance.jsonl

Raw RTDS payloads for Binance BTC/USDT price updates.

{"topic":"crypto_prices","type":"update","payload":{"symbol":"btcusdt","price":"96233.80","change24h":"-1.23","volume24h":"45000.5"},"timestamp":1771982700089}

Source: RTDS crypto_prices, type update.

Note: Captures price ticks only (no trades or order book). For richer Binance data, connect directly to Binance WebSocket.

market.jsonl

Raw CLOB market channel payloads. All events from market creation through resolution.

{"event_type":"price_change","asset_id":"11452395...","price":"0.48","timestamp":"1771982700100"}
{"event_type":"last_trade_price","asset_id":"11452395...","price":"0.48","timestamp":"1771982700200"}
{"event_type":"market_resolved","asset_id":"11452395...","winning_outcome":"Up","timestamp":"1771983000500"}
event_type Description
price_change Order placed/cancelled
last_trade_price Trade executed
tick_size_change Tick size updated
market_resolved Market settled

Source: wss://ws-subscriptions-clob.polymarket.com/ws/market with custom_feature_enabled: true.


Replay

import io, json, tarfile
from pathlib import Path

for archive in sorted(Path("data/btc-updown-5m").glob("*.tar.gz")):
    epoch = archive.stem  # e.g. "1771982700"
    meta_path = archive.with_suffix("").with_suffix(".meta.json")
    meta = json.loads(meta_path.read_text())
    if not meta["complete"]:
        continue

    tar = tarfile.open(archive, "r:gz")
    files = {m.name: tar.extractfile(m).read() for m in tar.getmembers() if m.isfile()}

    events = []
    for key in ("chainlink.jsonl", "binance.jsonl", "market.jsonl"):
        for line in files.get(key, b"").decode().strip().split("\n"):
            if line:
                events.append(json.loads(line))
    events.sort(key=lambda e: int(e["timestamp"]))

    for event in events:
        pass  # build state, backtest your model

WebSocket subscriptions

CLOB Market Channel

{"assets_ids": ["<YES_token_id>", "<NO_token_id>"], "type": "market", "custom_feature_enabled": true}

Endpoint: wss://ws-subscriptions-clob.polymarket.com/ws/market

RTDS — Chainlink BTC/USD

{"action": "subscribe", "subscriptions": [{"topic": "crypto_prices_chainlink", "type": "*", "filters": "{\"symbol\":\"btc/usd\"}"}]}

RTDS — Binance BTCUSDT

{"action": "subscribe", "subscriptions": [{"topic": "crypto_prices", "type": "update"}]}

Endpoint: wss://ws-live-data.polymarket.com


License

MIT

Releases

Packages

Contributors

Languages