nautechsystems/nautilus_trader · error · anyhow::Error
No liquidity available for market order on {instrument_id}
Error message
No liquidity available for market order on {instrument_id} What it means
The preview call for an emulated MARKET order succeeded but returned limit_price = None. AX returns a take-through price only when the book has resting liquidity on the side you cross; a None price means the preview found no actionable depth (empty or one-sided book), so the adapter cannot price the IOC limit and refuses to submit rather than send an unpriced order. The remaining_quantity warning above it may also signal a too-thin book for your size.
Source
Thrown at crates/adapters/architect_ax/src/execution.rs:255
let response = http_client
.inner
.preview_aggressive_limit_order(&request)
.await
.map_err(|e| {
anyhow::anyhow!("Failed to preview aggressive limit order: {e}")
})?;
if response.remaining_quantity > 0 {
log::warn!(
"Market order book depth insufficient: \
filled_qty={} remaining_qty={} for {instrument_id}",
response.filled_quantity,
response.remaining_quantity,
);
}
let limit_price_decimal = response.limit_price.ok_or_else(|| {
anyhow::anyhow!(
"No liquidity available for market order on {instrument_id}"
)
})?;
let price =
Price::from_decimal_dp(limit_price_decimal, instrument.price_precision())
.with_context(|| {
format!(
"Failed to convert AX take-through price {limit_price_decimal} for {instrument_id}"
)
})?;
log::debug!("Market order take-through price: {price} for {instrument_id}",);
Ok(price)
}
.await;
let price = match preview_result {
Ok(price) => price,View on GitHub (pinned to a4b06ed870)
Solutions
- Subscribe to quotes for the instrument and require a bid/ask on both sides before sending market orders
- Send an aggressive LIMIT order (e.g. mid +/- k*spread) instead of MARKET so pricing does not depend on preview
- Reduce size and/or wait for the book to repopulate, then retry
- Confirm the instrument is in its active trading session on AX
Example fix
// before
if best_bid.is_none() || best_ask.is_none() {
self.submit_order(&factory.market(id, side, qty)); // -> No liquidity
}
// after
if let (Some(bid), Some(ask)) = (best_bid, best_ask) {
let px = if side == OrderSide::Buy { ask + offset } else { bid - offset };
self.submit_order(&factory.limit(id, side, qty, px, TimeInForce::Ioc));
} Defensive patterns
Strategy: validation
Validate before calling
// Require two-sided liquidity before any MARKET order
let book_ok = self.cache.book_order(id, BookType::Default)
.map(|b| b.best_bid_price().is_some() && b.best_ask_price().is_some())
.unwrap_or(false);
if !book_ok {
self.warning("no two-sided book; skipping MARKET order");
return;
} Prevention
- Subscribe to quotes for every instrument you may market-order
- Cap market-order size to a fraction of visible depth on the crossed side
- Prefer IOC limits with an explicit price so empty books simply rest or cancel instead of erroring
When it happens
Trigger: Market order on an illiquid symbol whose book is empty on the crossed side (new listing, overnight halt, stale venue book); trading outside the instrument's active hours; requesting more size than the entire book holds.
Common situations: Backtest-only symbols that are actually dead in production; testnet instruments with fake sparse books; market orders sent right at auction open/close where the continuous book is empty.
Related errors
- Invalid order side: {e}
- Instrument {instrument_id} not found in cache
- Failed to preview aggressive limit order: {e}
- Authentication failed: {e}
- Timeout waiting for account {account_id} to be registered af
AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16).
Data as JSON: /api/errors/35f8453886b5520a.
Report an issue: GitHub.