nautechsystems/nautilus_trader · error

Empty book snapshot for {instrument_id}

Error message

Empty book snapshot for {instrument_id}

What it means

parse_book_snapshot converts a Polymarket book snapshot into order book deltas; a snapshot with zero bids and zero asks carries no state the engine can apply, so it is rejected rather than producing an empty book. Empty snapshots usually indicate a market with no resting orders or an upstream glitch.

Source

Thrown at crates/adapters/polymarket/src/websocket/parse.rs:161

    neg_risk: bool,
    last_trade_price: &'a str,
}

/// Parses a book snapshot into [`OrderBookDeltas`] (CLEAR + ADD).
pub fn parse_book_snapshot(
    snap: &PolymarketBookSnapshot,
    instrument_id: InstrumentId,
    price_precision: u8,
    size_precision: u8,
    ts_init: UnixNanos,
) -> anyhow::Result<OrderBookDeltas> {
    let ts_event = parse_timestamp_ms(&snap.timestamp)?;

    let bids_len = snap.bids.len();
    let asks_len = snap.asks.len();

    if bids_len == 0 && asks_len == 0 {
        anyhow::bail!("Empty book snapshot for {instrument_id}");
    }

    let total = bids_len + asks_len;
    let mut deltas = Vec::with_capacity(total + 1);

    // Every snapshot delta (including the opening CLEAR) carries F_SNAPSHOT so
    // downstream consumers can recognize the rebuild; F_LAST closes the batch
    // on the final delta. `OrderBookDelta::clear` already sets F_SNAPSHOT.
    let snapshot_flag = RecordFlag::F_SNAPSHOT as u8;
    deltas.push(OrderBookDelta::clear(instrument_id, 0, ts_event, ts_init));

    let mut count = 0;

    for level in &snap.bids {
        count += 1;
        let price = parse_price(&level.price, price_precision)?;
        let size = parse_quantity(&level.size, size_precision)?;
        let order = BookOrder::new(OrderSide::Buy, price, size, 0);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Treat the empty snapshot as a book clear/reset at the engine level instead of calling parse_book_snapshot, or skip and wait for a populated snapshot
  2. Log asset_id/timestamp and check the market's status on the venue before subscribing
  3. Retry the subscription — the venue typically follows with a populated snapshot
  4. If empty books are legitimate for your market, relax the guard to emit an opening CLEAR delta only

Example fix

// before
if bids_len == 0 && asks_len == 0 {
    anyhow::bail!("Empty book snapshot for {instrument_id}");
}
// after
if bids_len == 0 && asks_len == 0 {
    tracing::debug!("empty book snapshot for {instrument_id}; emitting clear only");
    return Ok(vec![OrderBookDelta::clear(instrument_id, book_sequence, ts_event, ts_init, F_SNAPSHOT)]);
}
Defensive patterns

Strategy: validation

Validate before calling

if snap.bids.is_empty() && snap.asks.is_empty() {
    // skip or treat as clear; do not call parse_book_snapshot
    return Ok(());
}
let deltas = parse_book_snapshot(instrument_id, &snap, ts_init)?;

Try / catch

match parse_book_snapshot(instrument_id, &snap, ts_init) {
    Ok(deltas) => apply(deltas),
    Err(e) if e.to_string().starts_with("Empty book snapshot") => log::debug!("empty snapshot for {instrument_id}; ignoring"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Receiving a snapshot message where both bids and asks arrays are empty and passing it to parse_book_snapshot(instrument_id, snap, ...).

Common situations: Very new or illiquid markets with no resting orders; venue emitting an initial empty snapshot before seeding levels; a market that was paused/delisted mid-session.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/55c9bdaf4a8cf763. Report an issue: GitHub.