TitaniumBOE-Sim Simulador del protocolo BOE de Cboe
Simulador del lado del exchange para el protocolo binario BOE de Cboe, con matching engine precio-tiempo, bots de trading, REST API, WebSocket y dashboard en un solo JAR.
- Stack
- Java 21 · Javalin · RocksDB · WebSocket · Docker
- Estado
- Terminado
- Año
- 2025
- Código
- GitHub ↗DeepWiki ↗
Últimos trades
Envía una orden y mira cómo se cruza con el libro.
Ver los bytes de esta orden ↓En el servidor real, el 99 % de las confirmaciones llega en menos de 3,5 ms.
En el cable, cada orden es un mensaje binario de 56 bytes. Toca un campo para ver cómo se decodifica.
Mensaje New Order real · 56 bytes
Inicio de mensaje offset 0–1 · 2 bytes
BA BA
Dos bytes fijos que marcan dónde empieza cada mensaje BOE en el flujo TCP.
Longitud offset 2–3 · 2 bytes
54 bytes
36 00 en Little Endian es 0x0036 = 54: el tamaño del mensaje sin contar los 2 bytes de inicio.
Tipo de mensaje offset 4 · 1 bytes
New Order
0x38 identifica una orden nueva. Con este byte el servidor decide cómo leer el resto.
Matching unit offset 5 · 1 bytes
0
Siempre 0 en los mensajes que envía el cliente.
Número de secuencia offset 6–9 · 4 bytes
42
2A 00 00 00 → 0x0000002A = 42. En Little Endian el byte menos significativo va primero.
ClOrdID offset 10–29 · 20 bytes
ORD-0001
El ID que el cliente le da a su orden, en ASCII y relleno con NUL (00) hasta 20 bytes.
Side offset 30 · 1 bytes
Compra
El carácter '1' en ASCII (0x31) significa compra; '2' sería venta.
Cantidad offset 31–34 · 4 bytes
100
64 00 00 00 → 0x64 = 100 contratos, de nuevo en Little Endian.
Número de bitfields offset 35 · 1 bytes
2
Avisa que vienen 2 bytes de bitfields: cada bit activo anuncia un campo opcional al final del mensaje.
Bitfield 1 offset 36 · 1 bytes
Price + OrdType
0x0C = 0000 1100: el bit 2 anuncia Price y el bit 3 anuncia OrdType.
Bitfield 2 offset 37 · 1 bytes
Symbol + Capacity
0x41 = 0100 0001: el bit 0 anuncia Symbol y el bit 6 anuncia Capacity.
Price offset 38–45 · 8 bytes
187,2500 USD
8 bytes en Little Endian: 0x1C9274 = 1.872.500. Con 4 decimales implícitos queda 187,2500, sin coma flotante en el cable.
OrdType offset 46 · 1 bytes
Límite
'2' (0x32) es una orden límite: solo se ejecuta a 187,25 o mejor.
Symbol offset 47–54 · 8 bytes
AAPL
AAPL en ASCII, relleno con espacios (20) hasta 8 bytes.
Capacity offset 55 · 1 bytes
Customer
'C' (0x43): la orden es de un cliente, no de la cuenta propia del bróker.
Qué es BOE
BOE (Binary Order Entry) es el protocolo con el que los participantes envían órdenes a los mercados de opciones de Cboe en EE. UU. No es texto: cada mensaje es una secuencia exacta de bytes en Little Endian, con campos de tamaño fijo, precios con 4 decimales implícitos y campos opcionales que se anuncian con bitfields. Arriba puedes enviar una orden al libro y ver después sus bytes, codificados igual que en el simulador.
Qué construí
Un simulador del lado del exchange, escrito a partir de la especificación v2.11.90: acepta conexiones TCP, autentica sesiones, recibe órdenes BOE y las casa en un motor de matching con prioridad precio-tiempo. Alrededor de eso:
- Tres bots (market maker, trend follower y random trader) que mantienen actividad en el libro.
- REST API y WebSocket para market data y operaciones.
- Un dashboard web servido desde el mismo JAR.
- Persistencia en RocksDB: órdenes, trades y sesiones sobreviven a un reinicio.
Por dentro
- Cada conexión TCP corre en su propia virtual thread de Java 21: código de I/O bloqueante, fácil de leer, sin pagar un hilo del sistema operativo por cliente.
- El matching se sincroniza por símbolo, no de forma global: AAPL y MSFT se casan en paralelo sin bloquearse.
- Las escrituras a RocksDB van a una cola asíncrona: el cliente recibe su confirmación sin esperar al disco.
- Los mensajes forman una jerarquía de sealed classes, así que el compilador conoce todos los tipos posibles.
Qué tan cerca está del spec
Buena parte coincide byte a byte con la especificación, pero no todo, y las diferencias están documentadas en el repositorio. Los códigos de Cancel y Modify no son los del spec (0x39/0x3A en lugar de 0x45/0x4A), algunos textos se rellenan con espacios en vez de NUL y el heartbeat es más permisivo (10 s / 30 s en lugar de 1 s / 5 s). Esto último es a propósito, para que una pausa del GC o un breakpoint no tumben la sesión. Con un cliente BOE real, cancelar o modificar órdenes fallaría.
Números medidos
Con la prueba de carga del repositorio, en un equipo de 16 núcleos y con el servidor recién iniciado en cada corrida:
- 500 conexiones TCP simultáneas, sin rechazos.
- P99 del Order Acknowledgment entre 2,7 y 3,5 ms (900 órdenes por corrida, en 10 sesiones).
- Entre 4.600 y 9.200 peticiones REST por segundo.
- 347 tests automatizados, todos en verde.
La P99 se mide después de las fases de conexiones, logins y REST, que calientan el JIT. Midiendo la latencia sola, con el servidor en frío, una corrida llegó a 11,7 ms.
Al medir descubrí que la propia prueba de carga estaba desactualizada: esperaba los códigos de sesión anteriores y enviaba órdenes para un símbolo que el servidor rechaza, así que los logins y la latencia llevaban tiempo sin medirse. La corregí antes de publicar estas cifras. La fase de memoria sigue sin servir: mide el heap del cliente, no el del servidor, así que no publico ese número.
El cuello de botella
El login no llega a la meta que me había puesto (200 por segundo): se queda en unos 50. Cada contraseña se verifica con BCrypt de coste 12, que consume del orden de 200 ms de CPU por intento; con 16 núcleos, el techo ronda los 60 logins por segundo. Es un costo deliberado: bajarlo abarataría un ataque de fuerza bruta.