{"record":{"id":"d75d00857e33e4ed","repo":"nautechsystems/nautilus_trader","slug":"error-unable-to-calculate-spread-no-bid-or-ask","errorCode":null,"errorMessage":"Error: Unable to calculate `spread` (no bid or ask)","messagePattern":"Error: Unable to calculate `spread` \\(no bid or ask\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/model/src/ffi/orderbook/book.rs","lineNumber":308,"sourceCode":"///\n/// Panics if there are no ask orders for best ask size.\n#[unsafe(no_mangle)]\n#[cfg_attr(feature = \"high-precision\", allow(improper_ctypes_definitions))]\npub extern \"C\" fn orderbook_best_ask_size(book: &mut OrderBook) -> Quantity {\n    abort_on_panic(|| {\n        book.best_ask_size()\n            .expect(\"Error: No ask orders for best ask size\")\n    })\n}\n\n/// # Panics\n///\n/// Panics if unable to calculate spread (requires at least one bid and one ask).\n#[unsafe(no_mangle)]\npub extern \"C\" fn orderbook_spread(book: &mut OrderBook) -> f64 {\n    abort_on_panic(|| {\n        book.spread()\n            .expect(\"Error: Unable to calculate `spread` (no bid or ask)\")\n    })\n}\n\n/// # Panics\n///\n/// Panics if unable to calculate midpoint (requires at least one bid and one ask).\n#[unsafe(no_mangle)]\npub extern \"C\" fn orderbook_midpoint(book: &mut OrderBook) -> f64 {\n    abort_on_panic(|| {\n        book.midpoint()\n            .expect(\"Error: Unable to calculate `midpoint` (no bid or ask)\")\n    })\n}\n\n/// # Panics\n///\n/// Panics if `order_side` is `NoOrderSide`.\n#[unsafe(no_mangle)]","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/ffi/orderbook/book.rs#L290-L326","documentation":"This panic occurs in the FFI wrapper `orderbook_spread` when `OrderBook::spread()` returns None, which happens when the book lacks at least one bid AND one ask, making the spread undefined. Because the wrapper uses `abort_on_panic`, the panic aborts the process instead of returning an error across FFI.","triggerScenarios":"Calling `orderbook_spread(book)` when either side (bids or asks) is empty — e.g. one-sided quotes only, before the first full snapshot, or after levels were deleted.","commonSituations":"Crossed/one-sided books from partial feeds; computing spread during strategy warm-up before data arrives; books for instruments with thin/absent one side; post-disconnect stale books.","solutions":["Check that both `has_bid_orders` and `has_ask_orders` (or equivalent best bid/ask availability) hold before calling the spread FFI function.","Wait for a complete initial snapshot (both sides populated) before computing spread.","Compute spread caller-side from optional best bid/ask values, returning None when either is missing.","Fix market-data subscriptions so both bid and ask updates flow for the instrument."],"exampleFix":"// before (Python FFI caller)\nspread = orderbook_spread(book)\n// after\nif has_bid_orders(book) and has_ask_orders(book):\n    spread = orderbook_spread(book)\nelse:\n    spread = None","handlingStrategy":"validation","validationCode":"def can_compute_spread(book) -> bool:\n    return book.bids_len() > 0 and book.asks_len() > 0","typeGuard":"def is_two_sided(book) -> bool:\n    return len(book.bids()) > 0 and len(book.asks()) > 0","tryCatchPattern":null,"preventionTips":["Require a full two-sided snapshot before computing spread or midpoint","Handle one-sided/thin books explicitly (skip spread logic)","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"],"tags":["rust","ffi","orderbook","panic","spread","process-abort"],"backgroundTag":"empty-result-set","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}