A persistent WebSocket connection that pushes every new tier=logical
and tier=endgame opportunity at the exact moment the arb engine
detects it — typically within 100ms of the underlying price/book
event that triggered it.
Plus tier and above only. Free and Pro stay on REST polling
(GET /v1/arb/live). This is the headline Plus differentiator —
no quota burn, no missed signals.
Endpoint
wss://api.polyquantlab.com/v1/arb/ws
Query parameters
| Param | Required | Description |
|---|
api_key | yes | Your PolyQuantLab API key (Plus or above) |
ticker | no | Comma-separated subset of BTC, ETH, SOL. Omit for all. |
WebSockets can’t carry custom Authorization headers reliably across
proxies, so auth is via query param. Use HTTPS-equivalent transport
security (this URL is TLS — never ws://).
Message types
Every message is a single JSON object with a type discriminator.
ready (sent once, on connect)
{
"type": "ready",
"ticker_filter": ["BTC", "ETH"],
"tier": "plus",
"channel": "pql:arb:actionable"
}
opportunity (zero or many — pushed as they fire)
Same shape as items in the opportunities array from
GET /v1/arb/live, with a type: "opportunity" field added. The
tier field is the actionable filter — only "logical" or
"endgame" are pushed; "stable" / "stale" rows are kept off the
push channel by design (they’re not tradable signals).
{
"type": "opportunity",
"as_of": "2026-06-06T04:00:02.118+00:00",
"market_id": "0x4a8f31...c2",
"ticker": "BTC",
"event_type": "5m",
"question": "Will Bitcoin price be above $109,500 at 04:05 UTC?",
"tier": "endgame",
"direction": "BUY_YES",
"fill_price": 0.82,
"expected_pnl_per_share": 0.167,
"...": "(remaining fields identical to /v1/arb/live)"
}
ping (every 25s if no opportunities)
Sent so a long-idle connection doesn’t get killed by intermediaries.
Clients can ignore the body — receiving any message is enough proof
of liveness. No client pong required.
Minimal Python client
import os
import json
import asyncio
import websockets
URL = (
"wss://api.polyquantlab.com/v1/arb/ws"
f"?api_key={os.environ['POLYQUANTLAB_API_KEY']}"
)
async def main():
async with websockets.connect(URL, ping_interval=20, ping_timeout=30) as ws:
async for raw in ws:
msg = json.loads(raw)
t = msg.get("type")
if t == "ready":
print(f"subscribed: {msg}")
elif t == "opportunity":
handle(msg)
elif t == "ping":
pass # ignore; just liveness
else:
print(f"unknown message type: {msg}")
def handle(opp):
print(
f"[{opp['tier'].upper():7s}] {opp['ticker']} {opp['event_type']} "
f"size_at=${opp['fill_price']:.3f} "
f"edge=${opp['expected_pnl_per_share']:.4f}/sh "
f"τ={opp['seconds_to_resolution']}s"
)
# … submit the order via your Polymarket SDK here …
asyncio.run(main())
Reconnect handling
Networks die. Add a backoff loop so a transient drop doesn’t lose
you signals for the rest of the day:
import asyncio, websockets
async def run_forever():
backoff = 1.0
while True:
try:
async with websockets.connect(URL) as ws:
backoff = 1.0 # success — reset
async for raw in ws:
handle(json.loads(raw))
except (websockets.ConnectionClosed, OSError) as e:
print(f"disconnected: {e}; reconnect in {backoff:.1f}s")
await asyncio.sleep(backoff)
backoff = min(backoff * 2, 30.0)
What you don’t get
- No historical replay. Connect = subscribe from now. If you
disconnect for 5 minutes and reconnect, any signals fired during
the gap are lost. Use
GET /v1/arb/audit?window=24h to backfill
if needed for analysis (note: settled-only, not real-time).
- No order submission. PolyQuantLab is a research / signal API;
execution stays on Polymarket. Pipe the message into your
py-clob-client loop — see Auto-trade logical arbs.
- No
stable / stale rows. Those have ~0 expected edge after
fees; surfacing them on the push channel would just waste your
socket bandwidth and tempt bad trades. They remain available via
REST GET /v1/arb/live.
Comparison vs REST polling
| REST GET /v1/arb/live | WS /v1/arb/ws |
|---|
| Tier | Free / Pro / Plus | Plus / above |
| Latency from detect → client | poll interval (3-10 s) | ~50-200 ms |
| Rate-limit consumption | yes — counts toward req/s | none |
| Best for | dashboard polling, batch analysis | auto-trader bots |
| Reconnect handling | not needed | you handle it |
tier=stable / stale rows | included | excluded |