# Optivex — Reference Solution Reference implementation of the Optivex exchange and the system tests used to evaluate participant submissions. ## Architecture Overview Four components communicate over TCP using length-prefixed Protobuf 3 messages: ``` ┌──────────┐ │ Admin │ └──┬───┬───┘ │ │ ┌───────┘ └────────┐ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ Order Book │◄───│ Info │ └──────────────┘ └──────────────┘ ▲ ▲ │ ┌───────────────┘ │ │ ┌──────┴────┴─────┐ │ Risk Gateway │ └─────────────────┘ ``` - **Order Book** — Matching engine. No upstream dependencies. - **Info** — Market data. Mirrors order book state to serve TOP_OF_BOOK and PRICE_DEPTH_BOOK subscriptions. - **Admin** — Instrument lifecycle. Orchestrates creation across Order Book and Info. - **Risk Gateway** — Execution + risk. Enforces limits before forwarding orders to Order Book. Implements both the `execution` and `risk_limits` protocols. ## Protocol Extensions vs Template The solution adds **internal management messages** for Admin ↔ Order Book and Admin ↔ Info communication. These are not part of the participant-facing API. Added to `common.proto` (numbered at 130+/140+ to avoid collisions with the public API range 0–67): | Message Type | Purpose | |---|---| | `INFO_CREATE_INSTRUMENT_REQUEST/RESPONSE` (130–131) | Admin → Info: register instrument with `order_book_id` | | `ORDER_BOOK_CREATE_REQUEST/RESPONSE` (140–141) | Admin → Order Book: create order book | | `ORDER_BOOK_DELETE_REQUEST/RESPONSE` (142–143) | Admin → Order Book: delete order book | Note that `info.CreateInstrumentRequest` carries `order_book_id` (book already exists), while `admin.CreateInstrumentRequest` carries `tick_size` (book doesn't exist yet). The Admin component bridges this gap with a two-phase flow: create order book first, then register the instrument in Info with the resulting ID. ## Non-Obvious Design Decisions ### Matching engine uses negated prices for max-heap Python's `heapq` is min-heap only. Bids need max-heap behavior (highest price first). The sort key negates the price for bids: `(-price, timestamp, order_id)` vs `(+price, timestamp, order_id)` for asks. This gives price-time priority with `order_id` as a final tiebreaker. Cancel uses `list.remove()` + `heapify()` — O(n) rather than O(log n) with lazy deletion, but sufficient for this scale. ### Info mirrors order book state from the event stream Info doesn't access the matching engine directly. `OrderBookClientAggregator` reconstructs book state from `OnOrderInserted`, `OnOrderCancelled`, and `OnTrade` events, maintaining quantity-per-price-level aggregates. A **pending trades mechanism** handles the ordering issue where `OnOrderInserted` references `trade_ids` for trades that haven't arrived yet. While pending trades exist, the book is considered inconsistent and market data updates are suppressed. Reads (`get_top_of_book`, `get_price_depth_book`) assert consistency. ### Rolling window limits use in-place list pruning `RiskLimitsStore` maintains timestamped event lists for each rolling metric (message rate, order quantity, order amount). On each check, entries older than `now - window_seconds` are pruned with `del list[:first_valid]`. This mutates the list in place — O(n) per check but amortized O(1) per entry since each is pruned exactly once. Limit checks follow a **check-then-record** pattern: `check_order_limits()` validates without side effects, and `record_order_attempt()` is called only after the check passes. This avoids polluting rolling windows with rejected orders. ### Decimal precision avoids float artifacts All prices and amounts go through `decimal_from_float()`: `Decimal(str(value)).quantize(Decimal("0.0001"))`. The `str()` intermediate avoids the well-known `Decimal(0.1)` → `0.100000000000000005...` problem.