WebSockets de Polymarket: datos en tiempo real que no se caen
Hacer polling del libro de órdenes es la forma más común de quemar presupuesto de límite de tasa sin ningún beneficio. Aquí están los cuatro streams, las opciones de suscripción que nadie documenta y el modo de fallo en el que tu bot recibe silencio y lo interpreta como calma.
Si tu bot hace polling de GET /book con un temporizador, esta página te ahorrará la mayor parte de tu presupuesto de límite de tasa y una buena parte de la latencia. Polymarket envía las actualizaciones del libro por WebSocket, y una suscripción te cuesta una conexión en lugar de miles de solicitudes.
La versión corta
Conéctate a wss://ws-subscriptions-clob.polymarket.com/ws/market, envía una suscripción con tus assets_ids y después envía un PING cada 10 segundos o te desconectarán. Pon custom_feature_enabled: true si quieres los eventos best_bid_ask, new_market o market_resolved: están desactivados por defecto y su ausencia se ve exactamente igual que un mercado tranquilo.
Los cuatro streams
| Stream | URL | Transmite | Autenticación |
|---|---|---|---|
| Market | wss://ws-subscriptions-clob.polymarket.com/ws/market | Actualizaciones públicas del libro, de precios y del ciclo de vida | Ninguna |
| User | wss://ws-subscriptions-clob.polymarket.com/ws/user | Actualizaciones de tus órdenes y operaciones | Sí |
| RTDS (legado) | wss://ws-live-data.polymarket.com | Comentarios; los precios de referencia pasaron a PolyBolt | Ninguna |
| PolyBolt | wss://ws-live-v2.polymarket.com/ws | Precios de referencia cripto, de acciones y TWAP | Sí |
| Sports | wss://sports-api.polymarket.com/ws | Estado de los partidos en vivo y marcadores | Ninguna |
El canal User es el que la gente pasa por alto
Si tu bot hace polling de /data/orders para saber si su orden se ejecutó, estás gastando presupuesto de límite de tasa en información que el canal User te envía en el instante en que ocurre, y con menos latencia. Cualquier bot que coloque órdenes debería estar en este stream.
Los precios de referencia se movieron el 15 de septiembre de 2026
Polymarket lanzó PolyBolt y convirtió RTDS en legado para los precios de referencia. crypto_prices, crypto_prices_chainlink y equity_prices ahora están en wss://ws-live-v2.polymarket.com/ws, la fuente cambió de Binance/Chainlink a Pyth, los símbolos son btcusd en minúsculas en lugar de btcusdt o btc/usd, y ahora se requiere autenticación. El mapeo completo está en la guía de migración a PolyBolt. Todo lo demás de esta página (los canales market y user del CLOB) no se ve afectado.
Suscribirse al canal market
Conéctate y envía un mensaje JSON de suscripción. assets_ids recibe IDs de token (los mismos que obtienes de los metadatos del mercado) y type debe ser "market".
{
"assets_ids": ["65818619657568813474341868652308942079804919287380422192892211131408793125422"],
"type": "market",
"initial_dump": true,
"level": 2,
"custom_feature_enabled": true
}| Campo | Obligatorio | Por defecto | Qué hace |
|---|---|---|---|
assets_ids | Sí | — | IDs de token a los que suscribirse |
type | Sí | — | Debe ser "market" |
initial_dump | No | true | Envía un snapshot completo del libro al suscribirte |
level | No | 2 | Nivel de suscripción: 1, 2 o 3 |
custom_feature_enabled | No | false | Activa los eventos best_bid_ask, new_market y market_resolved |
custom_feature_enabled es false por defecto
Esta es la trampa de la suscripción que le cuesta a la gente una tarde. Tres tipos de eventos (best_bid_ask, new_market y market_resolved) no se entregan salvo que los actives. No hay error ni aviso; simplemente nunca los recibes. Si estás esperando un evento market_resolved que nunca llega, este es el motivo.
Mantén initial_dump: true salvo que tengas un motivo concreto para no hacerlo. Sin él empiezas sin estado del libro y tienes que esperar a que las actualizaciones incrementales lo reconstruyan, lo que significa que tu bot arranca con una imagen incompleta.
Tipos de mensaje
Cada mensaje lleva un event_type. Ramifica sobre él de forma exhaustiva: un tipo no reconocido debe registrarse, no descartarse en silencio, porque así es como te enteras de que el protocolo cambió.
event_type | Significado | Hay que activarlo |
|---|---|---|
book | Snapshot completo del libro de órdenes | No |
price_change | Actualización incremental de precio/nivel | No |
last_trade_price | Se ejecutó una operación | No |
tick_size_change | Cambió la cuadrícula de ticks del mercado | No |
best_bid_ask | Actualización del mejor nivel del libro | Sí |
new_market | Se creó un mercado | Sí |
market_resolved | Se liquidó un mercado | Sí |
No ignores tick_size_change
El tamaño del tick es propio de cada mercado y puede reducirse a medida que el mercado madura. Si guardas en caché un tamaño de tick al arrancar y el mercado lo cambia, todas las órdenes límite posteriores se rechazan por infringir el tamaño mínimo de tick: un 400 que parece un bug en tu lógica de precios. Gestiona el evento y actualiza tu caché.
Mantener la conexión: PING cada 10 segundos
Este es el requisito que pilla a todo el mundo. El cliente debe enviar un PING cada 10 segundos; el servidor responde PONG. Si fallas el heartbeat, la conexión se corta.
Fíjate en la dirección: es un heartbeat del cliente. No estás esperando a que el servidor te haga ping. El keepalive integrado de una biblioteca WebSocket con otro intervalo no lo sustituye: envía tú mismo el PING a nivel de aplicación.
import asyncio, json, time
import websockets
URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
STALE_AFTER = 30 # seconds without any message = treat as dead
async def stream(token_ids, on_event):
backoff = 1
while True:
try:
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({
"assets_ids": token_ids,
"type": "market",
"initial_dump": True,
"custom_feature_enabled": True, # or lose 3 event types
}))
backoff = 1 # reconnected cleanly
last_msg = time.monotonic()
async def heartbeat():
while True:
await asyncio.sleep(10) # required cadence
await ws.send("PING")
hb = asyncio.create_task(heartbeat())
try:
while True:
# Silence is a failure mode, not a quiet market.
raw = await asyncio.wait_for(ws.recv(), timeout=STALE_AFTER)
last_msg = time.monotonic()
if raw == "PONG":
continue
for msg in json.loads(raw) if raw.startswith("[") else [json.loads(raw)]:
on_event(msg)
finally:
hb.cancel()
except Exception as exc:
# Reconnect with capped exponential backoff + jitter.
wait = min(backoff, 30) * (0.5 + 0.5 * asyncio.get_event_loop().time() % 1)
print(f"ws down ({exc}); reconnecting in {wait:.1f}s")
await asyncio.sleep(wait)
backoff = min(backoff * 2, 30)Usa el SDK salvo que tengas un motivo para no hacerlo
polymarket-client (Python) y @polymarket/client (TypeScript) implementan el heartbeat, la reconexión y el framing de mensajes, y combinan varios feeds en un único stream de eventos tipados. Hacerlo a mano solo compensa si necesitas un control que el SDK no expone. Ten en cuenta que las suscripciones son solo asíncronas: los clientes síncronos no implementan subscribe().
Cambiar suscripciones sin reconectar
Los mercados de cinco minutos vencen constantemente, así que un bot que sigue los “mercados BTC 5m actuales” tiene que cambiar su conjunto de suscripciones de forma continua. No tienes que cerrar la conexión para hacerlo: el canal market admite suscribirse y cancelar suscripciones sobre una conexión activa.
Esto importa más de lo que parece. Reconectar en cada rotación de mercado implica pagar una y otra vez el coste de establecer la conexión, volver a pedir los volcados iniciales y crear una ventana en la que no recibes nada justo cuando abre un mercado nuevo.
El modo de fallo que cuesta dinero
Un WebSocket caído no lanza una excepción. Deja de entregar mensajes. Para un bot ingenuo, “no hay actualizaciones” es indistinguible de “el mercado no se mueve”, así que un bot con una posición abierta se quedará indefinidamente con un precio desfasado, convencido de que nada ha cambiado.
- Controla la antigüedad del último mensaje. Si no ha llegado nada en ~30 segundos en un mercado activo, estás desconectado, no en calma. El timeout de
asyncio.wait_forde arriba hace exactamente eso. - Trata el desfase como una condición de error. Deja de cotizar, cierra posiciones si tus reglas de riesgo lo exigen y reconecta: no sigas operando con un libro congelado.
- Espera entre reconexiones, con jitter. Un bucle de reconexión cerrado durante una caída es la forma de que te limiten la tasa además de estar desconectado, y una flota que reconecta al unísono lo empeora.
- Resincroniza el estado después de reconectar. Te perdiste actualizaciones mientras no estabas. Toma el nuevo
initial_dumpcomo la verdad y descarta tu libro anterior, en lugar de fusionar actualizaciones incrementales con un snapshot desfasado. - Concilia las posiciones después de cualquier hueco. Si estuviste desconectado del canal User, puede que te perdieras una ejecución. Vuelve a leer las posiciones antes de actuar sobre un estado supuesto.
WebSockets y límites de tasa
Una pregunta frecuente, y la respuesta es el sentido de todo esto: una suscripción no consume presupuesto de límite de tasa por solicitud como el polling. Los límites documentados de Polymarket son límites HTTP por endpoint, y el streaming los esquiva casi por completo.
| Enfoque | Coste de seguir 20 mercados durante una hora |
|---|---|
Polling de /book cada segundo | 72,000 solicitudes frente a un presupuesto de 1,500 req/10s |
Polling de /books (por lotes) cada segundo | 3,600 solicitudes frente a un presupuesto de 500 req/10s |
| Suscripción WebSocket | Una conexión, más 360 heartbeats |
Seguirás haciendo llamadas HTTP (descubrimiento de mercados, tamaños de tick, colocación de órdenes, conciliación de posiciones), y esas siguen sujetas a los límites de tasa documentados. El streaming solo elimina la fuente de volumen de solicitudes más grande y menos útil.
La latencia es limitación disfrazada
Como la capa de Cloudflare limita retrasando en lugar de rechazando, un bot que hace polling por encima del límite no ve 429: ve cómo sus tiempos de respuesta suben sin hacer ruido. Si tus datos del libro han pasado de 40ms a 3 segundos, no estás sufriendo una caída; estás sufriendo tu propia tasa de solicitudes.
Lo que los streams no te darán
Un límite que conviene decir claramente, porque es lo que la gente intenta a continuación. Un WebSocket te da el libro desde el momento en que te suscribes. No puede darte el libro del martes pasado.
Polymarket no conserva ningún libro de órdenes histórico. Cuando un mercado se resuelve, su libro devuelve 404, y prices-history solo conserva aproximadamente un punto por minuto. Si quieres probar una estrategia con cómo se negociaron realmente los mercados, necesitas snapshots que alguien grabó en su momento. PolyTest lo hace para los mercados cripto Up/Down, con 8 niveles de profundidad por lado y mercados resueltos conservados. O pon en marcha tu propio recolector hoy, asume que empiezas sin historial y espera.
Preguntas frecuentes
- ¿Cuál es la URL del WebSocket de Polymarket?
- El canal público de mercado es wss://ws-subscriptions-clob.polymarket.com/ws/market y el canal autenticado de usuario es wss://ws-subscriptions-clob.polymarket.com/ws/user. También hay otros dos streams: RTDS en wss://ws-live-data.polymarket.com para precios de referencia, comentarios y actividad de operaciones, y un feed de deportes en wss://sports-api.polymarket.com/ws.
- ¿Hay un límite de tasa en el WebSocket de Polymarket?
- Una suscripción WebSocket no consume presupuesto de límite de tasa por solicitud como el polling HTTP: los límites documentados de Polymarket son límites HTTP por endpoint. El streaming es precisamente la forma recomendada de evitarlos. El requisito que sí obliga en una conexión es el heartbeat: envía un PING cada 10 segundos o la conexión se corta. Las llamadas HTTP que sigues haciendo para descubrimiento, tamaños de tick y colocación de órdenes siguen sujetas a los límites normales.
- ¿Cómo mantengo activa una conexión WebSocket de Polymarket?
- Envía un mensaje PING cada 10 segundos; el servidor responde con PONG. Es un heartbeat a nivel de aplicación del lado del cliente, así que el keepalive integrado de una biblioteca WebSocket con otro intervalo no lo sustituye. Controla también la antigüedad del último mensaje recibido: una conexión caída deja de entregar datos sin generar un error, así que trata unos 30 segundos de silencio en un mercado activo como una desconexión y no como un mercado tranquilo.
- ¿Por qué no recibo los eventos best_bid_ask o market_resolved?
- Esos eventos están desactivados por defecto. Pon custom_feature_enabled en true en tu mensaje de suscripción para activar best_bid_ask, new_market y market_resolved. No hay error ni aviso cuando falta el flag: los eventos simplemente nunca llegan, lo que es fácil de confundir con un mercado inactivo.
- ¿Mi bot de Polymarket debería usar WebSockets o hacer polling de la API REST?
- Usa WebSockets para los datos en vivo del libro y de precios. Seguir 20 mercados haciendo polling de /book una vez por segundo cuesta 72,000 solicitudes por hora frente a un presupuesto de 1,500 req/10s, mientras que una suscripción cuesta una conexión más los heartbeats; además, el polling devuelve una respuesta más desfasada, ya que ves el libro tal como estaba en tu último polling y no en el último cambio. Deja REST para el descubrimiento de mercados, los tamaños de tick, la colocación de órdenes y la conciliación de posiciones.
- ¿Puedo cambiar los mercados a los que estoy suscrito sin reconectar?
- Sí. El canal market admite suscribirse a activos y cancelar suscripciones sobre una conexión activa, lo que importa en mercados de vida corta como los contratos Up/Down de 5 minutos, que rotan constantemente. Reconectar en cada rotación supone pagar una y otra vez el coste de establecer la conexión y deja un hueco en la cobertura justo cuando abre un mercado nuevo.
Obtén los datos históricos que Polymarket no guarda
PolyTest registra los mercados cripto Up/Down de Polymarket mientras están activos: 240M+ snapshots con 8 niveles de profundidad del libro de órdenes, marcas de tiempo por debajo del segundo y mercados resueltos conservados. Empieza con una prueba gratuita de 3 días.
Sigue leyendo
Polymarket reemplazó RTDS: migrar a PolyBolt
El 15 de septiembre Polymarket convirtió RTDS, sin hacer ruido, en legado para los precios de referencia. Si envías precios spot por streaming a un bot, cambiaron seis cosas a la vez, incluidas la fuente de precios y el formato de los símbolos, y la mayoría fallan en silencio en lugar de dar un error.
Cómo crear un bot de trading para Polymarket
La mayoría de los tutoriales de bots para Polymarket se basan en SDK que ya no existen. Este es el camino actual de principio a fin: qué instalar en 2026, cómo funciona realmente la autenticación, dónde están los huecos de datos y los modos de fallo que matan a los bots en la primera semana.
Límites de tasa de la API de Polymarket: la referencia completa
Polymarket publica sus límites de tasa en varias páginas, en dos sistemas completamente distintos y con una ventana de 10 segundos que casi nadie espera. Aquí lo tienes todo en un conjunto de tablas, más lo que pasa realmente cuando superas un límite.
El SDK de Python de Polymarket en 2026 (y cómo dejar py-clob-client)
Si tu código todavía importa ClobClient desde py_clob_client, está construido sobre un paquete sustituido, igual que la mayor parte del código de Polymarket que te escribirá un asistente de IA. Este es el SDK actual y cómo pasarte a él.