DEV Community

ABROWNFOX001
ABROWNFOX001

Posted on

Implementing Robust RTDS WebSocket Reconnection Logic for Polymarket TWAP Feed

When you depend on a live feed for resolution and labeling (especially the official 60s TWAP after the Aug-7 cutover), a flaky WebSocket connection is not acceptable.

Here’s how I implemented reliable reconnection for the Polymarket RTDS client (btc15m/data/rtds.py).

Requirements

Automatic reconnect on disconnect, network errors, or unexpected close
Exponential backoff (with jitter) to avoid hammering the server
Clean re-authentication (HMAC) on every new connection
Automatic re-subscribe to the TWAP topic
Keepalive pings
In-memory state preserved (latest TWAP value + timestamp)
Never lose the last known good value during short outages

Core Design

class RTDSClient:
def init(self, ...):
self.ws = None
self.latest_twap = None
self.latest_ts = None
self._reconnect_delay = 1.0
self._max_delay = 60.0
self._running = False

  1. Main Loop with Reconnect

async def run(self):
self._running = True
while self._running:
try:
await self._connect_and_listen()
except Exception as e:
logger.warning(f"RTDS connection lost: {e}")
await self._backoff()

  1. Connect + Authenticate + Subscribe

On every connection:

Open WebSocket
Send HMAC-signed auth message (Username + HMAC Secret)
Wait for auth success
Send subscribe:
{
"topic": "crypto_prices_twap_sixty",
"type": "update",
"filters": "{\"symbol\":\"btc/usd\"}"
}
Start ping/pong keepalive task

  1. Exponential Backoff with Jitter

async def _backoff(self):
delay = self._reconnect_delay + random.uniform(0, 0.3 * self._reconnect_delay)
logger.info(f"Reconnecting in {delay:.1f}s...")
await asyncio.sleep(delay)
self._reconnect_delay = min(self._reconnect_delay * 2, self._max_delay)

Reset the delay to 1.0s after a successful connection that stays alive for > 30 seconds.

  1. Keepalive

Run a background task that sends a ping every 20–30 seconds.

If no pong is received within a timeout, force a reconnect.

  1. State Preservation

The latest TWAP value is stored on the client instance.

Even if the connection drops for 10–20 seconds, score_pending and the dataset builder can still read the last known good value (with a staleness check).

Staleness Guard

When using the TWAP for resolution:

if time.time() - self.latest_ts > 90:
Too stale → fall back to gamma
return None

This prevents using a TWAP that is older than 1.5 minutes.

Shadow Mode Logging (Pre-Cutover)

Before Aug-7 I ran the client in shadow mode and logged:

RTDS 60s TWAP
Snapshot closePrice
Whether they would have produced different up_won results

This confirmed the ~26% flip rate we previously measured and gave confidence that switching was necessary.

Production Checklist

[x] Credentials loaded from state/rtds.json or environment variables only
[x] Full reconnect + re-auth + re-subscribe path tested
[x] Backoff never exceeds 60s
[x] Keepalive pings active
[x] Staleness check before using TWAP for scoring
[x] Graceful shutdown (self._running = False)

Why This Matters

A single missed TWAP update at slot end can flip a resolution.

Over hundreds of 15-minute slots that compounds into meaningful label noise and incorrect paper PnL.

Spending the extra effort on solid reconnection logic is cheap insurance.

If you’re building anything that depends on Polymarket’s RTDS feeds, treat reconnection as a first-class feature — not an afterthought.

If you have more questions, please feel free to contact me at any time: https://t.me/abrownfox001

My Polymarket Activity: https://polymarket.com/@abrownfox001?tab=activity

Polymarket #RTDS #WebSocket #TradingBot #Python #AsyncIO #CryptoBot #PredictionMarkets

Top comments (0)