TitaniumBOE-Sim Simulador del protocolo BOE de Cboe
Exchange-side simulator for Cboe's BOE binary protocol, with a price-time matching engine, trading bots, REST API, WebSocket and a dashboard in a single JAR.
- Stack
- Java 21 · Javalin · RocksDB · WebSocket · Docker
- Status
- Completed
- Year
- 2025
- Code
- GitHub ↗DeepWiki ↗
Latest trades
Send an order and watch it cross the book.
See this order’s bytes ↓On the real server, 99% of acknowledgments arrive in under 3.5 ms.
On the wire, every order is a 56-byte binary message. Tap a field to see how it decodes.
Real New Order message · 56 bytes
Start of message offset 0–1 · 2 bytes
BA BA
Two fixed bytes that mark where each BOE message starts in the TCP stream.
Length offset 2–3 · 2 bytes
54 bytes
36 00 in Little Endian is 0x0036 = 54: the message size, not counting the 2 start bytes.
Message type offset 4 · 1 bytes
New Order
0x38 identifies a new order. This byte tells the server how to read the rest.
Matching unit offset 5 · 1 bytes
0
Always 0 in messages sent by the client.
Sequence number offset 6–9 · 4 bytes
42
2A 00 00 00 → 0x0000002A = 42. In Little Endian the least significant byte comes first.
ClOrdID offset 10–29 · 20 bytes
ORD-0001
The ID the client gives its order, in ASCII and padded with NUL (00) up to 20 bytes.
Side offset 30 · 1 bytes
Buy
The character '1' in ASCII (0x31) means buy; '2' would be sell.
Quantity offset 31–34 · 4 bytes
100
64 00 00 00 → 0x64 = 100 contracts, Little Endian again.
Number of bitfields offset 35 · 1 bytes
2
Says 2 bitfield bytes follow: each set bit announces an optional field at the end of the message.
Bitfield 1 offset 36 · 1 bytes
Price + OrdType
0x0C = 0000 1100: bit 2 announces Price and bit 3 announces OrdType.
Bitfield 2 offset 37 · 1 bytes
Symbol + Capacity
0x41 = 0100 0001: bit 0 announces Symbol and bit 6 announces Capacity.
Price offset 38–45 · 8 bytes
187.2500 USD
8 bytes in Little Endian: 0x1C9274 = 1,872,500. With 4 implied decimals that is 187.2500, with no floating point on the wire.
OrdType offset 46 · 1 bytes
Limit
'2' (0x32) is a limit order: it only fills at 187.25 or better.
Symbol offset 47–54 · 8 bytes
AAPL
AAPL in ASCII, padded with spaces (20) up to 8 bytes.
Capacity offset 55 · 1 bytes
Customer
'C' (0x43): the order belongs to a customer, not the broker's own account.
What BOE is
BOE (Binary Order Entry) is the protocol participants use to send orders to Cboe's U.S. options markets. It isn't text: every message is an exact sequence of bytes in Little Endian, with fixed-size fields, prices with 4 implied decimals, and optional fields announced through bitfields. Above you can send an order to the book and then see its bytes, encoded the same way the simulator does it.
What I built
An exchange-side simulator, written from the v2.11.90 specification: it accepts TCP connections, authenticates sessions, receives BOE orders and matches them in a price-time priority matching engine. Around that:
- Three bots (market maker, trend follower and random trader) that keep the book active.
- A REST API and WebSocket for market data and trading.
- A web dashboard served from the same JAR.
- RocksDB persistence: orders, trades and sessions survive a restart.
Under the hood
- Every TCP connection runs on its own Java 21 virtual thread: blocking I/O code that is easy to read, without paying for an OS thread per client.
- Matching is synchronized per symbol, not globally: AAPL and MSFT match in parallel without blocking each other.
- Writes to RocksDB go through an asynchronous queue: the client gets its acknowledgment without waiting on disk.
- Messages form a sealed-class hierarchy, so the compiler knows every possible type.
How close it is to the spec
Much of it matches the specification byte for byte, but not all of it, and the differences are documented in the repository. The Cancel and Modify codes aren't the spec's (0x39/0x3A instead of 0x45/0x4A), some text fields are padded with spaces instead of NUL, and the heartbeat is more lenient (10 s / 30 s instead of 1 s / 5 s). That last one is deliberate, so a GC pause or a breakpoint doesn't drop the session. With a real BOE client, cancelling or modifying orders would fail.
Measured numbers
With the repository's load test, on a 16-core machine and a freshly started server for each run:
- 500 simultaneous TCP connections, none refused.
- Order Acknowledgment P99 between 2.7 and 3.5 ms (900 orders per run, across 10 sessions).
- Between 4,600 and 9,200 REST requests per second.
- 347 automated tests, all green.
The P99 is measured after the connection, login and REST phases, which warm up the JIT. Measuring latency alone on a cold server, one run reached 11.7 ms.
While measuring I found the load test itself was out of date: it expected the old session codes and sent orders for a symbol the server rejects, so logins and latency hadn't been measured in a while. I fixed it before publishing these numbers. The memory phase is still not useful: it measures the client's heap, not the server's, so I don't publish that number.
The bottleneck
Login doesn't reach the target I had set (200 per second): it sits at around 50. Every password is checked with BCrypt at cost 12, which takes on the order of 200 ms of CPU per attempt; with 16 cores, the ceiling is around 60 logins per second. It's a deliberate cost: lowering it would make a brute-force attack cheaper.