Rust SDK

Order Book

Order book subscriptions with zero-copy parsing, efficient updates, and memory-optimized data structures

Order Book

The Rust SDK provides high-performance order book access with zero-copy parsing, incremental updates, and memory-efficient data structures.

Get Order Book Snapshot

Basic Query

use lux_dex_sdk::{Client, Result};

async fn get_orderbook(client: &Client) -> Result<()> {
    // Get top 10 levels
    let book = client.get_order_book("BTC-USD", 10).await?;

    println!("Best bid: {} @ {}",
        book.bids[0].size,
        book.bids[0].price
    );

    println!("Best ask: {} @ {}",
        book.asks[0].size,
        book.asks[0].price
    );

    println!("Spread: {:.2}",
        book.asks[0].price - book.bids[0].price
    );

    Ok(())
}

Full Depth

// Get full order book (all price levels)
let book = client.get_order_book_full("BTC-USD").await?;

println!("Total bid levels: {}", book.bids.len());
println!("Total ask levels: {}", book.asks.len());
println!("Total bid volume: {:.4}",
    book.bids.iter().map(|l| l.size).sum::<f64>()
);

Order Book Types

/// Order book snapshot
#[derive(Debug, Clone)]
pub struct OrderBook {
    pub symbol: String,
    pub bids: Vec<PriceLevel>,
    pub asks: Vec<PriceLevel>,
    pub timestamp: DateTime<Utc>,
    pub sequence: u64,
}

/// Single price level
#[derive(Debug, Clone, Copy)]
pub struct PriceLevel {
    pub price: f64,
    pub size: f64,
    pub order_count: u32,
}

impl OrderBook {
    /// Best bid price
    pub fn best_bid(&self) -> Option<f64> {
        self.bids.first().map(|l| l.price)
    }

    /// Best ask price
    pub fn best_ask(&self) -> Option<f64> {
        self.asks.first().map(|l| l.price)
    }

    /// Bid-ask spread
    pub fn spread(&self) -> Option<f64> {
        match (self.best_bid(), self.best_ask()) {
            (Some(bid), Some(ask)) => Some(ask - bid),
            _ => None,
        }
    }

    /// Mid price
    pub fn mid_price(&self) -> Option<f64> {
        match (self.best_bid(), self.best_ask()) {
            (Some(bid), Some(ask)) => Some((bid + ask) / 2.0),
            _ => None,
        }
    }

    /// Volume-weighted mid price
    pub fn vwap_mid(&self) -> Option<f64> {
        let bid = self.bids.first()?;
        let ask = self.asks.first()?;
        let total = bid.size + ask.size;
        Some((bid.price * ask.size + ask.price * bid.size) / total)
    }
}

WebSocket Streaming

Subscribe to Updates

use lux_dex_sdk::{Client, OrderBookUpdate};
use tokio_stream::StreamExt;

async fn stream_orderbook(client: &Client) -> Result<()> {
    let mut stream = client.subscribe_order_book("BTC-USD", 10).await?;

    while let Some(update) = stream.next().await {
        let update = update?;

        match update {
            OrderBookUpdate::Snapshot(book) => {
                println!("Snapshot: {} bids, {} asks",
                    book.bids.len(),
                    book.asks.len()
                );
            }
            OrderBookUpdate::Delta(delta) => {
                println!("Delta: {} bid changes, {} ask changes",
                    delta.bid_changes.len(),
                    delta.ask_changes.len()
                );
            }
        }
    }

    Ok(())
}

Delta Updates

/// Order book delta update
#[derive(Debug, Clone)]
pub struct OrderBookDelta {
    pub symbol: String,
    pub bid_changes: Vec<PriceLevelChange>,
    pub ask_changes: Vec<PriceLevelChange>,
    pub timestamp: DateTime<Utc>,
    pub sequence: u64,
}

/// Change to a price level
#[derive(Debug, Clone, Copy)]
pub struct PriceLevelChange {
    pub price: f64,
    pub size: f64,      // New size (0 = remove level)
    pub order_count: u32,
}

Local Order Book Maintenance

use lux_dex_sdk::{LocalOrderBook, OrderBookUpdate};
use std::collections::BTreeMap;

/// Maintains local order book state from stream updates
pub struct LocalOrderBook {
    symbol: String,
    bids: BTreeMap<OrderedFloat<f64>, PriceLevel>,  // Descending
    asks: BTreeMap<OrderedFloat<f64>, PriceLevel>,  // Ascending
    sequence: u64,
}

impl LocalOrderBook {
    pub fn new(symbol: &str) -> Self {
        Self {
            symbol: symbol.to_string(),
            bids: BTreeMap::new(),
            asks: BTreeMap::new(),
            sequence: 0,
        }
    }

    /// Apply update to local book
    pub fn apply(&mut self, update: OrderBookUpdate) -> Result<()> {
        match update {
            OrderBookUpdate::Snapshot(book) => {
                self.bids.clear();
                self.asks.clear();

                for level in book.bids {
                    self.bids.insert(OrderedFloat(-level.price), level);
                }
                for level in book.asks {
                    self.asks.insert(OrderedFloat(level.price), level);
                }

                self.sequence = book.sequence;
            }
            OrderBookUpdate::Delta(delta) => {
                // Verify sequence continuity
                if delta.sequence != self.sequence + 1 {
                    return Err(Error::SequenceGap {
                        expected: self.sequence + 1,
                        received: delta.sequence,
                    });
                }

                // Apply bid changes
                for change in delta.bid_changes {
                    let key = OrderedFloat(-change.price);
                    if change.size == 0.0 {
                        self.bids.remove(&key);
                    } else {
                        self.bids.insert(key, PriceLevel {
                            price: change.price,
                            size: change.size,
                            order_count: change.order_count,
                        });
                    }
                }

                // Apply ask changes
                for change in delta.ask_changes {
                    let key = OrderedFloat(change.price);
                    if change.size == 0.0 {
                        self.asks.remove(&key);
                    } else {
                        self.asks.insert(key, PriceLevel {
                            price: change.price,
                            size: change.size,
                            order_count: change.order_count,
                        });
                    }
                }

                self.sequence = delta.sequence;
            }
        }

        Ok(())
    }

    /// Get current snapshot
    pub fn snapshot(&self, depth: usize) -> OrderBook {
        OrderBook {
            symbol: self.symbol.clone(),
            bids: self.bids.values().take(depth).cloned().collect(),
            asks: self.asks.values().take(depth).cloned().collect(),
            timestamp: Utc::now(),
            sequence: self.sequence,
        }
    }
}

Zero-Copy Parsing

Zero-Copy Deserialization

use lux_dex_sdk::zerocopy::{OrderBookRef, PriceLevelRef};
use bytes::Bytes;

/// Zero-copy order book reference (borrows from buffer)
pub struct OrderBookRef<'a> {
    pub symbol: &'a str,
    pub bids: &'a [PriceLevelRef],
    pub asks: &'a [PriceLevelRef],
    pub timestamp: i64,
    pub sequence: u64,
}

/// Zero-copy price level
#[repr(C)]
pub struct PriceLevelRef {
    pub price: f64,
    pub size: f64,
    pub order_count: u32,
    _padding: u32,
}

impl<'a> OrderBookRef<'a> {
    /// Parse from raw bytes without allocation
    pub fn from_bytes(data: &'a [u8]) -> Result<Self> {
        // Binary protocol parsing
        // ...
    }

    /// Convert to owned type (allocates)
    pub fn to_owned(&self) -> OrderBook {
        OrderBook {
            symbol: self.symbol.to_string(),
            bids: self.bids.iter().map(|l| l.to_owned()).collect(),
            asks: self.asks.iter().map(|l| l.to_owned()).collect(),
            timestamp: DateTime::from_timestamp(self.timestamp, 0).unwrap(),
            sequence: self.sequence,
        }
    }
}

Using Zero-Copy in Streams

use lux_dex_sdk::zerocopy::OrderBookStream;

async fn zero_copy_stream(client: &Client) -> Result<()> {
    // Stream with zero-copy parsing
    let mut stream = client.subscribe_order_book_zerocopy("BTC-USD").await?;

    while let Some(result) = stream.next().await {
        // `book` borrows from internal buffer
        let book: OrderBookRef = result?;

        // Fast access without allocation
        if let (Some(bid), Some(ask)) = (book.bids.first(), book.asks.first()) {
            let spread = ask.price - bid.price;
            if spread < 0.01 {
                // Only allocate when needed
                process_tight_spread(book.to_owned()).await?;
            }
        }
    }

    Ok(())
}

Memory-Efficient Structures

Arena-Allocated Order Book

use bumpalo::Bump;

/// Order book using arena allocation
pub struct ArenaOrderBook<'a> {
    arena: &'a Bump,
    symbol: &'a str,
    bids: &'a mut [PriceLevel],
    asks: &'a mut [PriceLevel],
}

impl<'a> ArenaOrderBook<'a> {
    pub fn new(arena: &'a Bump, symbol: &str, depth: usize) -> Self {
        let symbol = arena.alloc_str(symbol);
        let bids = arena.alloc_slice_fill_default(depth);
        let asks = arena.alloc_slice_fill_default(depth);

        Self { arena, symbol, bids, asks }
    }

    /// Update from network data (no allocation)
    pub fn update(&mut self, data: &[u8]) -> Result<()> {
        // Parse directly into pre-allocated slices
        // ...
        Ok(())
    }
}

// Usage with arena reset
fn process_updates() {
    let arena = Bump::with_capacity(64 * 1024); // 64KB

    loop {
        let book = ArenaOrderBook::new(&arena, "BTC-USD", 100);
        // Process updates...

        // Reset arena for next iteration (no deallocation overhead)
        arena.reset();
    }
}

Compact Order Book

/// Memory-compact order book using fixed-point prices
pub struct CompactOrderBook {
    symbol: ArrayString<16>,  // Inline string
    bids: ArrayVec<CompactLevel, 100>,
    asks: ArrayVec<CompactLevel, 100>,
    timestamp: i64,
    sequence: u64,
}

/// Compact price level (16 bytes vs 24 bytes)
#[repr(C)]
pub struct CompactLevel {
    price_cents: i64,     // Price in cents (fixed-point)
    size_satoshis: i64,   // Size in satoshis (fixed-point)
}

impl CompactLevel {
    pub fn price(&self) -> f64 {
        self.price_cents as f64 / 100.0
    }

    pub fn size(&self) -> f64 {
        self.size_satoshis as f64 / 100_000_000.0
    }
}

Aggregated Order Book

By Price Bucket

use std::collections::HashMap;

/// Aggregate order book by price buckets
pub fn aggregate_by_bucket(book: &OrderBook, bucket_size: f64) -> AggregatedBook {
    let mut bid_buckets: HashMap<i64, f64> = HashMap::new();
    let mut ask_buckets: HashMap<i64, f64> = HashMap::new();

    for level in &book.bids {
        let bucket = (level.price / bucket_size).floor() as i64;
        *bid_buckets.entry(bucket).or_default() += level.size;
    }

    for level in &book.asks {
        let bucket = (level.price / bucket_size).ceil() as i64;
        *ask_buckets.entry(bucket).or_default() += level.size;
    }

    AggregatedBook {
        bucket_size,
        bids: bid_buckets.into_iter()
            .map(|(k, v)| (k as f64 * bucket_size, v))
            .collect(),
        asks: ask_buckets.into_iter()
            .map(|(k, v)| (k as f64 * bucket_size, v))
            .collect(),
    }
}

Order Book Analytics

Depth Analysis

impl OrderBook {
    /// Calculate total liquidity at price levels
    pub fn liquidity_at_depth(&self, depth_percent: f64) -> (f64, f64) {
        let mid = self.mid_price().unwrap_or(0.0);
        let threshold = mid * depth_percent / 100.0;

        let bid_liquidity: f64 = self.bids.iter()
            .take_while(|l| mid - l.price <= threshold)
            .map(|l| l.size * l.price)
            .sum();

        let ask_liquidity: f64 = self.asks.iter()
            .take_while(|l| l.price - mid <= threshold)
            .map(|l| l.size * l.price)
            .sum();

        (bid_liquidity, ask_liquidity)
    }

    /// Estimate slippage for market order
    pub fn estimate_slippage(&self, side: Side, size: f64) -> Option<f64> {
        let levels = match side {
            Side::Buy => &self.asks,
            Side::Sell => &self.bids,
        };

        let mut remaining = size;
        let mut total_cost = 0.0;

        for level in levels {
            let fill = remaining.min(level.size);
            total_cost += fill * level.price;
            remaining -= fill;

            if remaining <= 0.0 {
                break;
            }
        }

        if remaining > 0.0 {
            return None; // Insufficient liquidity
        }

        let avg_price = total_cost / size;
        let best_price = levels.first()?.price;

        Some((avg_price - best_price).abs() / best_price * 100.0)
    }

    /// Order book imbalance (-1 to 1)
    pub fn imbalance(&self, depth: usize) -> f64 {
        let bid_vol: f64 = self.bids.iter().take(depth).map(|l| l.size).sum();
        let ask_vol: f64 = self.asks.iter().take(depth).map(|l| l.size).sum();
        let total = bid_vol + ask_vol;

        if total == 0.0 {
            0.0
        } else {
            (bid_vol - ask_vol) / total
        }
    }
}

Benchmark: Order Book Operations

use criterion::{criterion_group, criterion_main, Criterion};

fn orderbook_benchmarks(c: &mut Criterion) {
    let book = create_test_orderbook(1000);

    c.bench_function("best_bid", |b| {
        b.iter(|| book.best_bid())
    });

    c.bench_function("spread", |b| {
        b.iter(|| book.spread())
    });

    c.bench_function("imbalance_10", |b| {
        b.iter(|| book.imbalance(10))
    });

    c.bench_function("slippage_1btc", |b| {
        b.iter(|| book.estimate_slippage(Side::Buy, 1.0))
    });

    c.bench_function("zerocopy_parse", |b| {
        let data = serialize_orderbook(&book);
        b.iter(|| OrderBookRef::from_bytes(&data))
    });
}

criterion_group!(benches, orderbook_benchmarks);
criterion_main!(benches);

Expected results:

best_bid                time:   [1.2 ns 1.3 ns 1.4 ns]
spread                  time:   [2.8 ns 2.9 ns 3.0 ns]
imbalance_10            time:   [45 ns 46 ns 47 ns]
slippage_1btc           time:   [234 ns 241 ns 248 ns]
zerocopy_parse          time:   [89 ns 92 ns 95 ns]

Complete Example

use lux_dex_sdk::{Client, LocalOrderBook, OrderBookUpdate, Result};
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::builder()
        .websocket_url("ws://localhost:8081")
        .build()
        .await?;

    let mut local_book = LocalOrderBook::new("BTC-USD");
    let mut stream = client.subscribe_order_book("BTC-USD", 100).await?;

    while let Some(update) = stream.next().await {
        let update = update?;

        // Update local state
        if let Err(e) = local_book.apply(update) {
            eprintln!("Sequence error: {}, resubscribing...", e);
            stream = client.subscribe_order_book("BTC-USD", 100).await?;
            continue;
        }

        // Get current snapshot
        let book = local_book.snapshot(10);

        // Analytics
        println!(
            "Mid: {:.2} | Spread: {:.2} | Imbalance: {:.2}",
            book.mid_price().unwrap_or(0.0),
            book.spread().unwrap_or(0.0),
            book.imbalance(10)
        );

        // Check for trading opportunity
        if book.imbalance(5) > 0.3 {
            println!("Strong bid pressure detected");
        }
    }

    Ok(())
}