Raw data collector for Polymarket BTC up/down 5-minute prediction markets. Captures all WebSocket events needed to replay markets for backtesting.
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.
Requires Python 3.12+ and uv.
git clone https://github.com/adriandlam/polyticker
cd polyticker
uv sync
uv run python main.pyThe collector waits for the next 5-minute boundary, then starts recording. Data is written to data/btc-updown-5m/. Press Ctrl+C to stop.
- RTDS WebSocket connects once and stays connected, buffering Chainlink and Binance price events in memory
- Each interval, a Gamma API call fetches event metadata (token IDs, market parameters)
- A Market Channel WebSocket opens per interval, streaming CLOB events directly to disk
- At interval end (+30s grace for resolution), the RTDS buffer is flushed to disk and
meta.jsonis written - Connection gaps are tracked —
meta.jsonflags whether the interval has complete data
uv run pytest tests/ -vAll 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 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 responsechainlink.jsonl— Chainlink oracle ticks (RTDS)binance.jsonl— Binance BTC price ticks (RTDS)market.jsonl— CLOB market channel events
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 |
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.
Raw RTDS payloads for Chainlink BTC/USD. Resolution source of truth — Polymarket uses Chainlink to determine up/down outcome.
Source: RTDS crypto_prices_chainlink, filter btc/usd.
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.
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.
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{"assets_ids": ["<YES_token_id>", "<NO_token_id>"], "type": "market", "custom_feature_enabled": true}Endpoint: wss://ws-subscriptions-clob.polymarket.com/ws/market
{"action": "subscribe", "subscriptions": [{"topic": "crypto_prices_chainlink", "type": "*", "filters": "{\"symbol\":\"btc/usd\"}"}]}{"action": "subscribe", "subscriptions": [{"topic": "crypto_prices", "type": "update"}]}Endpoint: wss://ws-live-data.polymarket.com
{"topic":"crypto_prices_chainlink","type":"update","payload":{"symbol":"btc/usd","price":"96220.30","timestamp":1771982700123},"timestamp":1771982700130}