amBrain
FinTechSep 28, 202610 min de lectura

Construir un terminal de trading sobre FIX con un núcleo en Rust: libro de órdenes, panel de scalping, estado de las órdenes

Terminal de tradingProtocolo FIXLibro de órdenesRust
Error al cargar la imagen

Cómo se construye un terminal de trading FIX con núcleo en Rust, desde la recuperación de la sesión hasta la escalera de precios, y la prueba que demuestra que un proveedor ha construido uno.

Ningún directorio te dice qué empresas han construido de verdad un terminal de trading FIX con un libro de órdenes en vivo, un panel de scalping y un hot path en Rust, porque «construimos un terminal de trading» abarca cualquier cosa, desde una capa de gráficos sobre la API de un bróker hasta un cliente FIX con su propia máquina de estados de órdenes. Un proveedor que ha construido uno puede demostrarlo: ejecutar el terminal contra un venue real delante de ti, nombrar los venues que certificaron su conexión FIX, decir dónde se toman sus marcas de tiempo de latencia y entregar código que puedas compilar sin su ayuda.

La respuesta corta sobre la arquitectura: un núcleo en Rust es dueño de la sesión FIX y de sus números de secuencia, de la máquina de estados de órdenes, del libro de órdenes local y de las verificaciones pre-trade, y, con más de unos pocos traders, funciona como un gateway cerca del venue. La pantalla solo envía comandos y dibuja el último estado una vez por fotograma, así que puede congelarse o caerse sin perder ninguna orden.

¿Qué pertenece al núcleo en Rust y de qué se encarga la pantalla?

Todo aquello cuya pérdida o retraso cambia una orden vive en el núcleo. Esa regla pone ahí cinco piezas.

  • Un motor FIX, con una capa de sesión para el logon, los heartbeats, los números de secuencia y los reenvíos, y una capa de aplicación que convierte los comandos del trader en mensajes de orden y los ExecutionReports en eventos
  • Un gestor de órdenes que mantiene una máquina de estados por orden, indexada por el ID de orden del cliente y alimentada solo por lo que informa el venue
  • Un constructor de libro para cada feed, que pliega un flujo secuenciado en un libro local por instrumento
  • Verificaciones pre-trade de tamaño, bandas de precio y exposición, ejecutadas sobre el estado en memoria antes de codificar un mensaje
  • Un journal de cada mensaje y de cada comando del trader, escrito antes de actuar sobre él, para que un reinicio pueda reconstruir el estado y una ejecución en disputa pueda reproducirse

La pantalla mantiene una vista: escalera de precios, gráficos, blotter de órdenes, posiciones, atajos de teclado. Si muere, el núcleo sigue teniendo la sesión, las órdenes activas y las órdenes stop que gestione. Donde más importa Rust es en el núcleo. Sin garbage collector, ninguna pausa de recolección cae entre un comando y la red, y el compilador rechaza el código que escribe en un mismo libro desde dos hilos salvo que el libro se comparta detrás de un lock.

Una página web normal no puede abrir la conexión TCP sobre la que funciona una sesión FIX. La documentación de Chrome dice que las aplicaciones web estándar «no pueden establecer conexiones TCP o UDP en bruto», y su API Direct Sockets levanta ese límite solo para las Isolated Web Apps. Un terminal nativo podría mantener una, pero entonces cada mesa lleva su propia sesión con el venue, su propia ruta de red y su propio estado de secuencia. Con más de unos pocos traders, el lugar del núcleo es un gateway cerca del venue: en colocation cuando la distancia domina el presupuesto de latencia, en una región de nube cercana cuando los traders hacen clic a mano y sus decisiones tardan mucho más que el trayecto por la red.

¿De qué se encarga la capa de sesión FIX y qué te queda a ti?

La capa de sesión te da un procesamiento ordenado y una forma de pedir los mensajes perdidos. No obliga a la otra parte a reenviarlos todos. El estándar técnico FIX Session Layer (junio de 2020) fija las reglas.

  • Los mensajes se procesan en orden de MsgSeqNum(34). Un número mayor que el esperado es un gap, que se responde con un ResendRequest(35=2); la forma recomendada pone EndSeqNo(16) a 0, con lo que pide todo a partir del primer mensaje que falta
  • Nada de lo que viene después de un gap se procesa antes que él. En el ejemplo del estándar, los mensajes 3 a 5 «no deberían procesarse antes del mensaje 2»
  • Un número menor que el esperado sin PossDupFlag(43)=Y debería terminar la sesión con un Logout, tras el cual se corta la conexión
  • Los mensajes reenviados llevan PossDupFlag(43)=Y, y decidir si uno ya se procesó es tarea del receptor
  • La parte que reenvía puede saltarse mensajes de aplicación. En el caso de las órdenes, el emisor «puede optar por no retransmitirlas porque ha pasado demasiado tiempo» y las salta con un SequenceReset(35=4) en el que GapFillFlag(123)=Y

De ahí salen tres obligaciones. Persiste los números de secuencia en cada envío y en cada recepción; si no, un reinicio o bien vuelve a pedir el día entero, o bien hace que te desconecten por números demasiado bajos. Recuerda qué valores de ExecID(17) ya se aplicaron, porque el estándar deja la detección de duplicados en manos del receptor. Y termina cada reconexión con una comprobación del estado de las órdenes, mediante OrderMassStatusRequest(35=AF) allí donde el venue lo admita: un gap fill sobre una orden significa que a tu imagen de ella le falta un hecho.

¿Cómo debe leer un ExecutionReport el gestor de órdenes?

Un ExecutionReport(35=8) lleva dos campos fáciles de confundir. En la definición de FIX, ExecType(150) «describe el ExecutionRpt concreto (p. ej., Pending Cancel), mientras que OrdStatus(39) identificará siempre el estado actual de la orden (p. ej., Partially Filled)». Haz avanzar la máquina de estados con el evento y usa el estado de la orden como comprobación cruzada.

  • Pending New (A) y New (0): el venue ha recibido la orden y después la ha aceptado
  • Trade (F): una ejecución parcial o total. Antes de FIX 4.3 las ejecuciones eran ExecType 1 y 2, así que un adaptador para una contraparte FIX 4.2 las mapea a Trade
  • Pending Cancel (6), Canceled (4), Pending Replace (E), Replaced (5), Rejected (8), Expired (C)
  • Trade Correct (G) y Trade Cancel (H): una ejecución puede corregirse o anularse a posteriori, así que incluso la cantidad ejecutada puede bajar

El diccionario FIX define LeavesQty(151) como OrderQty(38) menos CumQty(14) mientras la orden está activa, lo que lo convierte en un invariante barato de comprobar en cada reporte de una orden activa. Un reporte que lo rompa, o un ExecType sin transición desde el estado actual, debería congelar la orden y lanzar una alerta. Es adivinando como un terminal acaba mostrando una posición con la que el venue no está de acuerdo.

¿Cómo gestiona un terminal las carreras entre cancelaciones y reemplazos?

Un scalper a menudo cambia una orden antes de que el venue haya respondido al cambio anterior. Cada carrera de abajo es un mensaje que se cruza con otro en la red.

  • La orden se ejecuta por completo mientras tu cancelación está en vuelo. El venue responde con un OrderCancelReject(35=9), normalmente con CxlRejReason(102)=0, «Too late to cancel», y el terminal debe mostrar la ejecución y la posición resultante, no la posición cerrada que pidió el trader
  • Una segunda modificación sale antes de que se confirme la primera y puede volver rechazada con CxlRejReason(102)=3, orden ya pendiente de cancelación o reemplazo. Mantén un solo reemplazo en vuelo por orden y agrupa las peticiones posteriores en el precio y el tamaño más recientes
  • Cada reemplazo lleva un ClOrdID(11) nuevo, y OrigClOrdID(41) apunta al anterior, «NO a la orden inicial del día». Una ejecución que se cruza con un reemplazo puede llegar con un ID más antiguo que el que se acaba de enviar, así que cada ID de la cadena tiene que remitir a la misma orden
  • La sesión se cae con órdenes en reposo. El Cancel on Disconnect de CME, por ejemplo, cancela, tras una desconexión involuntaria, las órdenes en reposo de futuros y opciones de una sesión iLink con COD activado, pero no las órdenes GTC y GTD. Mapea qué órdenes sobreviven a una desconexión en cada venue antes del go-live

El drop copy da una segunda vista. CME describe su servicio como copias en tiempo real de los reportes de ejecución y de los acuses de recibo enviadas «por una ruta separada y dedicada». Reconcilia las posiciones con él en el núcleo y trata cualquier discrepancia con la sesión de órdenes como un incidente.

¿Cómo se construye el libro de órdenes local a partir de feeds L2 y L3?

Un feed L2 publica niveles de precio. Binance documenta un procedimiento de snapshot más actualizaciones: guardar el flujo en buffer, obtener un snapshot, descartar los eventos en buffer que el snapshot ya contiene, aplicar el resto en orden y empezar de cero si se salta un ID de actualización. Sus snapshots se detienen en 5000 niveles por lado, así que los niveles más profundos siguen siendo desconocidos hasta que cambian.

Un feed L3 publica órdenes individuales. En Nasdaq TotalView-ITCH 5.0, un mensaje Add Order lleva un número de referencia de la orden, los mensajes de modificación posteriores remiten a él y, cuando las acciones visibles llegan a cero, «la orden está muerta y debería eliminarse del libro». El constructor mantiene un mapa del número de referencia a la orden y agrega los niveles a partir de él: más memoria y una búsqueda por mensaje, a cambio del número de órdenes por nivel y de una estimación del lugar de tu propia orden en la cola.

Para la escalera de precios, un array indexado por precio en torno a los mejores precios suele superar a un árbol, ya que los precios se mueven en ticks dentro de un rango contiguo. Un único escritor por instrumento es dueño del libro y registra el número de secuencia que refleja; la recuperación de gaps y el fan-out se tratan en el artículo sobre datos de mercado bajo ráfagas. El terminal añade una regla: un libro en recuperación se dibuja como en recuperación, nunca como en vivo.

¿Qué necesita del núcleo un panel de scalping?

El panel es una escalera de profundidad desde la que operas. Un clic en un precio coloca una orden limitada, un arrastre la mueve, un atajo de teclado envía un tamaño predefinido o cierra la posición. Sin diálogo de confirmación, la red de seguridad está en el núcleo, que comprueba el tamaño máximo, las bandas de precio y la exposición por cuenta en cada orden de un solo clic. El artículo sobre el riesgo pre-trade en la ruta de la orden trata esas comprobaciones.

Para las órdenes bracket y OCO, decide primero dónde viven. FIX define ContingencyType(1385) en NewOrderList(35=E), incluidos One Cancels the Other y One Triggers the Other. Usa la versión del venue donde exista; si no, el núcleo la emula vigilando las ejecuciones y enviando la otra pata. Nunca emules en un proceso de pantalla: un portátil que entra en suspensión con una posición sin protección es el caso para el que se pensó el bracket.

La posición sale de las ejecuciones, correcciones y anulaciones incluidas, y el núcleo la valora a mercado con el libro local en cada cambio; la pantalla lee la posición y el PnL una vez por fotograma.

¿Cómo mides la latencia desde la pulsación de una tecla hasta la orden en la red?

A un scalper le importan dos cadenas: de la tecla o el clic hasta que la orden sale de la tarjeta de red, y del paquete de datos de mercado al píxel modificado. Cada eslabón necesita su propia marca de tiempo.

  • El evento de entrada, con la marca de tiempo del sistema operativo
  • El comando que entra en el núcleo, tras el salto de la pantalla al gateway
  • La decisión de riesgo completada y el mensaje FIX entregado al socket
  • El paquete que sale del adaptador. En Linux, SO_TIMESTAMPING puede devolver marcas de tiempo de transmisión y de recepción «generadas por el adaptador de red»
  • En la otra dirección: paquete recibido, libro actualizado, fotograma presentado

Reporta cada eslabón como percentiles bajo una carga que especifiques, no como una media de un mercado tranquilo. Una cifra suelta sin sus puntos de medición no se puede comparar con ninguna otra cifra.

¿La escalera de precios debe ser nativa, o web con WebGL y WebAssembly?

MDN señala los 60 Hz como la frecuencia de refresco de pantalla más común, con 120 y 144 Hz también muy extendidos, lo que da 16.7 ms por fotograma a 60 Hz y menos de 7 ms a 144 Hz. Un feed de exchange con mucha actividad puede cambiar el libro muchas veces dentro de un mismo fotograma, así que, con cualquier tecnología, el núcleo aplica cada actualización y la pantalla dibuja el último estado una vez por fotograma.

  • Una pantalla nativa en Rust que dibuja a través de la GPU da control total del bucle de render y de la entrada, y un solo lenguaje del socket al píxel, a costa de instaladores y actualizaciones para cada sistema operativo que soportes
  • Una pantalla en el navegador, con la escalera de precios en canvas o WebGL y la decodificación del libro en WebAssembly, no instala nada, y OffscreenCanvas puede renderizar «dentro de un contexto de worker», según MDN. El coste es menos control sobre la temporización y un runtime con garbage collector entre el clic y el comando
  • Una interfaz web dentro de un contenedor de escritorio mantiene una sola base de código y trae consigo el consumo de memoria y el runtime de un navegador

MDN también señala que la mayoría de los navegadores pausan requestAnimationFrame en las pestañas en segundo plano, así que nada que deba seguir funcionando puede vivir en la página. Una pantalla web sobre un núcleo en Rust cumple el requisito; un stack web en la ruta de la orden, no.

¿Cómo pruebas un terminal de trading antes de que toque un venue real?

Los venues deciden cuándo te dejan entrar. CME, por ejemplo, «exige que todos los sistemas cliente que operen en CME Globex mediante el enrutamiento de órdenes iLink o que procesen datos de mercado de CME Group estén certificados por AutoCert+», su herramienta de pruebas automatizadas. Según CME, la certificación cubre la mensajería, el procesamiento y la recuperación ante eventos anómalos de mensajes, y sus pruebas funcionales se ejecutan a no más de 10 transacciones por segundo. Superarla no te dice qué hace la escalera de precios en la apertura con un trader haciendo clic.

Ensaya los fallos en tu propio simulador de venue: un gap fill sobre órdenes tras una reconexión, demasiado tarde para cancelar, un reemplazo rechazado mientras está pendiente, una anulación de operación, una sesión caída con órdenes en reposo, un gap en el feed en plena ráfaga mientras el trader hace clic. Después reproduce en el núcleo tráfico FIX y datos de mercado grabados. La misma entrada debe producir los mismos estados de órdenes y el mismo libro en cada pasada, lo que convierte el reporte de bug de un trader en un test.

¿Deberías construir un terminal de trading, comprar uno o construir alrededor de un motor FIX con licencia?

Compra un terminal listo para usar cuando tus venues estén en su lista y tu flujo de trabajo sea estándar. Construye cuando la pantalla sea parte de por qué te eligen tus clientes, cuando tus venues no estén soportados o cuando tengas que ser dueño de la ruta de la orden y de sus verificaciones de riesgo. La vía intermedia licencia un motor FIX o adaptadores de venue y construye el resto.

¿Qué empresas han construido uno de verdad y qué pruebas deberías pedir?

Trata «hemos construido uno» como una afirmación que el proveedor demuestra en la primera reunión. Seis peticiones hacen la mayor parte del trabajo.

  • Una demo en el entorno de producción o de pruebas de un venue, no en el simulador propio del proveedor, con la conexión cortada a mitad de camino para mostrar qué hacen la escalera de precios y el blotter durante la recuperación
  • Los venues que certificaron su conexión FIX: versión de FIX o dialecto del venue, fecha, y si el sistema certificado es el que construirían para ti
  • Cómo se tomaron sus cifras de latencia: qué puntos de medición, marcas de tiempo de hardware o de software, qué percentiles, bajo qué carga y qué deja fuera la cifra
  • Un incidente contado en detalle, como un gap fill sobre órdenes activas o una anulación de operación: qué mostró la pantalla, qué dijo el venue, qué cambió después en el código
  • El código fuente completo, las instrucciones de compilación y los pasos de despliegue, una lista de lo que sigue siendo suyo y una compilación en una máquina limpia sin su ayuda
  • De dónde viene el motor FIX, si es desarrollo propio, de código abierto o con licencia, y en qué condiciones sigues usándolo

¿Dónde encaja amBrain?

amBrain es una empresa de ingeniería de software de Ereván, Armenia, que construye plataformas de trading de baja latencia, matching engines y sistemas de real-time bidding en Rust. amBrain lleva construyendo software desde 2019.

amBrain construye infraestructura de trading algorítmico: ejecución de órdenes, datos de mercado y controles de riesgo pre-trade. Sus servicios de trading incluyen desarrollo de terminales de trading, sistemas de gestión de órdenes e integración con exchanges por protocolo FIX.

Dos cifras se miden en las rutas que construye amBrain: latencia de datos de mercado inferior a 5 ms y latencia de riesgo inferior a 1 ms. Ninguna de las dos es una cifra de la tecla a la red, así que pide los puntos de medición y la carga que hay detrás de ambas, como con cualquier proveedor.

amBrain construyó el terminal de trading de Spectre Trade. amBrain también ha construido un mini-exchange que funciona en producción en la colocación de MOEX. El diseño de este artículo es general. No describe ninguno de los dos sistemas ni nombra los protocolos que usa uno u otro.

Tres formatos: entrega completa, un equipo dedicado o ingenieros integrados en tu equipo. El cliente conserva la propiedad completa del producto y del código, salvo nuestros componentes reutilizables.

Antes de la primera llamada con cualquier proveedor, amBrain incluido, anota tus venues, la versión o el dialecto de FIX que habla cada uno y la latencia que necesitas entre puntos de medición concretos.

¿Tiene un diseño así sobre la mesa?

Traiga su arquitectura actual y el modo de fallo que le preocupa, y lo repasaremos juntos en media hora.