Matching¶
Two books, one interface¶
Order-level books live in core (flox/book/) and are distinct from
NLevelOrderBook, which aggregates per-level totals for market data. Matching
needs individual orders: to keep time priority, cancel by id, and refill
icebergs.
| Book | Shape | Use |
|---|---|---|
MatchingBook |
std::map + std::list, allocates |
Reference oracle. Easy to reason about. |
LadderBook |
tick-indexed dense ladders, intrusive FIFO over a node pool, occupancy bitmap | Performance path. O(1) best, O(1) next level, no steady-state allocation. |
They are interchangeable (MatchingEngine<MatchingBook> /
MatchingEngine<LadderBook>), and a differential fuzz keeps them
observationally identical; see Verification.
LadderBook book(LadderBook::Config{
.basePriceRaw = 0,
.tickRaw = Price::fromDouble(0.01).raw(),
.numLevels = 20000, // price band height, in ticks
.maxOrders = 1 << 20}); // node pool capacity
Matching policies¶
Matcher<Book> implements the allocation rule:
- Price-time (FIFO), the default. Best price first; within a price, oldest order first.
- Pro-rata. Allocation at a level is proportional to resting size, with the usual leftover pass.
Matcher<MatchingBook> m(MatchPolicy::ProRata);
m.setStpGroup(/*account*/ 10, /*firm*/ 1); // firm-scope self-trade prevention
Self-trade prevention¶
Four modes, applied when the aggressor and the maker share an STP scope (the
account, or the firm group registered via setStpGroup):
| Mode | Effect |
|---|---|
CancelNewest |
the incoming order is killed |
CancelOldest |
the resting order is pulled, matching continues |
CancelBoth |
both go |
Decrement |
the smaller leg is removed, the larger is reduced by that size; no trade |
STP interacts with FOK: an all-or-none order cannot count liquidity it would
never be allowed to trade with, so the precheck excludes same-scope resting
size.
Order types and time in force¶
NewOrder carries the full venue vocabulary:
- Types:
LIMIT,MARKET,STOP_MARKET,STOP_LIMIT,TAKE_PROFIT*,TRAILING_STOP. - TIF:
GTC,IOC,FOK,GTD(withexpiryNs),POST_ONLY. - Iceberg.
visibleQuantityshows a peak and hides the rest; the hidden reserve is real liquidity for matching and stays out of the public feed. - Peg.
PegRef::{Bid,Ask,Mid}plus a signed offset; repriced at each submit boundary, tick-aligned, clamped so it never crosses. - OCO.
ocoGroup; a fill on one leg cancels its siblings. - Reduce-only. Perp orders that may only reduce a position, re-capped on submit, trigger, and modify.
The engine¶
MatchingEngine<Book> owns the lifecycle around the matcher: validation, risk
gates, stops, expiry, pegs, auctions, clearing, and event emission.
SymbolConfig cfg;
cfg.id = 1;
cfg.tickSize = Price::fromDouble(0.01);
cfg.minPrice = Price::fromDouble(1); // 0 = unchecked
cfg.maxPrice = Price::fromDouble(1000);
cfg.baseAsset = 0;
cfg.quoteAsset = 1;
MatchingEngine<MatchingBook> venue(cfg, [](const OutboundEvent& e) { publish(e); });
venue.submit(InboundCommand{order}, tsNs);
Every state-mutating input is an InboundCommand: NewOrder, CancelOrder,
ModifyOrder, MassCancel, Quote, LastLookDecision, SetMark,
ApplyFunding, AdminCmd. That is what makes deterministic replay possible;
see Runtime and recovery.
Pre-trade risk¶
Configured on SymbolConfig and adjustable live, so an operator can tighten
limits during volatility without a restart:
| Control | Config | Live setter |
|---|---|---|
| Tick / lot / min quantity | tickSize, lotSize, minQty |
— |
| Price band (collar) | minPrice, maxPrice |
— |
| Fat finger | maxOrderQty, maxOrderNotional |
setFatFinger |
| LULD volatility band | luldBps, luldHaltNs |
setLuldBps |
| Max resting orders per account | maxOpenOrders |
setMaxOpenOrders |
| Max position (perp) | maxPositionQty |
setPositionLimit |
| Margin requirement | initialMarginBps, maintenanceMarginBps |
setMarginBps |
| Halt | halted |
setHalted |
Market-maker features¶
- Last look.
lastLookWindowNs: the maker holds a fill and answers withLastLookDecision. On reject or timeout the held quantity does not trade, and both sides' reservations are returned. - MMP.
setMmp(account, qtyLimit, windowNs): if an account is filled faster than its limit inside the window, its book is pulled andMmpTriggeredfires. - Mass quote.
Quotereplaces both sides of a two-sided quote atomically.
Auctions and halts¶
Sequenced through AdminCmd, so they survive replay:
BeginPreOpen: accumulate without matching (a crossed book is allowed).OpenContinuous: uncross at the single volume-maximising price, then resume.ResumeAuction: clear a halt into a re-opening auction.HaltAndCancelAll: emergency halt that also pulls the resting book.Halt/Resume.