Deterministic
order matching.
A Rust matching engine with price-time priority, crash recovery, O(1) cancellation, and a single-node core ready for trusted production pilots.
Features
Built for the hot path
Every allocation, every branch, every cache line considered. Designed from the ground up for deterministic, predictable latency.
Price-Time Priority
Sorted-vec order book with O(log n) binary-search insertion and O(1) best-price access. FIFO execution within each price level.
- Bids descending, asks ascending
- Deterministic match ordering
- Fixed-point pricing (no floats)
- Zero-allocation matching loop
Four Order Types
Full order type support out of the box. Each type with well-defined semantics and deterministic execution behavior.
- Limit — rest on book at price or better
- Market — fill at best available
- IOC — immediate fill or cancel
- FOK — atomic all-or-nothing
O(1) Cancellation
Dense-indexed lookup plus intrusive FIFO storage keeps cancel on the hot path predictable and allocation-aware.
- Direct slot addressing via OrderHandle
- Pool allocator with free list reuse
- Contiguous memory layout
- O(1) direct lookup and unlink
Architecture
How it works
Single-threaded matching core with no locks on the hot path. Every layer designed for deterministic, cache-friendly execution.
Ingest
Zero-copy binary codec with length-prefixed framing. Messages decoded directly from the wire buffer with no intermediate allocations.
Match
Single-threaded core executes price-time priority matching. No locks, no atomics, no contention — fully deterministic on every invocation.
Allocate
Pool allocator backs all order storage. A single Vec with free list provides O(1) alloc and dealloc with optimal cache locality.
Deliver
Fast-ack and journal-backed publisher fanout deliver reports through bounded per-connection buffers with recovery-aware sequencing.
Use Cases
Five workflows on the hot path
The hot path is small. Five 10–20 line snippets take you from typed order shape to resting book, swept fills, O(1) cancel, live L2 readouts, and crash-replay parity — using the same public API the production engine runs.
Rest a limit order with price-time priority
Construct a typed Order with fixed-point price (price × 106) and submit. If it doesn't cross, it rests with stable price-time priority — same-price orders live on an intrusive linked-list pool, so insertion is O(1) within the level and O(log n) to find the level via the sorted-vec ladder.
use ferromatch_core::{Order, OrderBook, OrderType, Side, TimeInForce}; let mut book = OrderBook::new(/* symbol_id */ 1); // Resting bid at $100.50 for 200 shares let order = Order::new( /* id */ 42, /* price */ 100_500_000, // $100.50 /* quantity */ 200, Side::Buy, OrderType::Limit, TimeInForce::GoodTilCancel, /* timestamp */ ts_ns, ); let result = book.submit(order)?; // result.fills — empty (resting) // result.status — OrderStatus::Resting
Sweep the offer side with a market order
Issue a market order. The matcher walks the ask ladder in price-then-time order, eating each resting offer until the taker quantity is satisfied. Returns a MatchResult with the per-maker Fill vector and the taker status — Filled, PartiallyFilled, orRejected (under FillOrKill).
use ferromatch_core::{Order, OrderType, Side, TimeInForce}; // Sweep 500 shares against the offer side let taker = Order::new( /* id */ 1001, /* price */ 0, // ignored for market /* quantity */ 500, Side::Buy, OrderType::Market, TimeInForce::ImmediateOrCancel, ts_ns, ); let result = book.submit(taker)?; for fill in &result.fills { // fill.maker_order_id, fill.price, // fill.quantity } // result.status — OrderStatus::Filled
Cancel by ID in O(1)
Cancel by order ID alone — no price, no side. A dense Vec<OrderHandle>indexed by ID points directly at the order's slot in the intrusive pool. Unlinking is a single pointer move; the price level is dropped O(1) when it empties. Zero hash lookups on the cancel path.
use ferromatch_core::{OrderBook, MatchError}; // Order 42 is somewhere in the book — we don't track // its price or side, just the ID we got back at submit let cancelled = book.cancel(42)?; // cancelled.remaining_quantity — unfilled portion // Idempotent retry pattern: match book.cancel(9999) { Err(MatchError::OrderNotFound) => {}, // gone Ok(order) => audit(order), Err(e) => return Err(e), }
Read top-of-book and L2 depth in one call
Read the book without a copy. best_bid() / best_ask() peek the head of each sorted-vec ladder; spread() returns the gap in fixed-point ticks. depth(side, levels) returns a Vec<(price, quantity)> aggregated per price level for ladder displays, market-surveillance overlays, and routing analytics.
use ferromatch_core::{OrderBook, Side}; // Top of book — direct ladder head reads let bid = book.best_bid(); // Option<i64> ticks let ask = book.best_ask(); // Option<i64> let spr = book.spread(); // ask − bid // L2 depth — aggregated quantity per level let bids: Vec<(i64, u64)> = book.depth( Side::Buy, 10); let asks: Vec<(i64, u64)> = book.depth( Side::Sell, 10); for (price_ticks, qty) in &bids { // render ladder row }
Replay an mmap journal on restart
Every accepted message lands in a 64-byte cache-line-aligned JournalEntry mmapped to disk before the publisher fans it out. On restart, a JournalConsumer tails the journal under Acquire/Release semantics — read every entry, replay it through the engine, and the in-memory book reaches byte-for-byte parity with the pre-crash state.
use ferromatch_journal::{EventJournal, JournalConsumer, EVENT_ORDER_STATUS, EVENT_TRADE}; // Open the journal that survived the crash let journal = EventJournal::open( "ferromatch.journal", 1 << 20, )?; let mut consumer = JournalConsumer::new(&journal); while let Some(entry) = consumer.read_next() { match entry.event_kind { EVENT_ORDER_STATUS => replay_submit(&entry), EVENT_TRADE => replay_fill(&entry), _ => {} } } // book is now consistent with the on-disk record
Benchmarks
Public performance envelope
The engine ships Criterion microbenchmarks and an end-to-end stress harness. Validate on your hardware before committing latency budgets.
Code Quality
Safe by default
Safe Rust Core
The matching core and server deny unsafe code. Low-level I/O and journal primitives keep tightly scoped unsafe isolated.
Invariant Testing
Unit, integration, property-based, and adversarial protocol checks cover correctness, recovery, and edge-case behavior.
Modular Crates
Core, I/O, journal, and server crates stay separated by responsibility boundary for cleaner testing and iteration.
Enterprise Roadmap
Planned commercial modules
These are planned modules beyond the Apache 2.0 core, not current GA product capabilities.
HA/DR Cluster
Planned replication, standby, and failover orchestration for multi-node production deployments.
Risk Controls
Planned pre-trade limits, kill switches, and configurable risk policy for accounts, firms, and symbols.
Compliance & Audit
Planned retention workflows, signed audit exports, and regulated-environment compliance tooling.
Protocol Gateways
Planned FIX-first gateway work, followed by additional venue and market-data protocol adapters.
Multi-Tenant
Planned account hierarchy, RBAC, quotas, and tenant isolation for managed deployments.
Observability Suite
Planned latency analytics, replay forensics, dashboards, and richer operational incident tooling.
Wire Protocol
Binary. Compact. Fast.
Zero-copy binary protocol designed for the lowest possible serialization overhead
Length-prefixed framing with a single-byte message type discriminator. No schema negotiation, no version headers, no padding — just raw, structured bytes on the wire.
Prices use fixed-point integer representation to eliminate floating-point ambiguity. All arithmetic stays in integer space throughout the matching pipeline.
// Wire format: length-prefixed binary [4 bytes] payload length (u32 BE) [1 byte ] message type // Message types 0x01 — NewOrder 0x02 — CancelOrder 0x03 — ExecutionReport 0x04 — Heartbeat // Fixed-point pricing $100.50 = 100_500_000
Talk to us
The FerroMatch core is open source under Apache 2.0. Reach out if you want design-partner support, deployment guidance, or early planning around enterprise modules.
hello@morphiqlabs.comTell us about your use case
- Your target throughput and latency requirements
- Asset classes and order types you need to support
- Regulatory environment and compliance needs
- Integration timeline and existing infrastructure