nautechsystems/nautilus_trader · error · anyhow::Error

client_order_ids cannot be empty

Error message

client_order_ids cannot be empty

What it means

Raised by cancel_orders when the caller supplies Some(client_order_ids) with an empty Vec (and no venue_order_ids). BitMEX requires at least one clOrdID to cancel; an empty list would be rejected or cancel nothing. The adapter fails fast locally instead of sending a doomed request.

Source

Thrown at crates/adapters/bitmex/src/http/client.rs:1861

    ) -> anyhow::Result<Vec<OrderStatusReport>> {
        let mut params = super::query::DeleteOrderParamsBuilder::default();
        params.text(NAUTILUS_TRADER);

        // BitMEX API requires either client order IDs or venue order IDs, not both
        // Prioritize venue order IDs if both are provided
        if let Some(venue_order_ids) = venue_order_ids {
            if venue_order_ids.is_empty() {
                anyhow::bail!("venue_order_ids cannot be empty");
            }
            params.order_id(
                venue_order_ids
                    .iter()
                    .map(|id| id.to_string())
                    .collect::<Vec<_>>(),
            );
        } else if let Some(client_order_ids) = client_order_ids {
            if client_order_ids.is_empty() {
                anyhow::bail!("client_order_ids cannot be empty");
            }
            params.cl_ord_id(
                client_order_ids
                    .iter()
                    .map(|id| id.to_string())
                    .collect::<Vec<_>>(),
            );
        } else {
            anyhow::bail!("Either client_order_ids or venue_order_ids must be provided");
        }

        let params = params.build().map_err(|e| anyhow::anyhow!(e))?;

        let orders: Vec<BitmexOrder> = self.inner.cancel_orders_response(params).await?;

        let ts_init = self.generate_ts_init();
        let instrument = self.instrument_from_cache(instrument_id.symbol.inner())?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Only pass Some(client_order_ids) when the Vec is non-empty
  2. Validate at the call site and skip the request when the list is empty
  3. Switch to venue_order_ids or use the cancel_all_orders endpoint if the intent was to cancel everything

Example fix

// before
client.cancel_orders(Some(cl_ord_ids), None).await?;
// after
if cl_ord_ids.is_empty() { return Ok(()); }
client.cancel_orders(Some(cl_ord_ids), None).await?;
Defensive patterns

Strategy: validation

Validate before calling

if let Some(ids) = &client_order_ids {
    if ids.is_empty() { return Err(anyhow!("client_order_ids must not be empty")); }
}
client.cancel_orders(client_order_ids, venue_order_ids).await?;

Type guard

fn non_empty(ids: &Option<Vec<ClientOrderId>>) -> bool {
    ids.as_ref().is_some_and(|v| !v.is_empty())
}

Try / catch

match client.cancel_orders(client_order_ids, venue_order_ids).await {
    Ok(reports) => { /* handle */ }
    Err(e) if e.to_string().contains("cannot be empty") => { /* fix inputs and retry */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling cancel_orders with client_order_ids=Some(vec![]) and venue_order_ids=None. Common when client order IDs are collected from a filtered order set that yields no matches.

Common situations: Cancelling orders by client order ID after a filter (e.g. by instrument or strategy) that removed all entries; migrating code that assumed empty means 'cancel all'.

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/ecdebb1fa125b4b1. Report an issue: GitHub.