nautechsystems/nautilus_trader · error
BookOrder side must be Buy or Sell
Error message
BookOrder side must be Buy or Sell
What it means
BookOrder::to_book_price() converts the order's price into a BookPrice, but BookOrder.side is an Option<OrderSide>. The library panics via expect when side is None because a book order without a side has no valid price interpretation. It is a deliberate fail-fast for a struct that was constructed incompletely.
Source
Thrown at crates/model/src/data/order.rs:96
) -> Self {
Self {
side: side.into(),
price,
size,
order_id,
}
}
/// Returns a [`BookPrice`] from this order.
///
/// # Panics
///
/// Panics if `self.side` is `None`.
#[must_use]
pub fn to_book_price(&self) -> BookPrice {
BookPrice::new(
self.price,
self.side.expect("BookOrder side must be Buy or Sell"),
)
}
/// Returns the order exposure as an `f64`.
#[must_use]
pub fn exposure(&self) -> f64 {
self.price.as_f64() * self.size.as_f64()
}
/// Returns the signed order size as `f64`, positive for buys, negative for sells.
///
/// # Panics
///
/// Panics if `self.side` is `None`.
#[must_use]
pub fn signed_size(&self) -> f64 {
match self.side {
Some(OrderSide::Buy) => self.size.as_f64(),View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure the BookOrder is constructed with a valid Some(OrderSide::Buy) or Some(OrderSide::Sell) before calling to_book_price().
- Check side.is_some() before calling to_book_price(), or match on the Option and handle the None case explicitly.
- If the order came from an upstream feed, fix the parser/adapter so the side is always populated from the message.
Example fix
// before
let book_price = order.to_book_price();
// after
let book_price = order
.side
.map(|_| order.to_book_price())
.unwrap_or_else(|| panic!("BookOrder {:?} has no side; skipping", order.order_id)); Defensive patterns
Strategy: type-guard
Validate before calling
if order.side.is_none() { /* skip or reconstruct from feed */ } Type guard
fn has_side(order: &BookOrder) -> bool { order.side.is_some() } Try / catch
// Rust panics are not catchable idiomatic; guard instead: let price = order.side.map(|_| order.to_book_price());
Prevention
- Always construct BookOrder with a known OrderSide from the feed message.
- Validate L1/L2 updates carry a side before book assembly.
- Add a debug_assert in feed adapters that side is populated.
When it happens
Trigger: Calling to_book_price() on a BookOrder whose side field is None — typically a BookOrder built with side: None (e.g. from a partial L1/L2 update that did not carry a side, or a default-constructed order) then passed into book assembly paths like add, replace_l1, or book integrity checks.
Common situations: Feeding market-data deltas where an L1 update lacks a side; constructing BookOrder manually for tests with side omitted; deserializing a partial order book snapshot that dropped the side field.
Related errors
- `handle_book_mbo` {IMPL_ERR} `{}`
- NoOrderSide
- MBO delta tail must be queued
- L3 queue update should be processed
- in-flight mutex poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/41912624f101bc89.
Report an issue: GitHub.