nautechsystems/nautilus_trader · warning

empty Lighter WebSocket order book update

Error message

empty Lighter WebSocket order book update

What it means

parse_ws_order_book_deltas rejects order book WebSocket updates that contain no bid or ask levels (unless the update is a snapshot). The adapter treats an empty incremental update as a protocol/data anomaly rather than emitting an empty deltas batch.

Source

Thrown at crates/adapters/lighter/src/websocket/parse.rs:118

/// `is_snapshot` must be supplied by the caller because Lighter sends
/// `subscribed/order_book` for the full book on subscription and
/// `update/order_book` for incremental level changes afterwards.
///
/// # Errors
///
/// Returns an error if any price or size cannot be converted.
pub fn parse_ws_order_book_deltas(
    book: &LighterWsOrderBook,
    instrument: &InstrumentAny,
    timestamp_ms: u64,
    is_snapshot: bool,
    ts_init: UnixNanos,
) -> anyhow::Result<OrderBookDeltas> {
    let ts_event = parse_millis_to_nanos(timestamp_ms)?;
    let sequence = u64::try_from(book.nonce).context("negative Lighter book nonce")?;
    let total_levels = book.bids.len() + book.asks.len();

    anyhow::ensure!(
        is_snapshot || total_levels > 0,
        "empty Lighter WebSocket order book update",
    );

    let mut deltas = Vec::with_capacity(total_levels + usize::from(is_snapshot));

    if is_snapshot {
        let mut clear = OrderBookDelta::clear(instrument.id(), sequence, ts_event, ts_init);
        if total_levels == 0 {
            clear.flags |= RecordFlag::F_LAST as u8;
        }
        deltas.push(clear);
    }

    let mut processed = 0_usize;

    for bid in &book.bids {
        processed += 1;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Skip/ignore empty updates upstream if they are benign keep-alives
  2. Log the raw message and check whether the venue changed the update schema
  3. Handle the anyhow error by filtering it out in the message handler instead of failing the stream
  4. Verify subscription is still valid and the market is active
Defensive patterns

Strategy: try-catch

Validate before calling

if !is_snapshot && book.bids.is_empty() && book.asks.is_empty() {
    return Ok(()); // skip empty update
}

Type guard

fn has_levels(book: &WsBook) -> bool { !book.bids.is_empty() || !book.asks.is_empty() }

Try / catch

match parse_ws_order_book_deltas(msg) {
    Err(e) if e.to_string().contains("empty Lighter WebSocket order book update") => {
        log::debug!("skipping empty book update");
    }
    other => other?,
}

Prevention

When it happens

Trigger: A book update message arrives with empty bids and asks arrays and is_snapshot is false, e.g. venue sends a keep-alive-like update or an unexpected payload shape.

Common situations: Venue protocol change or edge-case message (book emptied via separate delete events); network issues producing truncated payloads; subscribing to a book that has been delisted.

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/6b524c77cfba7bee. Report an issue: GitHub.