orderbook-rs

repository·main·Indexed 19 days ago

https://github.com/joaquinbejar/orderbook-rs

A high-performance, lock-free price level implementation for limit order books in Rust (v0.12.1). Designed for low-latency financial applications, it provides tools for order matching, market microstructure metrics (VWAP, micro-price, imbalance), depth analysis, and market impact simulation. The library supports multiple order types, Time-in-Force (TIF) settings, and concurrent access patterns.

Tokens
36K
Snippets
97
Records
149
Agent score
65%

What's inside orderbook-rs

  1. Overview of OrderBook-rs

    main

    OrderBook-rs is a high-performance, thread-safe limit order book engine implemented in Rust. It is designed for low-latency trading systems, focusing on concurrent access patterns and lock-free data structures to maximize throughput in high-frequency trading (HFT) scenarios.

    Key Capabilities

    • Lock-Free Architecture: Uses atomics and lock-free structures to minimize thread contention.
    • Diverse Order Types: Supports limit, iceberg, post-only, fill-or-kill (FOK), immediate-or-cancel (IOC), good-till-date, trailing stop, pegged, market-to-limit, and reserve orders.
    • Concurrent Price Levels: Each price level can be modified independently by multiple threads without blocking.
    • Scalability: Designed to handle millions of orders and thousands of price levels with minimal memory overhead.
    • Performance Monitoring: Includes built-in statistics tracking for benchmarking.
  2. Core Concepts: Order Types and Time-In-Force

    main

    OrderBook-rs supports several order types and execution instructions:

    Order Types

    • Limit Orders: Placed at a specific price; execute at or better than the limit price.
    • Market Orders: Execute immediately at the best available price.
    • Iceberg Orders: Hide large orders by showing only a specified visible portion; the visible portion replenishes as filled.

    Time-In-Force (TIF)

    • Gtc (Good-Till-Cancel): Remains in the book until filled or manually cancelled.
    • Ioc (Immediate-Or-Cancel): Fills immediately at the best price or is cancelled.
    • Fok (Fill-Or-Kill): Must be filled completely or the entire order is cancelled.
  3. Understand benchmark limitations and environment requirements

    main

    When interpreting benchmark results, be aware of the following constraints:

    Environment

    • Host: The current benchmarks are run on a workstation, not a performance-tuned rig. For tighter tail latency numbers, use a Linux host with isolcpus= and nohz_full= configured, with threads pinned and the system allocator replaced by jemalloc or mimalloc.
    • OS: Results may vary on macOS due to lack of thread pinning.

    Methodology

    • Closed-loop only: The numbers represent pure service time, not load-induced tail latency. They do not account for open-loop scenarios.
    • Single-threaded driver: Benches issue one operation at a time. A multi-writer driver would likely surface DashMap shard contention, which is not captured in these single-threaded tests.
  4. Advanced Feature: Functional Iterators for Depth Analysis

    main

    OrderBook-rs provides zero-allocation, lazy iterators for efficient depth analysis. These allow you to traverse the book without allocating large vectors.

    • levels_until_depth(side, depth): Iterate through levels until a cumulative depth is reached.
    • levels_with_cumulative_depth(side, levels): Iterate through a fixed number of levels, providing cumulative size at each step.
    • levels_in_range(side, min_price, max_price): Iterate through levels within a specific price range.
    // Iterate with cumulative depth tracking
    for level in book.levels_with_cumulative_depth(Side::Sell, 10) {
        println!("Price: {}, Size: {}, Cumulative: {}", 
                 level.price, level.size, level.cumulative);
    }
    
    // Iterate until cumulative depth reached
    let levels: Vec<_> = book
        .levels_until_depth(Side::Buy, 1000)
        .collect();
    
    // Combine with functional operations
    let total_volume: u64 = book
        .levels_until_depth(Side::Buy, 5000)
        .map(|level| level.size)
        .sum();
  5. Implement real-time trade monitoring with TradeListener

    main

    The TradeListener system allows you to react to trades as they occur. You can use a simple callback or an advanced channel-based pattern for multi-book management.

    Patterns demonstrated:

    • Callback Pattern: Register a TradeListener to receive immediate notifications of matches.
    • Channel Pattern: Use trade_listener_channels.rs to route trades through channels to a BookManager, enabling asynchronous processing and multi-symbol management.

    Run the basic demo with:

    cargo run --bin trade_listener_demo
    cargo run --bin trade_listener_demo
  6. Understand the HDR benchmark methodology

    main

    The HDR (High Dynamic Range) benchmarks in orderbook-rs follow these principles:

    • Histogram Resolution: Uses Histogram::<u64> sized for 1 ns to 1 s with three significant figures.
    • Sample Collection: Uses std::time::Instant::now() around a closure, with std::hint::black_box to prevent compiler optimizations from removing measured code.
    • Warmup: Long-running scenarios (like add_only or mixed_70_20_10) discard the first 200,000 operations before measurement. Pre-loading scenarios seed the book in a non-measured loop.
    • Workload Determinism: Uses a self-contained xorshift PRNG seeded with 0xA5A5_A5A5_A5A5_A5A5_A5A5 to ensure reproducible operation streams.
    • Closed-loop (Coordinated Omission): The driver waits for each engine call to return before issuing the next. This measures pure service time. It systematically under-reports tail latencies compared to a real-world load generator because it does not account for queueing delays that occur under saturation.
    • CPU Pinning: On Linux, you can reduce variance by pinning to a specific core using taskset -c <core>.
  7. Perform cross-stream gap detection with engine sequences

    main

    Starting from v0.8.0, the OrderBook provides a monotonic sequence number to ensure all outbound events can be ordered and gaps can be detected.

    Every outbound emission (such as TradeEvent or PriceLevelChangedEvent) is assigned a unique sequence number via OrderBook::next_engine_seq(). You can use OrderBook::engine_seq() to retrieve the current sequence.

    This allows consumers to merge events from different streams (e.g., TradeListener and PriceLevelChangedListener) into a single, strictly ordered view by tracking the engine_seq field present in:

    • TradeResult
    • TradeEvent
    • PriceLevelChangedEvent
    • BookChangeEntry (NATS payload)
    // Accessors for the monotonic counter
    let seq = order_book.engine_seq();
    // The next sequence will be minted for the next outbound event
    let next_seq = order_book.next_engine_seq();
  8. Understand the OrderQueue implementation and performance

    main

    The OrderQueue within a PriceLevel uses a hybrid lock-free architecture to prevent deadlocks and maximize throughput under high contention.

    Architecture

    Instead of a single queue that requires draining to find or remove specific orders, the system uses:

    1. dashmap::DashMap: Stores orders to allow $O(1)$ average-case time complexity for insertions, lookups, and removals by Id.
    2. crossbeam_skiplist::SkipMap<sequence, Id>: A sequence-keyed index that maintains FIFO (First-In-First-Out) order for matching, while allowing $O(\log n)$ ordered iteration and deterministic snapshots.

    Performance Characteristics

    • High Contention (Hot Spots): The architecture is highly efficient when operations are concentrated on a single price level, capable of reaching tens of millions of operations per second.
    • Price Level Distribution: Optimal performance is typically observed with 50-100 price levels. Performance may degrade if the number of price levels is very low (1-10), as per-level contention increases.
  9. Manage multiple order books with BookManager

    main

    The BookManager provides centralized orchestration for multiple order books. It allows for:

    • Unified Trade Listening: A single listener for trade events across all managed books.
    • Multi-Book Operations: Perform mass cancels across all books using cancel_all_across_books(), cancel_by_user_across_books(), or cancel_by_side_across_books().
    • Flexible Runtimes: Supports both synchronous and asynchronous (Tokio) execution models.
  10. Understand the Binary Wire Protocol framing

    main

    The binary wire protocol uses a fixed-layout, little-endian framing. Every frame consists of a length prefix, a message kind, and a payload. Frames are contiguous; there are no separators or trailers. Decoders should advance their read cursor by the bytes_consumed value returned from decode_frame.

    Frame Layout:

    FieldSizeTypeNotes
    len4 Bu32 LEByte length of kind + payload. Does NOT include the 4-byte prefix itself.
    kind1 Bu8MessageKind discriminant
    payloadVariable-Length is len - 1

    Note: The minimum legal len is 1 (kind byte present, zero-byte payload).

    +-------------------+--------+--------------------------+
    | len (u32 LE)      | kind   | payload                  |
    | 4 B               | 1 B    | len - 1 B                |
    +-------------------+--------+--------------------------+