# How to Process Order Book Data and Build L2/L3 Books in Nautilus Trader

> Learn to process order book data and build L2 L3 books in Nautilus Trader using BookType L2_MBP or L3_MBO. Feed OrderBookDelta objects to track market depth in real-time.

- Repository: [Nautech Systems/nautilus_trader](https://github.com/nautechsystems/nautilus_trader)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Create an `OrderBook` instance with `BookType::L2_MBP` for aggregated market-by-price data or `BookType::L3_MBO` for individual market-by-order tracking, then feed it `OrderBookDelta` objects from your exchange adapter to build a real-time view of market depth.**

Processing order book data efficiently is critical for high-frequency trading strategies and market-making systems. In the `nautechsystems/nautilus_trader` repository, the order book implementation provides a unified interface for handling both aggregated L2 (Market-By-Price) and granular L3 (Market-By-Order) data. This guide explains how to process raw feed data and construct order books using the Rust core and Python bindings.

## Understanding Order Book Types in Nautilus Trader

Nautilus Trader models market depth through three logical layers, each optimized for different data granularity requirements.

**L1 (MBP - Market-By-Price)** represents top-of-book only, storing a single best bid and ask price level. This mode clears the ladder on every replacement and is suitable for simple quote tracking.

**L2 (MBP - Market-By-Price)** aggregates orders at each price level. The `BookLadder` stores a `BTreeMap<BookPrice, BookLevel>` where each level holds the **total size** for that price, regardless of how many individual orders comprise that total.

**L3 (MBO - Market-By-Order)** maintains every order individually. While using the same `BookLadder` structure, each `BookLevel` contains an `IndexMap<OrderId, BookOrder>` preserving distinct order IDs and their specific sizes.

## Core Data Structures for Order Book Processing

The foundation of order book processing resides in [`crates/model/src/orderbook/book.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/book.rs), which defines the `OrderBook` struct. When instantiating a book, you specify the desired `BookType` to determine aggregation behavior:

```rust
use nautilus_trader::model::{
    orderbook::OrderBook,
    enums::BookType,
    identifiers::InstrumentId,
};

let instrument_id = InstrumentId::from("BTCUSDT-PERP.BINANCE");

// Create L2 aggregated book
let l2_book = OrderBook::new(instrument_id, BookType::L2_MBP);

// Create L3 individual order book
let l3_book = OrderBook::new(instrument_id, BookType::L3_MBO);

```

The atomic unit of change is `OrderBookDelta`, defined in [`crates/model/src/data/delta.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/data/delta.rs). Each delta carries a single operation—**Add**, **Update**, **Delete**, or **Clear**—along with the `BookOrder` to which it applies.

## Processing Order Book Deltas

Exchange adapters convert raw JSON or binary messages into `OrderBookDelta` objects. The `OrderBook` exposes two primary entry points for applying these changes, both implemented in [`crates/model/src/orderbook/book.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/book.rs):

- `apply_delta(&mut self, delta: &OrderBookDelta)` – Validates the instrument ID and forwards to `apply_delta_unchecked`
- `apply_deltas(&mut self, deltas: &OrderBookDeltas)` – Batch processes a snapshot, typically a `Clear` followed by multiple `Add` operations

Internally, `apply_delta_unchecked` dispatches to `add`, `update`, or `delete` methods based on the delta action. These methods forward to the appropriate `BookLadder` (bids or asks) defined in [`crates/model/src/orderbook/ladder.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/ladder.rs).

The ladder handles side-specific logic:
- **L2 aggregation**: When `book_type != BookType::L1_MBP`, the `add` method verifies positive size, creates a `BookPrice`, and either merges into an existing `BookLevel` (summing the aggregated size) or creates a new level
- **L3 individual orders**: The same code path stores the full `BookOrder` inside the level's `orders` map, preserving every order ID
- **Zero-size handling**: For L1, an add with size=0 clears the ladder; for L2/L3, orders with zero or negative size are ignored

## Building an L2 Order Book Step-by-Step

The following example demonstrates constructing an L2 aggregated book and applying incremental updates:

```rust
use nautilus_trader::model::{
    orderbook::OrderBook,
    data::{OrderBookDelta, BookAction, BookOrder},
    enums::{BookType, OrderSide},
    identifiers::InstrumentId,
    types::{Price, Quantity},
};
use nautilus_core::UnixNanos;

// Initialize the book
let instrument_id = InstrumentId::from("BTCUSDT-PERP.BINANCE");
let mut book = OrderBook::new(instrument_id, BookType::L2_MBP);

// Create first delta - add bid at 20,000 with size 0.5
let delta1 = OrderBookDelta::new(
    instrument_id,
    BookAction::Add,
    BookOrder::new(OrderSide::Buy, Price::from("20000.00"), Quantity::from(0.5), 1),
    0,
    1,
    UnixNanos::now(),
    UnixNanos::now(),
);

book.apply_delta(&delta1).unwrap();

// Verify aggregation - add another order at same price
let delta2 = OrderBookDelta::new(
    instrument_id,
    BookAction::Add,
    BookOrder::new(OrderSide::Buy, Price::from("20000.00"), Quantity::from(0.3), 2),
    0,
    2,
    UnixNanos::now(),
    UnixNanos::now(),
);

book.apply_delta(&delta2).unwrap();

// L2 automatically aggregates: total size at 20,000 is now 0.8
assert_eq!(book.best_bid_size().unwrap(), Quantity::from(0.8));

```

## Building an L3 Order Book Step-by-Step

For strategies requiring individual order tracking, use L3 mode:

```rust
let mut book = OrderBook::new(instrument_id, BookType::L3_MBO);

// Add two distinct sell orders at the same price level
let delta1 = OrderBookDelta::new(
    instrument_id,
    BookAction::Add,
    BookOrder::new(OrderSide::Sell, Price::from("20100.00"), Quantity::from(0.4), 10),
    0,
    1,
    UnixNanos::now(),
    UnixNanos::now(),
);

let delta2 = OrderBookDelta::new(
    instrument_id,
    BookAction::Add,
    BookOrder::new(OrderSide::Sell, Price::from("20100.00"), Quantity::from(0.2), 11),
    0,
    2,
    UnixNanos::now(),
    UnixNanos::now(),
);

book.apply_delta(&delta1).unwrap();
book.apply_delta(&delta2).unwrap();

// In L3, orders remain separate within the price level
let level = book.asks(None).next().unwrap();
assert_eq!(level.orders.len(), 2);  // Two separate orders tracked

```

## Querying and Analyzing Order Book Data

The `OrderBook` API in [`crates/model/src/orderbook/book.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/book.rs) provides utilities for strategy development:

- **`bids_as_map(depth)`** – Returns a `price → size` map for the bid side. For L2, this shows aggregated sizes; for L3, sizes are summed per order.
- **`group_bids(group_size, depth)`** – Buckets quantities into price-size buckets useful for heat-map visualizations.
- **`get_avg_px_for_quantity(qty, side)`** – Calculates the average fill price for a given quantity, essential for slippage estimation.
- **`pprint(num_levels, group_size)`** – Pretty-prints the book for debugging, used extensively in the `orderbook_imbalance` examples.

## Integration with Exchange Adapters

Exchange adapters bridge raw feed data to the `OrderBook` structure. For example, the dYdX adapter in [`nautilus_trader/adapters/dydx/endpoints/market/orderbook.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/adapters/dydx/endpoints/market/orderbook.py) fetches HTTP snapshots and decodes them into `DYDXWsOrderbookMessageSnapshotContents`, which the adapter converts into `OrderBookDelta` objects.

The adapter pattern ensures that regardless of the exchange's native format, the core `OrderBook` receives standardized `OrderBookDelta` objects containing the instrument ID, action type (Add/Update/Delete/Clear), and the `BookOrder` details.

## Summary

- **Initialize** the `OrderBook` with `BookType::L2_MBP` for aggregated market-by-price data or `BookType::L3_MBO` for individual market-by-order tracking.
- **Apply** `OrderBookDelta` objects via `apply_delta()` or batch updates via `apply_deltas()` to maintain real-time book state.
- **Leverage** the `BookLadder` logic in [`crates/model/src/orderbook/ladder.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/ladder.rs) for automatic aggregation (L2) or individual order preservation (L3).
- **Query** the book using utility methods like `best_bid_price()`, `bids_as_map()`, and `get_avg_px_for_quantity()` to drive trading decisions.

## Frequently Asked Questions

### What is the difference between L2 and L3 order books in Nautilus Trader?

**L2 (Market-By-Price)** aggregates all orders at the same price level into a single total size, making it suitable for standard market depth analysis and most trading strategies. **L3 (Market-By-Order)** maintains each order individually with its unique ID, which is essential for order-specific tracking, queue position analysis, or when you need to distinguish between your own orders and public liquidity.

### How do I handle order book snapshots from exchanges?

Use the `apply_deltas()` method to process snapshots efficiently. Exchange adapters typically send a snapshot as an `OrderBookDeltas` object containing a `Clear` action followed by multiple `Add` actions for each price level. This batch approach in [`crates/model/src/orderbook/book.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/book.rs) ensures atomic book updates without partial state.

### Can I switch between L2 and L3 modes for the same instrument?

No, the `BookType` is set at initialization and cannot be changed dynamically. The `OrderBook` struct in [`crates/model/src/orderbook/book.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/book.rs) uses the book type to determine aggregation logic in the underlying `BookLadder`. If you need both views simultaneously, maintain two separate `OrderBook` instances fed by the same delta stream.

### What performance optimizations exist for high-frequency updates?

The `BookLadder` in [`crates/model/src/orderbook/ladder.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/model/src/orderbook/ladder.rs) uses a `BTreeMap` for price levels ensuring O(log n) insertion and deletion, while L3 mode uses an `IndexMap` for O(1) order lookup by ID. The `apply_delta_unchecked` method bypasses instrument validation for hot paths, and batch processing via `apply_deltas` reduces function call overhead during initial snapshots.