---
title: "Order Book Reconstruction | 0xArchive Docs"
description: "Reconstruct order books from 0xArchive snapshots and deltas across Hyperliquid, HIP-3, HIP-4, and Lighter depth feeds in client code."
canonical_url: "https://www.0xarchive.io/docs/sdks/reconstruction/"
markdown_url: "https://www.0xarchive.io/docs/sdks/reconstruction.md"
route: "/docs/sdks/reconstruction"
robots: "index, follow"
generated_from: "prerendered_html"
---

# Order Book Reconstruction

Server-side and client-side reconstruction for Hyperliquid, HIP-3, HIP-4, and Lighter depth feeds.

Use Reconstruction and replay Venues Hyperliquid, HIP-3, HIP-4, Lighter Depth L4/L2 and L3

[Installation](https://www.0xarchive.io/docs/sdks/installation/)

[Reconstruction](https://www.0xarchive.io/docs/sdks/reconstruction/)

Official clients for the core API in Python, TypeScript, and Rust.

## Order book reconstruction

Reconstruct Hyperliquid, HIP-3, and HIP-4 L4/L2 books server-side or client-side. Lighter uses checkpoints and deltas.

### Server-side reconstruction (Hyperliquid / HIP-3 / HIP-4)

Tier Pro+ (L4) / Build+ (L2) For Point-in-time L4 or L2 snapshots Recommended One API call, server handles matching engine

The server reconstructs the full orderbook from checkpoints and diffs, including the matching engine that removes crossed orders. Pass a timestamp for historical state, or omit for live. Works identically for Hyperliquid, HIP-3, and HIP-4 (HIP-4 coins use the numeric id 0, 1, 10, 11, ...).

```
# Server-side L4 reconstruction (Pro+ tier)
# The server handles the matching engine — just pass a timestamp.
from oxarchive import Client

client = Client(api_key="0xa_your_api_key")

# Hyperliquid: get L4 orderbook at a specific point in time
l4 = client.hyperliquid.l4_orderbook.get("BTC", timestamp=1711900800000)
print(f"BTC L4: {l4['bid_count']} bids, {l4['ask_count']} asks")
print(f"Best bid: {l4['bids'][0]['price']}, Best ask: {l4['asks'][0]['price']}")

# HIP-3: same method, same response format
hip3_l4 = client.hyperliquid.hip3.l4_orderbook.get("xyz:CL", timestamp=1711900800000)
print(f"Crude Oil L4: {hip3_l4['bid_count']} bids, {hip3_l4['ask_count']} asks")

# Omit timestamp for current live state (from Redis, sub-second)
live = client.hyperliquid.l4_orderbook.get("BTC")

# L2 full-depth (aggregated from L4, Build+ tier)
l2 = client.hyperliquid.l2_orderbook.get("BTC", timestamp=1711900800000)
print(f"BTC L2: {l2['bid_levels']} bid levels, {l2['ask_levels']} ask levels")
print(f"Best bid: {l2['bids'][0]['px']} ({l2['bids'][0]['n']} orders)")
```

| Endpoint | Tier | Returns |
| --- | --- | --- |
| `/orderbook/:symbol/l4` | Pro+ | L4 order-level book: individual orders with OID, wallet, price, size. |
| `/orderbook/:symbol/l2` | Build+ | L2 full-depth book: aggregated price levels with total size and order count. |

### Client-side L4/L2 reconstruction (Hyperliquid / HIP-3 / HIP-4)

Tier Pro+ For Time-series iteration, backtesting, custom analysis Exchanges Same method for Hyperliquid, HIP-3, and HIP-4

Fetch checkpoints and diffs, then apply them client-side with the matching engine. When a new order crosses the spread, the matching engine filled opposite-side orders at crossing prices — you must remove them to avoid a crossed book. Group diffs by block and filter non-resting orders (filled/canceled in the same block) for accuracy. L2 is derived by aggregating L4 orders by price level.

```
# Client-side L4 reconstruction with matching engine (Pro+ tier)
# Same approach for Hyperliquid and HIP-3 — identical diff format.
from oxarchive import Client, L4OrderBookReconstructor
from collections import defaultdict

client = Client(api_key="0xa_your_api_key")
start, end = 1711900800000, 1711901100000  # 5-minute window

# 1. Fetch nearest L4 checkpoint
checkpoints = client.hyperliquid.l4_orderbook.history("BTC", start=start-1800000, end=start)
checkpoint = checkpoints.data[-1]  # nearest before target

# 2. Fetch L4 diffs from checkpoint to target
diffs = client.hyperliquid.l4_orderbook.diffs("BTC", start=start, end=end)

# 3. Fetch order statuses for non-resting filter (optional, improves accuracy)
statuses = client.hyperliquid.orders.history("BTC", start=start, end=end)
non_resting = defaultdict(set)
for s in statuses.data:
    if s["status"] != "open":
        non_resting[s["block_number"]].add(s["oid"])

# 4. Reconstruct with matching engine
book = L4OrderBookReconstructor()
book.load_checkpoint(checkpoint)

# Group diffs by block, apply in order
blocks = defaultdict(list)
for d in diffs.data:
    blocks[d["block_number"]].append(d)

for bn in sorted(blocks):
    nr = non_resting.get(bn)
    for diff in blocks[bn]:
        book.apply_diff(diff, nr)

# 5. Result: non-crossed L4 orderbook
assert not book.is_crossed(), "Book should not be crossed"
print(f"Best bid: {book.best_bid()}, Best ask: {book.best_ask()}")
print(f"Orders: {len(book.bids())} bids, {len(book.asks())} asks")

# 6. Derive L2 from L4 (aggregate by price level)
l2_bids, l2_asks = book.derive_l2()
print(f"L2 levels: {len(l2_bids)} bid, {len(l2_asks)} ask")
```

| Method | Best use | Notes |
| --- | --- | --- |
| `L4OrderBookReconstructor` | Core reconstruction class | Matching engine built in. Handles crossing orders and non-resting filter. |
| `loadCheckpoint() / applyDiff()` | Step through time | Initialize from checkpoint, then apply diffs block-by-block. |
| `deriveL2()` | L2 from L4 | Aggregate L4 orders by price level (sum sizes, count orders per level). |
| `isCrossed()` | Validation | Should always return false after correct reconstruction. |

### Lighter tick reconstruction

Tier Enterprise For Tick-level Lighter order book history Preferred path Auto-paginating iterator

```
# Orderbook Reconstruction (Enterprise tier only)
from oxarchive import Client, OrderBookReconstructor

client = Client(api_key="0xa_your_api_key")

# Option 1: Auto-paginating iterator (recommended for large time ranges)
# Automatically handles pagination, fetching up to 1,000 deltas per request
for snapshot in client.lighter.orderbook.iterate_tick_history(
    "BTC",
    start="2026-01-01T00:00:00Z",
    end="2026-01-01T12:00:00Z"  # 12 hours of data
):
    print(f"{snapshot.timestamp}: mid={snapshot.mid_price}")
    if some_condition:
        break  # Early exit supported

# Option 2: Get fully reconstructed snapshots (single page)
snapshots = client.lighter.orderbook.history_reconstructed(
    "BTC",
    start="2026-01-01T00:00:00Z",
    end="2026-01-01T01:00:00Z"
)
for ob in snapshots:
    print(f"{ob.timestamp}: bid={ob.bids[0].px} ask={ob.asks[0].px}")

# Option 3: Get raw tick data for custom reconstruction
tick_data = client.lighter.orderbook.history_tick("BTC", start=start, end=end)
print(f"Checkpoint: {len(tick_data.checkpoint.bids)} bids")
print(f"Deltas: {len(tick_data.deltas)} updates")

# Check for sequence gaps
gaps = OrderBookReconstructor.detect_gaps(tick_data.deltas)
if gaps:
    print("Gaps detected:", gaps)
```

| Method | Best use | Notes |
| --- | --- | --- |
| `iterateTickHistory()` | Long windows and production replay | Auto-paginates and handles large delta ranges cleanly. |
| `historyTick()` | Raw checkpoints and deltas | Single page, maximum 1,000 deltas. |
| `historyReconstructed()` | Direct reconstructed snapshots | Single page when you need the finished book immediately. |
| `detectGaps()` | Continuity checks | Use before backtests or downstream reconstruction pipelines. |
