nautechsystems/nautilus_trader · error

Bybit order book update missing bid levels and no previous q

Error message

Bybit order book update missing bid levels and no previous quote provided

What it means

When building a `QuoteTick` from a Bybit order book message, no best bid could be extracted from `data.b` and no `last_quote` was supplied to fall back on. The adapter cannot produce a two-sided top-of-book quote, so it errors instead of emitting a one-sided quote.

Source

Thrown at crates/adapters/bybit/src/websocket/parse.rs:347

    let size_precision = instrument.size_precision();

    let get_best =
        |levels: &[Vec<String>], label: &str| -> anyhow::Result<Option<(Price, Quantity)>> {
            if let Some(values) = levels.first() {
                parse_book_level(values, price_precision, size_precision, label).map(Some)
            } else {
                Ok(None)
            }
        };

    let bids = get_best(&msg.data.b, "bid")?;
    let asks = get_best(&msg.data.a, "ask")?;

    let (bid_price, bid_size) = match (bids, last_quote) {
        (Some(level), _) => level,
        (None, Some(prev)) => (prev.bid_price, prev.bid_size),
        (None, None) => {
            anyhow::bail!(
                "Bybit order book update missing bid levels and no previous quote provided"
            );
        }
    };

    let (ask_price, ask_size) = match (asks, last_quote) {
        (Some(level), _) => level,
        (None, Some(prev)) => (prev.ask_price, prev.ask_size),
        (None, None) => {
            anyhow::bail!(
                "Bybit order book update missing ask levels and no previous quote provided"
            );
        }
    };

    QuoteTick::new_checked(
        instrument.id(),
        bid_price,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Always cache the last parsed quote per symbol and pass it as `last_quote` so deltas can fall back to the previous bid.
  2. Wait for the snapshot message (`orderbook.1.<symbol>` with full levels) before processing delta messages.
  3. Skip delta frames received before the first snapshot instead of parsing them.

Example fix

// before: feed deltas with no prior state
let quote = parse_orderbook_quote(&msg, None)?;
// after: maintain per-symbol last quote
let quote = parse_orderbook_quote(&msg, last_quotes.get(&symbol))?;
last_quotes.insert(symbol, quote.clone());
Defensive patterns

Strategy: fallback

Validate before calling

let has_bid = msg.data.b.iter().any(|l| !l.is_empty());
let has_prev = last_quote.is_some();
if !has_bid && !has_prev {
    return; // wait for snapshot
}

Try / catch

match parse_orderbook_quote(&msg, last_quote) {
    Ok(quote) => publish(quote),
    Err(e) if e.to_string().contains("missing bid levels") => {
        tracing::debug!("delta before snapshot; waiting for snapshot");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `parse_orderbook_quote` receives a delta update whose `b` list is empty (all bid levels deleted by the update) while `last_quote` is `None` — i.e. the first frame for a symbol is a delta with no snapshot and no cached prior quote.

Common situations: Starting a subscription and receiving a delta before the initial snapshot; reconnecting to the Bybit order book stream without re-seeding the cached last quote; a malformed message where the bid array is empty.

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/790607daf5e1741f. Report an issue: GitHub.