Saltar al contenido principal
ingenieríabacktestingbots de trading
Lo Que Subestimé al Construir una Plataforma No-Code de Bots de Trading

Lo Que Subestimé al Construir una Plataforma No-Code de Bots de Trading

La propuesta de un bot de trading no-code es simple: describes una estrategia en un formulario y el software la ejecuta por ti. La parte difícil no es el formulario. Es que cada simplificación que le ofreces al usuario hay que pagarla en algún punto del sistema, normalmente en un lugar que no esperabas.

Estas son tres cosas en las que me equivoqué al respecto, y lo que cada una nos enseñó en realidad. Están ordenadas según lo mucho que las juzgué mal.

Puntos clave

  • Las APIs de los exchanges fallan en silencio más a menudo de lo que fallan a gritos. Un HTTP 200 con menos datos de los que pediste es el modo de fallo contra el que tienes que diseñar.
  • Nunca confíes en el resultado de un backtest hasta que hayas probado el propio simulador. Una curva de capital de aspecto limpio puede venir de una ejecución rota.
  • La aritmética de punto flotante sobre las cantidades de las órdenes produce errores de desfase de un paso que solo aparecen en pares concretos.
  • Decidir no custodiar nunca los fondos del usuario elimina toda una clase de problemas y crea uno más pequeño pero más afilado: tu sistema debe ser correcto sin poder deshacer nada jamás.
  • Cada comodidad que le ofreces al usuario se convierte en una restricción en algún punto del motor. Ese intercambio suele valer la pena, pero nunca es gratis.

1. Supuse que una petición fallida parecería un fallo

Este es el que más tiempo costó, y es el primero sobre el que advertiría a cualquier desarrollador.

Descargamos velas históricas de los exchanges para ejecutar backtests. El modelo mental ingenuo es que una petición o tiene éxito o da error: obtienes tus datos, o recibes un código distinto de 200 y lo manejas. Las APIs reales de los exchanges no se comportan así bajo carga. Devuelven HTTP 200 con menos velas de las que pediste, y ninguna señal de que algo salió mal.

Aquí tienes un caso real de nuestros logs, y las cifras son exactas. Un backtest de periodo largo pidió unas 536,000 velas. A las 16:34 recibió 58,000. Estado 200. Sin campo de error. Sin advertencia. La misma petición, sin cambios, a las 17:54 recibió las 535,000 completas.

Piensa en lo que eso significa si no estás verificando. El motor simula tan tranquilo una estrategia sobre el once por ciento del historial previsto, produce una curva de capital de aspecto plausible, e informa un resultado. El usuario lee un número que es incorrecto de una forma que nada en la salida revela. Eso es peor que un crash. Un crash es honesto.

La solución es conceptualmente aburrida y era fácil de pasar por alto: después de descargar, verifica que los datos que recibiste realmente cubren el periodo que pediste, y trata cualquier faltante como un fallo aunque el transporte dijera que todo estaba bien. La cobertura es una propiedad que tienes que afirmar, no una que puedas asumir.

Había una lección de segundo orden escondida tras la primera. Una vez que empezamos a detectar el truncamiento, el movimiento obvio era hacer fallar la ejecución. Pero las cifras de arriba muestran que estos truncamientos son transitorios: la petición idéntica una hora después devolvió los datos completos. Fallar sin más habría quemado la cuota del usuario por lo que no es más que un tropiezo temporal del lado del exchange. Así que el comportamiento correcto es reintentar una vez antes de fallar, lo que implicó rehacer el camino de fallo para que una condición reintentable no quede marcada de forma permanente como fallo a la salida.

De esto salieron dos reglas, y me llevaría ambas a cualquier proyecto que hable con una API de terceros:

  • Valida la forma de una respuesta exitosa, no solo su código de estado. El código de estado te dice que la petición se completó. No te dice que la respuesta sea lo que pediste.
  • Distingue los fallos transitorios de los permanentes antes de decidir qué hacer. Se ven idénticos en el momento en que ocurren y merecen respuestas opuestas.

2. Supuse que el resultado de un backtest era un resultado

Relacionado, pero un error distinto, y va más hondo que el problema del truncamiento.

Cuando construyes un simulador, gastas tu esfuerzo de pruebas en las estrategias. ¿Se comporta correctamente la lógica DCA? ¿Se disparan las salidas donde deberían? Esa es la pregunta interesante, así que es ahí donde va la atención de forma natural.

El problema es que un simulador roto produce una salida que se ve exactamente igual que la de uno que funciona. Una ejecución que procesó en silencio una fracción de los datos igual arroja un número. Una ejecución cuyas condiciones de salida nunca se evaluaron igual arroja un número, y uno sospechosamente plano. Hemos visto resultados volver en exactamente +0.00% con cero operaciones, que es la señal más ruidosa posible de que algo va mal con la maquinaria, y la más fácil de malinterpretar como «esta estrategia no hace nada».

El cambio que arregló esto fue cambiar qué probamos. Prueba el simulador, no la estrategia. En concreto, eso significa afirmar cosas que la estrategia no debería poder cambiar: que el número de velas procesadas coincide con el número descargado, que una ejecución sobre un periodo conocido realmente abarca ese periodo, que una configuración con disparo garantizado produce operaciones. Cuando esos invariantes se cumplen, un resultado de aspecto extraño es un hallazgo real sobre la estrategia. Cuando no, el resultado es ruido disfrazado.

La forma general de esto, para cualquier cosa que simule: un simulador que no puede fallar a gritos fallará en silencio, y el fallo silencioso en un sistema que produce números es el peor modo de fallo que existe. La gente actúa sobre números. No actúa sobre trazas de pila.

3. Subestimé cuánto trabajo de precisión es en realidad dimensionar órdenes

Este no es intelectualmente difícil. Simplemente es mucho más grande de lo que parece, y se esconde en un lugar donde los errores son caros.

Los exchanges no aceptan cantidades arbitrarias. Cada par de trading tiene un tamaño de paso (step size) para la cantidad y un tamaño de tick (tick size) para el precio, y las órdenes que no se alinean son rechazadas. Así que cada cantidad que el sistema calcula hay que redondearla hacia abajo a un paso válido antes de enviarla. Eso suena a una sola función de utilidad.

Entonces llega el punto flotante. Considera redondear hacia abajo 0.29 a un paso de 0.01. Escálalo y IEEE-754 te da 28.999999999999996, que se redondea hacia abajo a 28, que al reescalar vuelve a 0.28. El valor ya estaba alineado al paso. La aritmética lo movió un paso entero hacia abajo, y lo hizo en silencio, en la dirección de una orden más pequeña. Arreglarlo requiere un épsilon durante el paso de escalado, lo bastante pequeño para que un valor genuinamente por debajo como 0.289999 siga redondeándose hacia abajo correctamente, y lo bastante grande para absorber el ruido de representación.

Esa es una corrección de una línea que requirió depuración real para encontrarla, porque el síntoma no es una excepción. Es una orden ligeramente más pequeña de lo previsto, en algunos pares y en otros no, según dónde caiga el tamaño de paso respecto a la representación binaria.

El punto más amplio, y la razón por la que enumero esto como algo que subestimé: la precisión en un sistema de trading no es una función de utilidad, es una categoría de trabajo. Los tamaños de paso y de tick difieren por par, por exchange, y entre spot y futuros. Los valores mínimos de orden no son un solo número que puedas consultar y dejar fijo en el código. Vienen del exchange por símbolo, y en algunas plataformas dependen del precio actual. Cada vez que te encuentres escribiendo una sola constante para algo que el exchange define por par, estás escribiendo un bug con mecha de retardo.

Esto se lo mostramos a los usuarios en vez de ocultarlo, porque la alternativa es una orden que se rechaza por razones que ellos no pueden ver.

4. La restricción que no esperaba que me gustara

La última no es un error. Es una decisión cuyas consecuencias subestimé, pero para bien.

Freya nunca custodia los fondos de trading del usuario. Los bots operan a través de claves API del exchange con permiso para leer y operar, y nunca para retirar. El dinero permanece en la cuenta de exchange del usuario en todo momento.

Esperaba que esto fuera un punto de marketing. Resultó ser uno arquitectónico, e hizo el sistema más pequeño. No hay libro mayor de custodia, ni saldo interno que reconciliar contra uno externo, ni cola de retiros, ni política de cold wallet para el capital de trading. Toda una categoría de los problemas más difíciles del software financiero simplemente no existe, porque nunca asumimos aquello que los crea.

Lo que lo reemplaza es más estrecho y más afilado: tu sistema tiene que ser correcto en tiempo real, porque no puede deshacer nada. Con custodia puedes, en principio, revertir una transferencia interna. Colocar una orden en la cuenta de exchange de otra persona es definitivo en el momento en que se llena. Eso empuja todo el peso de la ingeniería hacia la corrección antes de que salga la petición en lugar de la reconciliación posterior, lo cual es un intercambio que volvería a hacer.

Lo que me diría a mí mismo al principio

  • Asume que toda API de terceros a veces tiene éxito de forma incorrecta, y diseña la detección para ello antes de necesitarla.
  • Escribe pruebas de invariantes para tu simulador antes de escribir aserciones sobre estrategias. Las pruebas de estrategia no valen nada hasta que pasen las del simulador.
  • Trata la precisión como un dominio, no como un ayudante.
  • Elige tus restricciones temprano. Las que aceptas de entrada son mucho más baratas que las que descubres después.

Ninguno de estos era un problema exótico. Eran problemas de ingeniería corrientes que archivé como «pequeños» durante la planificación, que es exactamente cómo se volvieron caros.


Lecturas Relacionadas

Crea una cuenta

Pon en práctica lo que aprendiste y abre una cuenta de Freya hoy.

Regístrate ahora
Este contenido es solo con fines educativos y no constituye asesoramiento financiero. El trading de criptomonedas conlleva riesgos significativos. Nunca invierta más de lo que puede permitirse perder.