nautechsystems/nautilus_trader · error · anyhow::Error

missing price component in {label} level

Error message

missing price component in {label} level

What it means

parse_book_level expects each orderbook level to be a 2-element array [price, size]. This error means the level array was empty (or missing its first element), so there is no price string to parse into a Price. Bybit adapters throw it while converting a raw WebSocket orderbook delta/snapshot level into a Nautilus (Price, Quantity) pair.

Source

Thrown at crates/adapters/bybit/src/common/parse.rs:961

    }

    for level in &result.a {
        push_level(level, OrderSide::Sell)?;
    }

    OrderBookDeltas::new_checked(instrument_id, deltas)
        .context("failed to assemble OrderBookDeltas from Bybit message")
}

pub fn parse_book_level(
    level: &[String],
    price_precision: u8,
    size_precision: u8,
    label: &str,
) -> anyhow::Result<(Price, Quantity)> {
    let price_str = level
        .first()
        .ok_or_else(|| anyhow::anyhow!("missing price component in {label} level"))?;
    let size_str = level
        .get(1)
        .ok_or_else(|| anyhow::anyhow!("missing size component in {label} level"))?;
    let price = parse_price_with_precision(price_str, price_precision, label)?;
    let size = parse_quantity_with_precision(size_str, size_precision, label)?;
    Ok((price, size))
}

/// Parses a kline entry into a [`Bar`].
pub fn parse_kline_bar(
    kline: &BybitKline,
    instrument: &InstrumentAny,
    bar_type: BarType,
    timestamp_on_close: bool,
    ts_init: Option<UnixNanos>,
) -> anyhow::Result<Bar> {
    let price_precision = instrument.price_precision();
    let size_precision = instrument.size_precision();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the raw WebSocket frame and fix/upgrade payload deserialization so each level is [price, size].
  2. Guard in the caller: skip or log levels whose length != 2 before calling parse_book_level.
  3. Check the Bybit adapter/nautilus version matches the exchange API version in use.
  4. Log the full message id/context when this occurs and report to Bybit support if the exchange sends empty levels.

Example fix

// before
for level in data.a { let (p, s) = parse_book_level(level, ...)?; ... }
// after
for level in data.a {
    if level.len() < 2 { log::warn!("skipping malformed level: {level:?}"); continue; }
    let (p, s) = parse_book_level(level, ...)?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Rust: before consuming a parsed level
fn level_is_valid(level: &[serde_json::Value]) -> bool { level.len() >= 2 && level[0].is_string() }

Type guard

fn as_book_level(level: &serde_json::Value) -> Option<(&str, &str)> {
    let arr = level.as_array()?;
    Some((arr.get(0)?.as_str()?, arr.get(1)?.as_str()?))
}

Try / catch

match parse_book_level(level, pp, sp, "bid") {
    Ok((price, size)) => apply(price, size),
    Err(e) => log::warn!("skipping malformed level: {e:#}"),
}

Prevention

When it happens

Trigger: parse_orderbook receives a level entry that is an empty array or an array whose first element is absent (level.first() returns None).

Common situations: Malformed or truncated Bybit WebSocket payloads; exchange sending an empty level during snapshot->delta transitions; buggy custom middleware stripping fields; using an undocumented/changed message schema after a Bybit API version update.

Related errors


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