nautechsystems/nautilus_trader · error

Empty order book: no valid price levels for market order

Error message

Empty order book: no valid price levels for market order

What it means

After parsing book levels, calculate_market_price drops levels with zero price or zero size; if nothing valid remains it bails out, since a book whose every level is degenerate offers no tradable price. This differs from the fully-empty-book case: levels were present but none were usable.

Source

Thrown at crates/adapters/polymarket/src/execution/parse.rs:774

    for level in book_levels {
        let price = parse_decimal_exact(&level.price).context("invalid market-book price")?;
        let size = parse_decimal_exact(&level.size).context("invalid market-book size")?;
        anyhow::ensure!(
            price > Decimal::ZERO && price < Decimal::ONE,
            InvalidMarketPriceError("market-book price must be in (0, 1)".to_string())
        );
        anyhow::ensure!(
            size >= Decimal::ZERO,
            "market-book size must be non-negative"
        );

        if !size.is_zero() {
            parsed_levels.push((price, size));
        }
    }

    if parsed_levels.is_empty() {
        anyhow::bail!("Empty order book: no valid price levels for market order");
    }

    match side {
        PolymarketOrderSide::Buy => parsed_levels.sort_by_key(|a| a.0),
        PolymarketOrderSide::Sell => parsed_levels.sort_by_key(|b| std::cmp::Reverse(b.0)),
    }

    let mut remaining = amount;
    let mut last_price = Decimal::ZERO;
    let mut total_base_qty = Decimal::ZERO;

    for &(price, size) in &parsed_levels {
        last_price = price;

        match side {
            PolymarketOrderSide::Buy => {
                let level_usdc = size
                    .checked_mul(price)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter or sanitize book levels before calling, and treat an all-degenerate book as 'no liquidity'.
  2. Re-fetch the book from the CLOB API; an all-zero snapshot is usually transient or malformed.
  3. Log the raw payload when this fires to confirm whether the adapter or the exchange produced the bad data.
  4. Check adapter/API version compatibility in case the book schema changed and fields now deserialize as zero.

Example fix

// before
let mp = calculate_market_price(&book.levels, amount, side)?;
// after
let valid: Vec<_> = book.levels.iter().filter(|l| !l.size.is_zero() && !l.price.is_zero()).collect();
anyhow::ensure!(!valid.is_empty(), "book has no tradable levels; skipping");
let mp = calculate_market_price(&book.levels, amount, side)?;
Defensive patterns

Strategy: validation

Validate before calling

let usable = book_levels.iter()
    .any(|l| !l.price.is_zero() && !l.size.is_zero());
if !usable {
    return Err("book contains only zero price/size levels".into());
}

Prevention

When it happens

Trigger: Calling calculate_market_price with a non-empty book_levels slice where every level has price == 0 or size == 0 — typically from a malformed or placeholder API snapshot (all-zero payload) rather than a genuinely empty array.

Common situations: Upstream exchange returning zero-filled book rows during incidents; deserialization of a stub/placeholder book; stale cached snapshots zeroed out by a serializer bug.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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