nautechsystems/nautilus_trader · critical
Error: Unable to calculate `midpoint` (no bid or ask)
Error message
Error: Unable to calculate `midpoint` (no bid or ask)
What it means
This panic fires in the FFI wrapper `orderbook_midpoint` when `OrderBook::midpoint()` returns None because the book does not contain at least one bid and one ask, so the midpoint is undefined. The `abort_on_panic` wrapper turns the panic into a process abort rather than a recoverable FFI error.
Source
Thrown at crates/model/src/ffi/orderbook/book.rs:319
/// # Panics
///
/// Panics if unable to calculate spread (requires at least one bid and one ask).
#[unsafe(no_mangle)]
pub extern "C" fn orderbook_spread(book: &mut OrderBook) -> f64 {
abort_on_panic(|| {
book.spread()
.expect("Error: Unable to calculate `spread` (no bid or ask)")
})
}
/// # Panics
///
/// Panics if unable to calculate midpoint (requires at least one bid and one ask).
#[unsafe(no_mangle)]
pub extern "C" fn orderbook_midpoint(book: &mut OrderBook) -> f64 {
abort_on_panic(|| {
book.midpoint()
.expect("Error: Unable to calculate `midpoint` (no bid or ask)")
})
}
/// # Panics
///
/// Panics if `order_side` is `NoOrderSide`.
#[unsafe(no_mangle)]
#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
pub extern "C" fn orderbook_get_avg_px_for_quantity(
book: &mut OrderBook,
qty: Quantity,
order_side: OrderSideOptional,
) -> f64 {
book.get_avg_px_for_quantity(
qty,
order_side
.as_option()
.expect("Order side must be Buy or Sell"),View on GitHub (pinned to 18893faf8b)
Solutions
- Guard with a check that both bid and ask sides are non-empty before calling the midpoint FFI function.
- Defer midpoint calculations until a full two-sided snapshot has been applied.
- Compute the midpoint from optional best bid/ask in caller code, returning None when either side is absent.
- Ensure subscriptions deliver both bid and ask quotes/depth for the instrument.
Example fix
// before (Python FFI caller)
mid = orderbook_midpoint(book)
// after
if has_bid_orders(book) and has_ask_orders(book):
mid = orderbook_midpoint(book)
else:
mid = None Defensive patterns
Strategy: validation
Validate before calling
def can_compute_midpoint(book) -> bool:
return book.bids_len() > 0 and book.asks_len() > 0 Type guard
def is_two_sided(book) -> bool:
return len(book.bids()) > 0 and len(book.asks()) > 0 Prevention
- Defer midpoint math until both sides are populated
- Treat empty books as an expected state and query optional accessors instead of FFI panicking ones
- Remember FFI panics abort the process — they cannot be caught as exceptions
When it happens
Trigger: Calling `orderbook_midpoint(book)` when the bid or ask side is empty — partial quotes, pre-snapshot books, or books cleared by a reset.
Common situations: Warm-up periods before market data lands; one-sided liquidity; data feeds that publish only trades; querying after a `clear` in response to a gap fill.
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
- Error: No bid orders for best bid price
- Error: No ask orders for best ask price
- Error: No bid orders for best bid size
- Error: No ask orders for best ask size
- Error: Unable to calculate `spread` (no bid or ask)
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/7512740e309c129b.
Report an issue: GitHub.