nautechsystems/nautilus_trader · error

Cannot batch cancel orders for different instruments: {} vs

Error message

Cannot batch cancel orders for different instruments: {} vs {}

What it means

All orders named in a batch cancel must belong to the same instrument. Each order's instrument_id is compared to the first order's; any mismatch aborts the batch because a BatchCancelOrders command is venue/instrument-scoped.

Source

Thrown at crates/trading/src/strategy/mod.rs:714

        // TODO: Snapshot all orders from the cache. See `cancel_order` for the rationale.
        let orders: Vec<OrderAny> = {
            let cache_rc = StrategyNative::strategy_core_mut(self).cache_rc();
            let cache = cache_rc.borrow();
            client_order_ids
                .iter()
                .map(|id| {
                    cache
                        .try_order_owned(id)
                        .map_err(|e| anyhow::anyhow!("Cannot cancel order: {e}"))
                })
                .collect::<Result<_, _>>()?
        };

        let instrument_id = orders[0].instrument_id();

        for order in &orders {
            if order.instrument_id() != instrument_id {
                anyhow::bail!(
                    "Cannot batch cancel orders for different instruments: {} vs {}",
                    instrument_id,
                    order.instrument_id()
                );
            }

            if order.is_emulated() || order.is_active_local() {
                anyhow::bail!("Cannot include emulated or local orders in batch cancel");
            }
        }

        let mut cancels = Vec::with_capacity(orders.len());

        for order in orders {
            if !self.mark_order_pending_cancel(&order)? {
                continue;
            }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Group client_order_ids by instrument_id and issue one cancel_orders call per instrument
  2. Verify the list only contains IDs from a single instrument (e.g. from strategy orders for that instrument)
  3. Use cancel_all_orders(instrument_id) per instrument instead of a mixed batch

Example fix

// before
strategy.cancel_orders(&all_ids, instrument_id, None, None).await?;
// after
for iid in all_instruments {
    let ids: Vec<ClientOrderId> = all_orders.iter()
        .filter(|o| o.instrument_id() == iid)
        .map(|o| o.client_order_id())
        .collect();
    if !ids.is_empty() {
        strategy.cancel_orders(&ids, iid, None, None).await?;
    }
}
Defensive patterns

Strategy: validation

Validate before calling

let resolved: Vec<_> = ids.iter().map(|id| cache.order(id)).collect::<Option<Vec<_>>>().unwrap_or_default();
let iid = resolved[0].instrument_id();
assert!(resolved.iter().all(|o| o.instrument_id() == iid), "batch cancel spans instruments");

Prevention

When it happens

Trigger: Passing client_order_ids resolving to orders on two or more different instruments to `cancel_orders`.

Common situations: Cancelling 'all working orders' across symbols in one call; building the ID list from a multi-instrument cache scan; after a symbol change without regenerating the list.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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