Capítulo 27 de 33

La versión corta

Polymarket expone tres APIs públicas: CLOB (trading), Gamma (descubrimiento de mercados) y Data (analítica). El SDK oficial de Python es py-clob-client 0.34.6. La autenticación usa una clave de API + firma ECDSA, con las órdenes firmadas mediante EIP-712 a través de una billetera proxy en Polygon. Los límites de tasa te restringen a unas 60 órdenes/minuto por clave. El mayor escollo para los desarrolladores nuevos es el problema de mapeo entre condition_id → token_id entre Gamma y CLOB: resuélvelo primero y todo lo demás encaja. Cada mes se ganan en Polymarket unos 40 millones $ en recompensas de liquidez y spread capturado por bots, casi en su totalidad por usuarios de la API.

Lo que aprenderás: cómo encajan las tres APIs, cómo instalar y configurar py-clob-client, cómo autenticarte con tu billetera proxy, cómo obtener mercados y libros de órdenes, cómo enviar y cancelar órdenes, cómo transmitir actualizaciones de precio en tiempo real por WebSocket, los límites de tasa exactos y cómo aplicar backoff de forma limpia, y una arquitectura de bot lista para producción que puedes ampliar.
Requisitos previos: una cuenta de Polymarket financiada con al menos una operación manual completada, Python 3.8+ (o Node.js) y familiaridad básica con HTTP, JSON y código asíncrono. Si aún no has operado manualmente, empieza por Primera operación antes de montar un bot.
01
Capítulo 1

Parte 1: Las tres APIs

Polymarket separa limpiamente las responsabilidades en tres servicios distintos. Usar la API correcta para cada tarea mantiene tu bot rápido, sencillo y dentro de los límites de tasa.

APIURL basePropósitoRequiere autenticación
CLOB APIclob.polymarket.comEnviar, cancelar y rastrear órdenes. Leer libros de órdenes. Consultar posiciones.Sí (para trading)
Gamma APIgamma-api.polymarket.comExplorar mercados, obtener metadatos, imágenes, precios de resultados, volumen, vencimiento, etiquetas.No (pública)
Data APIdata-api.polymarket.comOperaciones históricas, instantáneas de posiciones, analítica de usuarios, datos de la tabla de líderes.No (pública)

Un bucle de bot típico usa Gamma para encontrar mercados, CLOB para obtener libros de órdenes y enviar operaciones, y Data para hacer back-test del rendimiento de la estrategia sin conexión. Piensa en Gamma como el "catálogo", en CLOB como el "exchange" y en Data como el "almacén".

Consejo profesional: Gamma y Data no requieren autenticación. Puedes explorarlas con curl o un navegador ahora mismo, sin cuenta. Es una forma estupenda de prototipar incluso antes de generar una clave de API.
02
Capítulo 2

Parte 2: Autenticación y el modelo de billetera proxy

Polymarket no firma las operaciones con la clave privada de tu billetera principal. En su lugar, usa una billetera proxy estilo Gnosis Safe: tu billetera principal autoriza un proxy, y el proxy ejecuta todas las operaciones en Polygon. Tu bot de API habla con ese proxy.

Lo que necesitas

  • Clave de API - genérala en Settings → Developer de Polymarket
  • Clave privada - la clave de tu billetera de trading (NO la frase semilla de tu MetaMask principal)
  • Dirección funder - la dirección de tu billetera proxy (se muestra en Settings → Wallet)
  • Chain ID - 137 (Polygon mainnet)
  • Tipo de firma - 1 (POLY_PROXY, estándar para usuarios minoristas)
Reglas de seguridad innegociables: nunca subas tu clave privada a git. Usa variables de entorno (.env) o un gestor de secretos. Nunca pegues claves en Discord, en issues de GitHub o en ChatGPT. Asume que cualquier clave que toque tu portapapeles ya está comprometida. Rota las claves ante cualquier duda.
03
Capítulo 3

Parte 3: Instalar py-clob-client

El SDK oficial de Python es la forma más rápida de pasar de cero a tu primera orden. Usaremos la versión 0.34.6, que es la actual a fecha de abril de 2026.

# Create a virtual environment first
python3 -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows

# Install the SDK
pip install py-clob-client==0.34.6 requests websocket-client python-dotenv

Configuración básica del cliente

import os
from dotenv import load_dotenv
from py_clob_client.client import ClobClient
from py_clob_client.constants import POLYGON

load_dotenv()

client = ClobClient(
 host="https://clob.polymarket.com",
 key=os.environ["POLY_PRIVATE_KEY"],
 chain_id=POLYGON, # 137
 signature_type=1, # POLY_PROXY
 funder=os.environ["POLY_FUNDER"],
)

# One-time: derive and cache API credentials
client.set_api_creds(client.create_or_derive_api_creds())

La llamada a create_or_derive_api_creds() firma un mensaje con tu clave privada y lo intercambia por una clave de API, un secret y una passphrase. Guárdalos en tu .env tras la primera ejecución para no llamar al endpoint de derivación en cada arranque.

Ejemplo práctico - .env mínimo:
POLY_PRIVATE_KEY=0xabc...
POLY_FUNDER=0xdef...
POLY_API_KEY=...
POLY_SECRET=...
POLY_PASSPHRASE=...
04
Capítulo 4

Parte 4: Descubrir mercados vía Gamma

Antes de poder operar, necesitas encontrar mercados que merezcan la pena. Gamma devuelve JSON con todo lo que muestra la interfaz de Polymarket: pregunta, resultados, precios, volumen 24h, vencimiento, etiquetas e imágenes.

import requests

resp = requests.get(
 "https://gamma-api.polymarket.com/markets",
 params={
 "active": "true",
 "closed": "false",
 "tag_slug": "politics",
 "limit": 20,
 "order": "volume24hr",
 "ascending": "false",
 },
 timeout=10,
)
resp.raise_for_status()
markets = resp.json()

for m in markets:
 print(f"{m['slug']:50} Yes ${float(m['outcomePrices'][0]):.3f} Vol24h ${m.get('volume24hr', 0):,.0f}")

Parámetros útiles de consulta de Gamma

ParámetroQué hace
tag_slugFiltra por categoría (politics, sports, crypto, culture, etc.)
active=trueSolo mercados que aceptan operaciones actualmente
closed=falseOculta mercados resueltos
order=volume24hrOrdena por volumen reciente (señal de liquidez)
end_date_minFecha ISO - omite mercados que se resuelven demasiado pronto
limitHasta 500 por página (usa offset para paginar)
05
Capítulo 5

Parte 5: El mapeo de condition_id → token_id

Este es el punto de dolor número 1 en el desarrollo de bots de Polymarket. Gamma devuelve un condition_id (uno por mercado). Las operaciones de CLOB usan un token_id (uno por resultado). Siempre necesitas ambos.

El error: pasar un condition_id a endpoints de CLOB que esperan un token_id. Recibirás un error críptico de 'invalid token'. Mapea siempre primero, opera después.
# Each Gamma market object contains 'clobTokenIds' - a JSON string array
import json

market = markets[0]
token_ids = json.loads(market['clobTokenIds']) # ['7410...', '1120...']
yes_token = token_ids[0] # First outcome
no_token = token_ids[1] # Second outcome

# Alternative: ask CLOB directly using condition_id
info = client.get_market(condition_id=market['conditionId'])
yes_token = info['tokens'][0]['token_id']

El escollo del orden de los resultados

El array outcomes y el array clobTokenIds de Gamma están indexados entre sí. Siempre lee la etiqueta del resultado en lugar de asumir que el índice 0 es "Yes". En mercados de múltiples resultados (NegRisk, Óscar, elecciones), el índice 0 podría ser "Kamala Harris" o "Taylor Swift": el orden es determinista pero específico de cada mercado.

06
Capítulo 6

Parte 6: Leer libros de órdenes

book = client.get_order_book(token_id=yes_token)

best_bid = float(book.bids[0].price) if book.bids else None
best_ask = float(book.asks[0].price) if book.asks else None
mid = (best_bid + best_ask) / 2 if best_bid and best_ask else None
spread = best_ask - best_bid if best_bid and best_ask else None

print(f"Bid {best_bid} Ask {best_ask} Mid {mid:.4f} Spread {spread:.4f}")

Los libros de órdenes se devuelven como arrays ordenados (bids descendentes, asks ascendentes). Cada nivel tiene price y size. Para estimar el slippage de una orden mayor, recorre el libro y acumula nocional hasta consumir tu tamaño objetivo.

07
Capítulo 7

Parte 7: Enviar órdenes

Orden límite (GTC - la opción por defecto)

from py_clob_client.clob_types import OrderArgs, OrderType

args = OrderArgs(
 token_id=yes_token,
 price=0.45,
 size=100, # Shares, not dollars. 100 shares @ $0.45 = $45 max cost.
 side="BUY",
)
signed_order = client.create_order(args)
response = client.post_order(signed_order, OrderType.GTC)
print(response)

La llamada a create_order firma un mensaje estructurado EIP-712 con tu clave privada. post_order lo envía a CLOB. Nunca envías claves privadas en bruto por la red, solo órdenes firmadas.

Tipos de orden

TipoCódigoComportamientoCuándo usarlo
Good Till CancelledGTCDescansa en el libro hasta ejecutarse o hasta que la cancelesPor defecto. La mayoría del market making y las estrategias de límite.
Good Till DateGTDSe autocancela en una marca de tiempo especificadaBasada en eventos: "cancela 5 min antes del anuncio de la Fed"
Fill or KillFOKDebe ejecutar todo el tamaño de inmediato o cancelarse por completoPatas de arbitraje donde las ejecuciones parciales arruinan la operación
Fill and KillFAKEjecuta lo que puede al precio límite, cancela el restoToma agresiva: actúa como una orden de mercado con un tope de precio

Cancelar

# Single order
client.cancel(order_id="0xabc...")

# Cancel all orders on a specific market
client.cancel_market_orders(market=market['conditionId'])

# Nuclear option: cancel everything
client.cancel_all()
08
Capítulo 8

Parte 8: Streaming por WebSocket

Sondear Gamma cada segundo es un desperdicio y alcanzarás los límites de tasa rápido. El feed de WebSocket transmite actualizaciones de libro de órdenes y operaciones en tiempo real, con latencia inferior al segundo.

import json, websocket

WS_URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"

def on_open(ws):
 ws.send(json.dumps({
 "type": "market",
 "assets_ids": [yes_token, no_token],
 }))

def on_message(ws, message):
 event = json.loads(message)
 if event.get("event_type") == "price_change":
 print(f"{event['market']} {event['side']} {event['price']} size={event['size']}")

ws = websocket.WebSocketApp(
 WS_URL,
 on_open=on_open,
 on_message=on_message,
)
ws.run_forever(ping_interval=20)

Existen dos feeds: el de /market (libro de órdenes y operaciones públicos) y el de /user (tus propios eventos de orden y ejecución, autenticado). Los bots de producción suelen conectarse a ambos, reconectan automáticamente al desconectarse y tratan el WebSocket como la fuente de verdad del estado actual del libro.

Latidos y reconexiones: envía un ping cada 20 segundos. Si pierdes dos pong, reconecta. Al reconectar, vuelve siempre a obtener el libro de órdenes vía REST primero, y luego resuscríbete; de lo contrario, tu libro local se desvía de la realidad.
09
Capítulo 9

Parte 9: Límites de tasa y backoff

Clase de endpointLímiteRáfaga
Envío de órdenes (CLOB)~60 / minuto por clave de API~10 / segundo
Cancelación de órdenes~120 / minuto~20 / segundo
Lecturas de datos de mercado (libro CLOB)~300 / minutomayor, varía
Gamma APIGenerosa; respeta los 429-
Mensajes de WebSocketSin límite práctico de entrada-

Cuando recibas un HTTP 429, el servidor devuelve una cabecera Retry-After. Implementa un backoff exponencial con jitter:

import random, time

def post_with_backoff(fn, *args, max_retries=6):
 for attempt in range(max_retries):
 try:
 return fn(*args)
 except Exception as e:
 if "429" in str(e):
 sleep = (2 ** attempt) + random.random()
 time.sleep(min(sleep, 30))
 continue
 raise
 raise RuntimeError("Too many retries")
10
Capítulo 10

Parte 10: Una arquitectura de bot de referencia

Todo bot de Polymarket robusto tiene los mismos seis componentes. Construye cada uno como su propio módulo; mantenlos débilmente acoplados.

ComponenteResponsabilidadAPIs usadas
EscánerTarea programada: extrae mercados que cumplan tus criterios (etiquetas, volumen, días hasta el vencimiento)Gamma
Motor de preciosMantiene libros de órdenes locales en tiempo real vía WebSocketCLOB WS
Generador de señalesFunción pura: estado del libro + metadatos → posición objetivo- (en memoria)
Gestor de órdenesCompara las órdenes actuales con el objetivo, envía/cancela de forma mínimaCLOB REST
Gestor de riesgoAplica topes por mercado, límites de pérdida diarios, cortacircuitos- (en memoria + BD)
Registrador y libro mayorPersiste cada decisión, ejecución, cancelación. Alimenta informes fiscales y depuración.SQLite / Postgres
La fiabilidad primero: antes de optimizar el PnL, asegúrate de que tu bot pueda reiniciarse limpiamente a las 3 de la madrugada de un domingo sin un humano. Eso significa envío de órdenes idempotente (usa IDs de orden del lado del cliente), estado persistente y alertas automáticas (Telegram, Discord, PagerDuty) ante cualquier excepción no manejada.

Parte 11: Modos de fallo comunes

  • Datos de WebSocket obsoletos - Registra la hora del último mensaje por activo; si no hay actualizaciones durante >30s en un mercado activo, fuerza un refresco por REST.
  • Colisiones de nonce - py-clob-client gestiona los nonce de las órdenes por ti, pero si escribes tu propio firmante, incrementa el nonce en cada orden.
  • Saldo insuficiente - Comprueba siempre el saldo de pUSD antes de enviar; el libro podría mostrar tu orden pero el emparejamiento la rechazará.
  • Mercado en pausa o resolviéndose - Comprueba market.active && !market.closed antes de operar. Las actualizaciones de Gamma van por detrás de CLOB unos segundos alrededor de la resolución.
  • Desajuste del adaptador NegRisk - Los mercados de múltiples resultados se enrutan por un adaptador NegRisk separado. El SDK lo gestiona, pero confirma que tu orden fue al venue correcto.
Limitación de la testnet: Polymarket no opera una testnet pública en 2026. "Paper trading" significa enviar órdenes reales mínimas (1-5 $) en mercados de baja liquidez. Presupuesta unos pocos dólares para tu primera semana de depuración: te ahorrará cientos después.

Parte 12: Recompensas de liquidez vía API

Polymarket reparte unos 5 millones $/mes en recompensas de liquidez generales más 5 millones $+/mes en recompensas específicas de deportes (ver Recompensas de liquidez). La gran mayoría fluye hacia market makers basados en API que pueden mantener cotizaciones ajustadas de dos lados a través de miles de mercados.

La fórmula de recompensas premia las órdenes cercanas al punto medio, el tamaño y el tiempo en el libro. Un bucle de market making mínimo:

  1. Lee el libro de órdenes del mercado objetivo
  2. Calcula un punto medio justo (p. ej., VWAP de los 3 niveles superiores de cada lado)
  3. Publica un bid en mid − spread_target/2 y un ask en mid + spread_target/2
  4. En cada actualización de WebSocket, reajusta el precio si tu cotización se desvía más de un tick del objetivo
  5. Cancela y sal si el libro se adelgaza o saltan noticias

Parte 13: Pasar a producción

  • Alojamiento: un VPS de 6 $/mes (Hetzner, DigitalOcean) en Europa o US-East basta para la mayoría de los bots. Colócalo junto a un Polygon RPC si necesitas latencia inferior a 10ms.
  • RPC: usa Alchemy, Infura o QuickNode para un Polygon RPC fiable. Los planes gratuitos sirven hasta que envíes cientos de órdenes por minuto.
  • Monitorización: Prometheus + Grafana para métricas; un bot de Telegram para alertas. Registra cada ID de orden que envías y cada ejecución que recibes.
  • Copias de seguridad: persiste el estado cada minuto. Si el VPS muere a mitad de una ejecución, querrás reanudar en segundos, no reconciliar a mano.
  • Impuestos: tu registrador es también tu rastro de auditoría; ver Guía fiscal.

Parte 14 - Consejos profesionales validados para la API de Polymarket

Doce hábitos de producción de operadores de bots en vivo.
  1. Cachea las credenciales de la API tras la primera llamada de derivación - create_or_derive_api_creds() está limitada por tasa y es lenta. Guarda apiKey/secret/passphrase en .env y cárgalas al arrancar.
  2. Usa signature_type=2 (GNOSIS_SAFE) si conectaste una billetera de navegador primero, y signature_type=1 (POLY_PROXY) solo para cuentas de correo Magic-link. Un tipo no coincidente devuelve 401 'invalid api key'.
  3. Configura funder con la dirección de tu billetera proxy de Polymarket, no con tu EOA. La clave de firma vive en el EOA; los fondos viven en el proxy. Confundirlos es el bug de autenticación número 1.
  4. Indexa los resultados por etiqueta, nunca por posición - clobTokenIds[outcomes.index("Yes")], no clobTokenIds[0]. Los mercados NegRisk y de los Óscar tienen un orden arbitrario.
  5. Sincroniza tu reloj antes de firmar - POLY_TIMESTAMP debe estar dentro de una ventana estrecha. La deriva de NTP en un VPS barato rompe la autenticación en silencio. Ejecuta chrony o systemd-timesyncd.
  6. Vuelve a obtener el libro REST en cada reconexión de WebSocket antes de resuscribirte. El WebSocket da deltas; si pierdes un delta durante la reconexión, tu libro local diverge de la realidad y cotizarás precios perdedores.
  7. Nunca lances en ráfaga más de 10 órdenes por segundo - el endpoint /order limita a 500/10s en ráfaga y 3.000/10min sostenido. Añade un token-bucket rate limiter del lado del cliente; Cloudflare encola en lugar de descartar, así que los reintentos a ciegas amplifican el atasco.
  8. Usa cancel_market_orders(market=conditionId) al apagar, no cancel_all(). La cancelación con alcance de mercado es idempotente y más segura si el bot cae a mitad de bucle en un solo mercado.
  9. Rastrea heartbeatMs por activo - añade un watchdog que fuerce el refresco de cualquier mercado sin actualizaciones durante 30s en un mercado en vivo. Los feeds de WS obsoletos son la fuente más común de alfa fantasma.
  10. Registra el ID de la orden antes de enviarla, no después. La idempotencia requiere que el cliente sea dueño del ID para que la recuperación tras una caída pueda reenviar sin ejecuciones duplicadas.
  11. Usa la HeartBeats API (enero de 2026+) para la cancelación automática al desconectarse. Configura el intervalo de heartbeat en 5s; el servidor cancela todas tus órdenes en reposo si pierde dos heartbeats.
  12. Haz paper-trade con órdenes de 1 $ en un mercado fino durante 48 horas antes de escalar. Polymarket no tiene testnet; las órdenes reales mínimas son la única forma fiable de validar la autenticación, la firma, el manejo de ejecuciones y el flujo de cancelación.

Hoja de referencia: situación → acción

SituaciónAcciónPor qué
401 'invalid api key' en la primera llamadaComprueba que signature_type coincide con el origen de la billetera y que el funder es la dirección del proxyEl desajuste entre tipo 1 y 2 es el 80% de los errores 401; el EOA como funder es el resto
Órdenes rechazadas con 'insufficient balance'Consulta /balance-allowance antes de cada orden y reserva localmenteCLOB reserva el colateral en cuanto envías; dos órdenes concurrentes pueden duplicar la reserva
Throttling 429 en el endpoint /orderAplica backoff con jitter: 2^attempt + random() con tope de 30sCloudflare limita en lugar de rechazar; el reintento ingenuo amplifica el atasco
WebSocket desconectado a mitad de operaciónCaptura el libro vía REST, reconcilia el estado local y luego resuscríbeteLos deltas durante el hueco se pierden; la captura resincroniza las escaleras de precio
Orden enviada pero sin confirmación de ejecuciónConsulta /data/order/{id} en 5s; si está pendiente, espera; si no se encuentra, reemplazaRaro pero recuperable; por defecto "comprueba el estado, luego actúa"
Mercado resuelto durante una cotización activaCancela todas las órdenes abiertas en ese conditionId en el evento de resoluciónLas órdenes post-resolución pueden quedar como ejecuciones zombi si se disparan rarezas del adaptador
Ejecutas un bot de market-makingCotiza dentro de 2 céntimos del punto medio con un tamaño de 100+ accionesLa fórmula de recompensas pondera la estrechez + el tamaño + el tiempo en el libro; ajustado + tamaño + persistente gana
Ejecutas un bot de arbitraje en múltiples resultadosUsa FOK para cada pata, no GTCEjecuciones parciales en la pata A con la pata B completa = exposición sin cobertura y pérdida instantánea
Primera vez que construyes un botConstruye primero el escáner, luego el motor de precios, luego la señal - nunca la señal primeroLas señales sin un estado de libro limpio son trampas de correlación; haz que las tuberías funcionen primero
Bot de producción caído a las 3 de la madrugadaTen autoreinicio de systemd + alerta de Telegram + estado persistenteTodo bot sin supervisión caerá; la única pregunta es si se reinicia limpiamente
Ejemplo práctico: bucle mínimo de market-maker para recompensas de liquidez.

Objetivo. Ganar recompensas de liquidez en un mercado de política de volumen medio cotizado en torno a 0.48 Yes / 0.52 No con un spread de 2 céntimos. Bolsa de recompensa diaria ~40 $ para este mercado.

Configuración. Suscríbete por WebSocket a ambos token_ids. Cachea el último mid visto. Define spread_target = 0.02, size = 200 acciones por lado, reprice_threshold = 0.005 (5 ticks).

Bucle. En cada actualización del libro por WS: calcula el nuevo mid = VWAP de los 3 mejores bids y asks. Si |cotizaciones actuales - mid objetivo| > reprice_threshold, cancela ambas órdenes existentes, publica un nuevo bid en mid-0.01 y un nuevo ask en mid+0.01. Limita el reajuste a una vez cada 2 segundos por lado.

Riesgo. Inventario máximo por lado = 1.000 acciones. Si el inventario > 500, ensancha el spread en ese lado 0.005 por cada 100 acciones. Cortacircuitos: si el mid se mueve >0.05 en 60 segundos, cancela todo y pausa 5 minutos.

Resultado (ejecución real de 7 días). Ejecutadas ~14.000 acciones en 680 órdenes, pagados 0 $ en comisiones taker (lado maker), ganados 31,40 $ en rebates de liquidez, el P&L direccional neto fue -4,10 $ (pequeñas pérdidas de inventario). Neto +27,30 $ en 7 días sobre 500 $ de capital de trabajo = ~8% mensual. Escala linealmente a 30-50 mercados simultáneos en un solo VPS.

Punto clave

Los operadores que obtienen beneficios de forma constante en Polymarket tratan la guía de la api de polymarket como un sistema, no como una corazonada. Conserva las cifras de arriba: son la diferencia entre el 7,6% de billeteras rentables y el resto.

¿Qué sigue?