nautechsystems/nautilus_trader · error · anyhow::Error

No valid Interactive Brokers order ID available

Error message

No valid Interactive Brokers order ID available

What it means

reserve_next_local_order_id locks the shared next_order_id counter and ensures it is positive before handing out an IB order ID. If the counter is zero or negative — meaning no valid order ID has been received from the IB gateway (via the nextValidId message) — this error is thrown. It guards against issuing order IDs before the connection is properly seeded.

Source

Thrown at crates/adapters/interactive_brokers/src/execution/core.rs:362

            {
                tracing::error!("Error submitting order list: {e}");
            }
        };

        self.pending_tasks
            .spawn(future)
            .context("failed to register IB execution command task")?;

        Ok(())
    }

    fn cached_order_for_modify(&self, client_order_id: &ClientOrderId) -> Option<OrderAny> {
        self.core.cache().order(client_order_id).map(|o| o.clone())
    }

    fn reserve_next_local_order_id(next_order_id: &Arc<Mutex<i32>>) -> anyhow::Result<i32> {
        let mut guard = next_order_id.lock();
        anyhow::ensure!(
            *guard > 0,
            "No valid Interactive Brokers order ID available"
        );
        let order_id = *guard;
        *guard += 1;
        Ok(order_id)
    }

    fn apply_client_order_id_floor(next_id: i32, client_id: i32) -> i32 {
        let client_slot = client_id.unsigned_abs() % 1000;
        if client_slot == 0 {
            return next_id;
        }

        let order_id_floor = (client_slot as i32) * 1_000_000;
        if next_id > order_id_floor {
            next_id
        } else {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Wait for the client connection/handshake to complete (next valid order ID received) before submitting orders.
  2. Verify the connect() flow populates next_order_id with the value from IB's nextValidId and logs it.
  3. Retry order submission after the client reports it is fully connected; add readiness gating in the trading node before dispatching orders.

Example fix

// before
let order_id = self.reserve_next_local_order_id()?;
// after
if !self.is_order_id_ready() {
    anyhow::bail!("Cannot submit order: IB order ID not yet received from gateway");
}
let order_id = self.reserve_next_local_order_id()?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn order_ids_available(next_order_id: &Arc<Mutex<i32>>) -> bool {
    *next_order_id.lock() > 0
}

Try / catch

match reserve_next_local_order_id(&next_order_id) {
    Ok(id) => submit(id),
    Err(e) => { tracing::warn!("Order ID not ready: {}", e); defer_until_connected(); }
}

Prevention

When it happens

Trigger: Submitting an order (which reserves a local order ID) before the IB gateway has delivered the next valid order ID, or if a stale/invalid value (<= 0) was stored in the counter.

Common situations: Sending orders immediately after client start before the nextValidId handshake completes; reconnect logic resetting the counter to 0; gateway issues that prevent the initial order ID message from arriving.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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