Skip to content
All projects
LiveTradingSystems

FlashPoint

A low-latency limit order book and matching engine.

FlashPoint screenshot

Overview

FlashPoint is a single-instrument limit order book and matching engine, built milestone by milestone the way a production system would be: domain types before data structures, a tested container before an engine that mutates it, and a measured tuning pass instead of guessed optimisation. It supports limit and market orders, all three time-in-force rules (GTC/IOC/FOK), cancel and modify with correct queue-priority semantics, and publishes every action as an ordered, sequence-numbered event stream that a market-data consumer or a replay tool can read.

Problem

Matching-engine projects may skip the hard parts (real price-time priority, partial fills, cancel-by-id in O(1)) or bolt on a 'benchmarks' section with numbers that aren't stress-tested. There was no small, complete example of an order book built with the discipline a real trading system needs: strong types that make a price/quantity transposition a compile error, sanitizer-clean concurrency-adjacent memory management, and a performance narrative backed by reproducible measurement rather than intuition.

Solution

An intrusive doubly-linked FIFO queue over a pooled node vector for O(1) cancel from any queue position, sitting under ordered price-level maps for O(log L) level lookup. The matching engine is a thin template over that container, publishing a single flat trivially-copyable Event type so the whole session can be replayed byte-for-byte. Every milestone shipped with mutation-tested unit tests (deliberately broken code confirmed to fail the suite) before moving to the next.

Architecture

Domain types (Price/Quantity/OrderId/Side as non-interconvertible strong types) → OrderBook (price-time priority container, no matching logic) → MatchingEngine (crosses orders, owns venue policy like market-order protection bands, emits events) → Event stream (sequence-numbered, one record type for trades/acks/rejects/cancels/modifies) → demo + benchmarks consuming the same public API. Sanitizers run on every CI build across Linux/GCC, Linux/Clang, and macOS/AppleClang.

Tech Stack

C++20CMakeGoogleTestGoogle BenchmarkAddressSanitizerUndefinedBehaviorSanitizer

Challenges

  • Getting cancel-by-id to true O(1) without abandoning cache-friendly aggregation: solved with an intrusive queue over a pooled vector, addressed by 32-bit index rather than pointer, with cached per-level totals so depth queries never walk the queue.
  • A modify that loses queue priority internally re-submits the order, which risked the event stream publishing a phantom second 'Accepted' the client never sent: solved by splitting the matching core into a private apply() that submit() and modify() each wrap with their own acknowledgement.
  • Benchmarking on a device with no core-pinning: solved by treating results as relative costs rather than absolute latencies, and by building a synthetic long-running feed generator specifically to test workloads (e.g. level churn) the static benchmarks couldn't simulate.

Lessons Learned

  • Measurement beats intuition, repeatedly. A candidate data structure (sorted vector) was eliminated before writing it once a level-creation benchmark was added; a pooled allocator was built, measured to do nothing, and reverted; that dead end then led straight to a real bug (best_bid() being O(log L) while documented O(1)), fixed for a 2.4-4.7x win.
  • Mutation testing is what makes 'tests pass' mean something. Deliberately injecting bugs (transposed maker/taker, LIFO instead of FIFO, off-by-one crossing checks) and confirming the suite catches every one was the actual bar for measuring successful feature implementation.
  • Low-latency systems require careful designing at every stage, and trade-offs have to be made from the first step.

Future Improvements

  • A direct-indexed price ladder to close the remaining O(log L) gap on mutation paths, diagnosed but deliberately deferred
  • Self-trade prevention and per-order participant IDs
  • Multi-instrument sharding via one engine per symbol