Verified single-node core

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.

<200ns
Local Match Snapshot
5M+/s
Throughput Target
Rust core
Verified Engine

Built for the hot path

Every allocation, every branch, every cache line considered. Designed from the ground up for deterministic, predictable latency.

P/T

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
4x

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
O1

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

How it works

Single-threaded matching core with no locks on the hot path. Every layer designed for deterministic, cache-friendly execution.

1

Ingest

Zero-copy binary codec with length-prefixed framing. Messages decoded directly from the wire buffer with no intermediate allocations.

2

Match

Single-threaded core executes price-time priority matching. No locks, no atomics, no contention — fully deterministic on every invocation.

3

Allocate

Pool allocator backs all order storage. A single Vec with free list provides O(1) alloc and dealloc with optimal cache locality.

4

Deliver

Fast-ack and journal-backed publisher fanout deliver reports through bounded per-connection buffers with recovery-aware sequencing.

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.

01 · Orders

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
Read the full guide →
Limit BUY · 200 @ $100.50 · resting
02 · Orders

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
Read the full guide →
Market BUY · 500 sweep · 3 fills
03 · Orders

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 the full guide →
Cancel by ID · O(1) pointer unlink
04 · Book

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
}
Read the full guide →
L2 ladder · 6 levels each side · live SPY-class chain
05 · Durability

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
Read the full guide →
Journal replay · seq 0..1247 · Acquire/Release

Public performance envelope

The engine ships Criterion microbenchmarks and an end-to-end stress harness. Validate on your hardware before committing latency budgets.

<300ns
published target
Single Match
Current public target for the single-node matching core
5M+/s
published target
End-to-End Throughput
Public throughput target for the TCP-to-journal pipeline
Criterion + stress
included
Verification Surface
Microbenchmarks and live pipeline validation ship with the repo

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.

PBT

Invariant Testing

Unit, integration, property-based, and adversarial protocol checks cover correctness, recovery, and edge-case behavior.

4

Modular Crates

Core, I/O, journal, and server crates stay separated by responsibility boundary for cleaner testing and iteration.

Planned commercial modules

These are planned modules beyond the Apache 2.0 core, not current GA product capabilities.

HA
Wave 2

HA/DR Cluster

Planned replication, standby, and failover orchestration for multi-node production deployments.

RK
Wave 1

Risk Controls

Planned pre-trade limits, kill switches, and configurable risk policy for accounts, firms, and symbols.

AU
Wave 2

Compliance & Audit

Planned retention workflows, signed audit exports, and regulated-environment compliance tooling.

GW
Wave 1

Protocol Gateways

Planned FIX-first gateway work, followed by additional venue and market-data protocol adapters.

MT
Wave 2

Multi-Tenant

Planned account hierarchy, RBAC, quotas, and tenant isolation for managed deployments.

OB
Wave 1

Observability Suite

Planned latency analytics, replay forensics, dashboards, and richer operational incident tooling.

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
Zero-copy decodeFixed-point pricesLength-prefixed framing4 message types

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.com

Tell 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