Hace unas semanas, decidí construir mi propio bot para Polymarket. La versión completa me llevó varias semanas.
Estaba dispuesto a invertir este esfuerzo porque en Polymarket existen ineficiencias reales. Aunque ya hay algunos bots aprovechando estas ineficiencias para obtener ganancias, no son suficientes; las oportunidades en este mercado aún superan con creces la cantidad de bots.
Lógica de construcción del bot
La lógica de este bot se basa en una estrategia que ejecutaba manualmente y que automatizé para mejorar la eficiencia. El bot opera en el mercado «BTC 15-minute UP/DOWN» (Subida/Bajada de BTC en 15 minutos).
El bot ejecuta un monitor en tiempo real que cambia automáticamente a la ronda actual de BTC de 15 minutos, transmite en streaming la mejor oferta de compra/venta (best bid/ask) a través de WebSocket, muestra una interfaz de terminal fija y permite un control completo mediante comandos de texto.
En modo manual, puedes realizar órdenes directamente.
buy up
buyshares up
El modo automático ejecuta un ciclo repetitivo de dos fases (two-leg).
En la primera fase, solo observa las fluctuaciones de precio durante los primeros windowMin minutos después del inicio de cada ronda. Si alguno de los lados cae lo suficientemente rápido (al menos un movePct% en aproximadamente 3 segundos), activa la «Fase 1 (Leg 1)», comprando el lado que se desplomó.
Después de completar la Fase 1, el bot nunca comprará el mismo lado nuevamente. Espera la «Fase 2 (Leg 2, es decir, la cobertura)» y solo se activa cuando se cumple la siguiente condición: leg1EntryPrice + oppositeAsk <= sumTarget.
Cuando se cumple esta condición, compra el lado opuesto. Una vez completada la Fase 2, el ciclo termina y el bot vuelve al estado de observación, esperando la siguiente señal de desplome con los mismos parámetros.
Si la ronda cambia durante el ciclo, el bot abandona el ciclo abierto y reinicia con la misma configuración en la siguiente ronda.
Los parámetros del modo automático se configuran de la siguiente manera: auto on
· shares: Tamaño de la posición para las operaciones de dos fases.
· sum: Umbral permitido para la cobertura.
· move (movePct): Umbral de desplome (por ejemplo, 0.15 = 15%).
· windowMin: Tiempo permitido, desde el inicio de cada ronda, para ejecutar la Fase 1.
Backtesting (Retroprueba)
La lógica del bot es simple: esperar una venta masiva, comprar el lado que acaba de caer, luego esperar a que el precio se estabilice y cubrir comprando el lado opuesto, asegurando que: priceUP + priceDOWN < 1.
Pero esta lógica necesita ser probada. ¿Realmente funciona a largo plazo? Más importante, el bot tiene muchos parámetros (acciones, suma, porcentaje de movimiento, minutos de ventana, etc.). ¿Qué conjunto de parámetros es óptimo y maximiza las ganancias?
Mi primer pensamiento fue hacer funcionar el bot en vivo durante una semana y observar los resultados. El problema es que esto lleva demasiado tiempo y solo prueba un conjunto de parámetros, y yo necesitaba probar muchos.
Mi segundo pensamiento fue realizar backtesting utilizando datos históricos en línea de la API CLOB de Polymarket. Desafortunadamente, para el mercado de Subida/Bajada de 15 minutos de BTC, el endpoint de datos históricos siempre devolvía conjuntos de datos vacíos. Sin ticks (saltos de precio) históricos, el backtesting no podía detectar el «desplome en aproximadamente 3 segundos», no activaba la Fase 1 y producía 0 ciclos y 0% de ROI (Retorno de Inversión), sin importar los parámetros.
Tras una investigación más profunda, descubrí que otros usuarios tenían el mismo problema al obtener datos históricos para ciertos mercados. Probé otros mercados que sí devolvían datos históricos y concluí que, para este mercado específico, los datos históricos simplemente no se conservan.
Debido a esta limitación, la única forma confiable de hacer backtesting de esta estrategia era crear mi propio conjunto de datos históricos grabando el mejor precio de venta (best-ask) en tiempo real mientras el bot estaba en ejecución.
El grabador escribe instantáneas en el disco, que contienen:
· Marca de tiempo
· Identificador de ronda (round slug)
· Segundos restantes
· ID del token UP/DOWN
· Mejor precio de venta UP/DOWN
Posteriormente, el «backtesting grabado» reproduce estas instantáneas y aplica determinísticamente la misma lógica automática. Esto garantiza la obtención de datos de alta frecuencia necesarios para detectar desplomes y condiciones de cobertura.
En total, recopilé 6 GB de datos en 4 días. Podría haber grabado más, pero consideré que era suficiente para probar diferentes conjuntos de parámetros.
Empecé probando este conjunto de parámetros:
· Saldo inicial: $1,000
· 20 acciones por operación
· sumTarget = 0.95
· Umbral de desplome = 15%
· windowMin = 2 minutos
También apliqué una tarifa constante del 0.5% y un spread del 2%, para mantenerme en un escenario conservador.
El backtesting mostró un ROI del 86%, con $1,000 convirtiéndose en $1,869 en solo unos días.
Luego probé un conjunto de parámetros más agresivo:
· Saldo inicial: $1,000
· 20 acciones por operación
· sumTarget = 0.6
· Umbral de desplome = 1%
· windowMin = 15 minutos
Resultado: ROI de -50% después de 2 días.
Esto muestra claramente que la selección de parámetros es el factor más importante. Puede hacerte ganar mucho dinero o llevar a pérdidas significativas.
Limitaciones del Backtesting
Incluso incluyendo tarifas y spread, el backtesting tiene sus limitaciones.
· Primero, solo utiliza unos pocos días de datos, lo que podría no ser suficiente para obtener una perspectiva completa del mercado.
· Se basa en instantáneas grabadas del mejor precio de venta; en la realidad, las órdenes podrían ejecutarse parcialmente o a precios diferentes. Además, la profundidad del libro de órdenes y el volumen disponible no están modelados.
· No captura microfluctuaciones por debajo del nivel de segundos (los datos se muestrean una vez por segundo). Aunque el backtesting tiene marcas de tiempo de 1 segundo, muchas cosas pueden pasar entre un segundo y otro.
· En el backtesting, el deslizamiento (slippage) es constante; no se simula la latencia variable (por ejemplo, 200–1500 ms) o los picos de red.
· Cada operación de fase se considera ejecutada «al instante» (sin cola de órdenes, sin órdenes pendientes).
· Las tarifas se aplican de manera uniforme, mientras que en la realidad pueden depender de: mercado/token, maker vs taker, nivel de tarifas o condiciones.
Para ser pesimista (prudente), apliqué una regla: si la Fase 2 no se ejecuta antes del cierre del mercado, la Fase 1 se considera una pérdida total (total loss).
Esto es deliberadamente conservador, pero no siempre se ajusta a la realidad:
· A veces la Fase 1 se puede cerrar antes,
· A veces termina in the money (ITM) y gana,
· A veces la pérdida puede ser parcial y no total.
Aunque la pérdida podría estar sobreestimada, esto proporciona un escenario práctico de «peor caso».
Lo más importante es que el backtesting no puede simular el impacto de tus órdenes grandes en el libro de órdenes o atraer a otros traders que cacen tus operaciones. En la realidad, tus órdenes pueden:
· Perturbar el libro de órdenes,
· Atraer o repeler a otros traders,
· Causar deslizamiento no lineal.
El backtesting asume que eres un tomador de liquidez puro (price taker), sin ningún impacto.
Finalmente, no simula límites de frecuencia (rate limits), errores de API, órdenes rechazadas, pausas, timeouts, reconexiones, o que el bot esté ocupado y pierda una señal.
El backtesting es extremadamente valioso para identificar buenos rangos de parámetros, pero no es una garantía del 100%, ya que algunos efectos del mundo real no pueden ser modelados.
Infraestructura
Planeo ejecutar este bot en una Raspberry Pi para evitar consumir recursos de mi máquina principal y mantenerlo funcionando 24/7.
Pero aún hay espacio para mejoras significativas:
· Usar Rust en lugar de JavaScript proporcionaría un rendimiento y tiempos de procesamiento muy superiores.
· Ejecutar un nodo RPC de Polygon dedicado reduciría aún más la latencia.
· Desplegarlo en un VPS cerca de los servidores de Polymarket también reduciría significativamente la latencia.
Seguramente hay otras optimizaciones que aún no he descubierto. Actualmente, estoy aprendiendo Rust, ya que se está convirtiendo en un lenguaje indispensable para el desarrollo en Web3.














