nautechsystems/nautilus_trader · error · anyhow::Error

Cannot submit an empty order list

Error message

Cannot submit an empty order list

What it means

handle_submit_order_list_async validates upfront that the batch it was asked to submit contains at least one order via anyhow::ensure!. An empty order list would produce zero IB child orders and no meaningful batch submission, so it is rejected before taking the order-submit lock.

Source

Thrown at crates/adapters/interactive_brokers/src/execution/core_orders.rs:422

        orders: &[OrderAny],
        client: &Arc<Client>,
        order_id_map: &Arc<Mutex<AHashMap<ClientOrderId, i32>>>,
        venue_order_id_map: &Arc<Mutex<AHashMap<i32, ClientOrderId>>>,
        instrument_id_map: &Arc<Mutex<AHashMap<i32, InstrumentId>>>,
        trader_id_map: &Arc<Mutex<AHashMap<i32, TraderId>>>,
        strategy_id_map: &Arc<Mutex<AHashMap<i32, StrategyId>>>,
        active_order_contexts: &Arc<Mutex<AHashMap<i32, TrackedOrderContext>>>,
        terminal_order_contexts: &Arc<Mutex<FifoCacheMap<i32, TrackedOrderContext, 10_000>>>,
        next_order_id: &Arc<Mutex<i32>>,
        instrument_provider: &Arc<InteractiveBrokersInstrumentProvider>,
        exec_sender: &tokio::sync::mpsc::UnboundedSender<ExecutionEvent>,
        clock: &'static AtomicTime,
        account_id: AccountId,
        strategy_id: StrategyId,
        order_submit_lock: &Arc<tokio::sync::Mutex<()>>,
    ) -> anyhow::Result<()> {
        let num_orders = orders.len();
        anyhow::ensure!(!orders.is_empty(), "Cannot submit an empty order list");

        let _submit_guard = order_submit_lock.lock().await;
        let ib_account = account_id
            .to_string()
            .split_once('-')
            .map_or_else(|| account_id.to_string(), |(_, value)| value.to_string());
        let mut ib_order_ids = AHashMap::with_capacity(num_orders);

        for order in orders {
            let ib_order_id = Self::reserve_next_local_order_id(next_order_id)?;
            ib_order_ids.insert(order.client_order_id(), ib_order_id);
        }

        for order in orders {
            if let Some(parent_order_id) = order.parent_order_id()
                && !ib_order_ids.contains_key(&parent_order_id)
            {
                let map = order_id_map.lock();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Guard the call site: skip calling handle_submit_order_list_async when orders.is_empty().
  2. Fix the batch-construction logic so at least one order survives filtering.
  3. Log the strategy state at the call site to see why the list ended up empty.

Example fix

// before
exec_client.handle_submit_order_list_async(orders, ...).await?;
// after
if !orders.is_empty() {
    exec_client.handle_submit_order_list_async(orders, ...).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(!orders.is_empty(), "refusing to submit empty order batch");

Prevention

When it happens

Trigger: Calling handle_submit_order_list_async with an empty Vec of orders (e.g. a bracket/OCO batch builder that filtered out all orders, or a caller passing an unpopulated list).

Common situations: Strategy code building contingent order batches where all entries were dropped by conditional logic (e.g. every leg failed validation); refactors that moved filtering before batch construction.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/ba8ad95fe2ff7626. Report an issue: GitHub.