nautechsystems/nautilus_trader · error

market-book size must be non-negative

Error message

market-book size must be non-negative

What it means

calculate_market_price requires every book level size to be non-negative. A negative size is impossible in a real order book and indicates corrupt venue data, so the level is rejected with this ensure! guard instead of corrupting the liquidity walk.

Source

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

    amount: Decimal,
    side: PolymarketOrderSide,
) -> anyhow::Result<MarketPriceResult> {
    if book_levels.is_empty() {
        anyhow::bail!("Empty order book: no liquidity available for market order");
    }

    // Parse and sort levels deterministically so we never depend on API ordering.
    // BUY: asks ascending (best/lowest first). SELL: bids descending (best/highest first).
    anyhow::ensure!(amount > Decimal::ZERO, "market amount must be positive");
    let mut parsed_levels = Vec::with_capacity(book_levels.len());
    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)),
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Dump the raw book response and find the level with the negative size to locate the corruption source.
  2. Fix the book maintenance code so level updates/deletions can never leave negative sizes.
  3. Skip or drop non-negative-violating levels and rebuild the snapshot from a fresh REST fetch.

Example fix

// before
levels.push(Level { size: delta_size, .. }); // delta can be negative
// after
let new_size = existing.size + delta_size;
anyhow::ensure!(new_size >= Decimal::ZERO);
levels.push(Level { size: new_size, .. });
Defensive patterns

Strategy: validation

Validate before calling

if levels.iter().any(|l| Decimal::from_str(&l.size).map(|s| s < Decimal::ZERO).unwrap_or(true)) {
    return Err(anyhow!("book contains negative sizes"));
}

Type guard

fn has_non_negative_sizes(levels: &[Level]) -> bool {
    levels.iter().all(|l| Decimal::from_str(&l.size).map(|s| s >= Decimal::ZERO).unwrap_or(false))
}

Prevention

When it happens

Trigger: A market-book response passed to calculate_market_price contains a level whose parsed size is negative — e.g. a malformed API payload, a diff/update applied in the wrong order, or a sign-convention bug in a local book builder.

Common situations: Applying book deltas without handling deletions, leaving negative residuals; buggy caching of book state; venue API incidents returning invalid payloads.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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