nautechsystems/nautilus_trader · error

Batch cancel failed: {error_msg}

Error message

Batch cancel failed: {error_msg}

What it means

Batch cancel (cancel multiple orders at once, in chunks of BATCH_CANCEL_LIMIT) returned result != Success; the error field text from Kraken Futures is embedded in the message. The whole chunk's batch request failed at the venue.

Source

Thrown at crates/adapters/kraken/src/http/futures/client.rs:2357

    /// # Returns
    /// The total number of successfully cancelled orders.
    pub async fn cancel_orders_batch(
        &self,
        venue_order_ids: Vec<VenueOrderId>,
    ) -> anyhow::Result<usize> {
        if venue_order_ids.is_empty() {
            return Ok(0);
        }

        let mut total_cancelled = 0;

        for chunk in venue_order_ids.chunks(BATCH_CANCEL_LIMIT) {
            let order_ids: Vec<String> = chunk.iter().map(|id| id.to_string()).collect();
            let response = self.inner.cancel_orders_batch(order_ids).await?;

            if response.result != KrakenApiResult::Success {
                let error_msg = response.error.as_deref().unwrap_or("Unknown error");
                anyhow::bail!("Batch cancel failed: {error_msg}");
            }

            let success_count = response
                .batch_status
                .iter()
                .filter(|s| {
                    s.status == Some(KrakenSendStatus::Cancelled)
                        || s.cancel_status
                            .as_ref()
                            .is_some_and(|cs| cs.status == KrakenSendStatus::Cancelled)
                })
                .count();

            total_cancelled += success_count;
        }

        Ok(total_cancelled)
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the embedded error_msg to identify whether it is a rate limit, auth, or per-order validation problem.
  2. Reduce batch size / pace requests if the error indicates rate limiting; the client already chunks by BATCH_CANCEL_LIMIT but back off on failures.
  3. Validate every venue_order_id in the list corresponds to an open order before batching.
  4. Fall back to per-order cancels for the chunk and collect per-order outcomes so one bad id doesn't abort all.

Example fix

// before: one chunk failure aborts everything
client.batch_cancel_orders(instrument_id, &all_ids).await?;

// after: per-order fallback on chunk failure
match client.batch_cancel_orders(instrument_id, &all_ids).await {
    Ok(()) => {}
    Err(e) => { for id in &all_ids { let _ = client.cancel_order(instrument_id, None, Some(*id)).await; } }
}
Defensive patterns

Strategy: fallback

Validate before calling

assert!(ids.len() <= 100, "Kraken batch cancel limit exceeded");
assert!(ids.iter().all(|i| open_orders.contains(i)), "batch contains non-open order ids");

Type guard

fn batch_is_valid(ids: &[VenueOrderId], open: &HashSet<VenueOrderId>) -> bool {
    !ids.is_empty() && ids.len() <= 100 && ids.iter().all(|i| open.contains(i))
}

Try / catch

match client.batch_cancel_orders(instrument_id, &ids).await {
    Err(e) => {
        log::warn!("batch cancel failed ({e}); falling back to per-order cancels");
        for id in &ids { let _ = client.cancel_order(instrument_id, Some(*id), None).await; }
    }
    Ok(_) => {}
}

Prevention

When it happens

Trigger: Calling the batch cancel with >100 ids (beyond Kraken's limit) or an invalid id in the list; venue rejects the whole batch for auth, rate limit, or one malformed order_id; API result flag not 'success' in the batch response.

Common situations: Mass-flatten logic cancelling many open orders at once; stale id lists from a previous session; hitting Kraken batch-cancel rate limits during volatile markets.

Related errors


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