nautechsystems/nautilus_trader · error
Empty order book: no liquidity available for market order
Error message
Empty order book: no liquidity available for market order
What it means
calculate_market_price walks an order book to price a market order; it rejects the request up front when book_levels is empty, because there is no liquidity at all from which to derive a fill price. This is a fail-fast guard before any level parsing or sorting.
Source
Thrown at crates/adapters/polymarket/src/execution/parse.rs:749
/// Sorts levels deterministically before walking:
/// - BUY (asks): ascending by price, best (lowest) ask first
/// - SELL (bids): descending by price, best (highest) bid first
///
/// This ensures correct results regardless of the CLOB API's response ordering.
///
/// For BUY: walks asks best-first, accumulates `size * price` (pUSD) until >= amount.
/// Also accumulates the exact shares at each level for precise base qty.
/// For SELL: walks bids best-first, accumulates `size` (shares) until >= amount.
///
/// Returns the crossing price and expected base quantity. If insufficient liquidity,
/// uses all available levels. If the book side is empty, returns an error.
pub fn calculate_market_price(
book_levels: &[ClobBookLevel],
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"
);
View on GitHub (pinned to 18893faf8b)
Solutions
- Check the book is non-empty before calling, and surface 'no liquidity' to the user instead of attempting the order.
- Re-fetch the book — an empty snapshot may be transient; add a short retry with backoff.
- Verify the correct asset_id/token_id is being used to fetch the book for the intended outcome.
- Halt trading on that market if empty books persist; it may be delisted or paused.
Example fix
// before
let mp = calculate_market_price(&book.levels, amount, side)?;
// after
if book.levels.is_empty() {
eprintln!("no liquidity for {symbol:?}; skipping market order");
return Ok(None);
}
let mp = calculate_market_price(&book.levels, amount, side)?; Defensive patterns
Strategy: try-catch
Validate before calling
if book_levels.is_empty() {
return Ok(None); // no liquidity — skip instead of erroring
} Try / catch
match calculate_market_price(&book.levels, amount, side) {
Ok(mp) => mp,
Err(e) if e.to_string().contains("Empty order book") => {
// no liquidity: back off and retry later
schedule_refetch();
return Ok(None);
}
Err(e) => return Err(e),
} Prevention
- Verify the book is non-empty right after fetching it
- Retry empty snapshots with short backoff — they can be transient
- Confirm the asset_id/token_id maps to an active market
- Alert on persistently empty books; the market may be paused or delisted
When it happens
Trigger: Calling calculate_market_price with an empty slice of ClobBookLevel — e.g. the CLOB API returned an empty book payload, the market just opened/closed, or a fetch bug returned no levels — for either Buy or Sell market orders.
Common situations: Polling a newly listed or nearly-closed market whose book has been cleared; an upstream API outage returning empty book snapshots; a symbol/token-id mix-up querying a book that has no levels.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- Empty order book: no valid price levels for market order
- market-book price must be in (0, 1)
- Unsupported `TimeInForce` for Polymarket market order: {valu
- Polymarket only supports L2_MBP order book deltas, received
- Polymarket does not support OrderBookDepth10 subscriptions;
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/63f9e3711fb56f15.
Report an issue: GitHub.