nautechsystems/nautilus_trader · error
invalid OrderSide: must be Buy or Sell, was {side}
Error message
invalid OrderSide: must be Buy or Sell, was {side} What it means
specified_order_side (crates/model/src/data/bet.rs:778) narrows the three-valued OrderSide (BUY/SELL/NO_ORDER_SIDE) into OrderSideSpecified for the betting conversion functions (probability_to_bet, inverse_probability_to_bet). NO_ORDER_SIDE means "unset", so rather than guessing a direction the function bails.
Source
Thrown at crates/model/src/data/bet.rs:786
fn check_nonzero_denominator(value: Decimal, name: &str) -> anyhow::Result<()> {
if value.is_zero() {
anyhow::bail!("invalid {name}: must be non-zero")
}
Ok(())
}
/// Converts [`OrderSide`] into a specified side for betting conversions.
///
/// # Errors
///
/// Returns an error if `side` is [`OrderSide::NoOrderSide`].
pub fn specified_order_side(side: OrderSide) -> anyhow::Result<OrderSideSpecified> {
match side {
OrderSide::Buy => Ok(OrderSideSpecified::Buy),
OrderSide::Sell => Ok(OrderSideSpecified::Sell),
OrderSide::NoOrderSide => {
anyhow::bail!("invalid OrderSide: must be Buy or Sell, was {side}")
}
}
}
fn checked_add(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
lhs.checked_add(rhs)
.ok_or_else(|| anyhow::anyhow!("Decimal overflow adding {lhs} and {rhs}"))
}
fn checked_sub(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
lhs.checked_sub(rhs)
.ok_or_else(|| anyhow::anyhow!("Decimal overflow subtracting {rhs} from {lhs}"))
}
fn checked_mul(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
lhs.checked_mul(rhs)
.ok_or_else(|| anyhow::anyhow!("Decimal overflow multiplying {lhs} by {rhs}"))
}View on GitHub (pinned to 2114cf6f76)
Solutions
- Set the order side (BUY or SELL) before any betting conversion.
- Filter or skip orders with OrderSide::NoOrderSide before converting.
- Fix the upstream construction or deserialization that leaves the side unset.
Example fix
// before
let bet = probability_to_bet(prob, volume, specified_order_side(order.side())?)?;
// after
let side = match order.side() {
OrderSide::Buy => OrderSideSpecified::Buy,
OrderSide::Sell => OrderSideSpecified::Sell,
OrderSide::NoOrderSide => {
anyhow::bail!("order {} has no side; set BUY or SELL first", order.id())
}
};
let bet = probability_to_bet(prob, volume, side)?; Defensive patterns
Strategy: type-guard
Validate before calling
if matches!(order.side(), OrderSide::NoOrderSide) {
anyhow::bail!("order side is unset; set BUY or SELL before betting conversion");
}
let side = specified_order_side(order.side())?; Type guard
fn has_specified_side(side: OrderSide) -> bool {
!matches!(side, OrderSide::NoOrderSide)
} Try / catch
match specified_order_side(order.side()) {
Ok(side) => side,
Err(e) if e.to_string().contains("must be Buy or Sell") => {
anyhow::bail!("order {} has no side set", order.client_order_id())
}
Err(e) => return Err(e),
} Prevention
- Always set BUY or SELL when constructing orders destined for betting conversions.
- Filter out sideless orders before batch conversion.
- Tighten deserialization defaults so a missing side field fails early, not here.
When it happens
Trigger: specified_order_side(OrderSide::NoOrderSide), usually indirectly: passing an order whose side was never set into probability_to_bet/inverse_probability_to_bet, or a default-initialized OrderSide enum value.
Common situations: Constructing orders without a side; deserialization falling back to the NO_ORDER_SIDE default on missing fields; generic code that converts every order in a collection when some are sideless placeholders.
Related errors
- Unsupported `OrderSide` for Binance: {value:?}
- Liability-based betting is only applicable for Lay side.
- invalid probability: must be non-zero
- invalid probability: must not be 1.0 (inverse would be zero)
- Price must be greater than 1.0 for lay liability calculation
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/b70cc8a91d5b58d6.
Report an issue: GitHub.