nautechsystems/nautilus_trader · error · anyhow::Error
Invalid order side: {e}
Error message
Invalid order side: {e} What it means
Thrown while pricing an emulated MARKET order during submit_order: the adapter converts the Nautilus OrderSide to AX's two-value side enum (B/S) via AxOrderSide::try_from. AX only encodes Buy and Sell, so any other OrderSide discriminant (NO_SIDE or UNDEFINED) fails with 'Invalid order side for AX', wrapped by this message. In practice Nautilus orders normally always carry BUY or SELL, so hitting this means an order object was built or deserialized without a side.
Source
Thrown at crates/adapters/architect_ax/src/execution.rs:228
};
let ws_orders = self.ws_orders.clone();
let trader_id = self.core.trader_id;
let emitter = self.emitter.clone();
let clock = self.clock;
let http_client = self.http_client.clone();
self.spawn_task("submit_order", async move {
// AX emulates market orders with preview-priced IOC limits, so book moves
// between preview and submission can produce partial fills.
let (price, submit_time_in_force, submit_post_only) = if order_type
== OrderType::Market
{
let preview_result: anyhow::Result<Price> = async {
let symbol = instrument_id.symbol.inner();
let ax_side = AxOrderSide::try_from(order_side)
.map_err(|e| anyhow::anyhow!("Invalid order side: {e}"))?;
let qty_contracts = quantity_to_contracts(quantity)?;
let instrument = http_client.get_instrument(&symbol).ok_or_else(|| {
anyhow::anyhow!("Instrument {instrument_id} not found in cache")
})?;
let request =
PreviewAggressiveLimitOrderRequest::new(symbol, qty_contracts, ax_side);
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!(View on GitHub (pinned to a4b06ed870)
Solutions
- Log order.order_side() before submit and fix the caller so the order is created with OrderSide::Buy or OrderSide::Sell
- Create orders through the Strategy's order_factory so side is always populated
- If the side truly is optional in your flow, submit a LIMIT order only after the side is known
Example fix
// before
let order = factory.market(
instrument.id,
order_side_from_signal.unwrap_or_default(), // NO_SIDE when None
qty,
); // -> "Invalid order side"
// after
let side = order_side_from_signal
.context("signal produced no order side")?; // fail before submit
let order = factory.market(instrument.id, side, qty); Defensive patterns
Strategy: validation
Validate before calling
// Before submit_order for a MARKET order
use nautilus_model::enums::OrderSide;
fn side_ok(side: OrderSide) -> bool {
matches!(side, OrderSide::Buy | OrderSide::Sell)
}
assert!(side_ok(order.order_side()), "order has no side"); Type guard
fn is_ax_supported_side(side: OrderSide) -> bool {
matches!(side, OrderSide::Buy | OrderSide::Sell)
} Prevention
- Always build orders with the Strategy order_factory so side is a required argument
- Assert side_ok(...) in debug builds before every submit
- Never default a side parameter with unwrap_or_default() (that yields NO_SIDE)
When it happens
Trigger: Submitting a MARKET order whose order_side() is NO_SIDE/UNDEFINED (manually constructed OrderAny, corrupted order state, or a code path that creates an OrderInitialized without setting side); any future OrderSide variant added to Nautilus that the adapter has not mapped.
Common situations: Custom strategy code constructing orders directly instead of via the order factory; porting code where the side parameter was accidentally dropped; serialized/restored order objects with a default-0 (NO_SIDE) side field.
Related errors
- Instrument {instrument_id} not found in cache
- Failed to preview aggressive limit order: {e}
- No liquidity available for market order on {instrument_id}
- 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/6cd347dc51d426c9.
Report an issue: GitHub.