gtat-tech-career-kickstarte.../solution/README.md

69 lines
4.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 067):
| Message Type | Purpose |
|---|---|
| `INFO_CREATE_INSTRUMENT_REQUEST/RESPONSE` (130131) | Admin → Info: register instrument with `order_book_id` |
| `ORDER_BOOK_CREATE_REQUEST/RESPONSE` (140141) | Admin → Order Book: create order book |
| `ORDER_BOOK_DELETE_REQUEST/RESPONSE` (142143) | 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.