nautechsystems/nautilus_trader · error · anyhow::Error

External order claim for

Error message

External order claim for {instrument_id} appears more than once for {strategy_id}

What it means

`claim_external_orders` iterates the requested instrument IDs and uses an AHashSet to detect duplicates within a single call. The same instrument appearing twice in one request is ambiguous (which strategy claim wins?) so it bails before mutating any state.

Solutions

  1. Deduplicate the instrument list before calling claim_external_orders (e.g. collect into a HashSet/AHashSet first)
  2. Fix the list construction so each instrument appears once per call
  3. Split into multiple calls if repeated claims across time are intended (per-call uniqueness still required)

Example fix

// before
let ids = vec![btc_usd, eth_usd, btc_usd];
cache.claim_external_orders(strategy_id, &ids)?;
// after
let ids: Vec<InstrumentId> = [btc_usd, eth_usd, btc_usd].into_iter().collect::<AHashSet<_>>().into_iter().collect();
cache.claim_external_orders(strategy_id, &ids)?;
Defensive patterns

Strategy: validation

Validate before calling

let unique: AHashSet<InstrumentId> = instruments.iter().copied().collect();
if unique.len() != instruments.len() {
    return Err(anyhow::anyhow!("duplicate instruments in claim request"));
}

Try / catch

if let Err(e) = cache.claim_external_orders(strategy_id, &instruments) {
    if e.to_string().contains("appears more than once") {
        let deduped: Vec<_> = instruments.iter().copied().collect::<AHashSet<_>>().into_iter().collect();
        cache.claim_external_orders(strategy_id, &deduped)?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling `claim_external_orders(strategy_id, &instruments)` with a slice containing the same `InstrumentId` twice in the same invocation.

Common situations: Building the instrument list by concatenating config lists that overlap; a loop that appends the same instrument each iteration; deduplication omitted when merging per-strategy and global instrument sets.

Related errors


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

Appendix: source

Thrown at crates/common/src/cache/mod.rs:2373

    /// External orders, fills, and materialized reconciliation activity for matching instrument
    /// IDs are assigned to the strategy. Existing claims owned by other strategies are preserved.
    ///
    /// The operation is atomic: either every requested instrument is claimed or the cache is
    /// unchanged. Passing an empty slice clears all claims owned by the strategy.
    ///
    /// # Errors
    ///
    /// Returns an error if an instrument is repeated or claimed by another strategy.
    pub fn set_external_order_claims(
        &mut self,
        strategy_id: StrategyId,
        instrument_ids: &[InstrumentId],
    ) -> anyhow::Result<()> {
        let mut requested = AHashSet::with_capacity(instrument_ids.len());

        for instrument_id in instrument_ids {
            if !requested.insert(*instrument_id) {
                anyhow::bail!(
                    "External order claim for {instrument_id} appears more than once for {strategy_id}"
                );
            }

            if let Some(existing) = self.external_order_claims.get(instrument_id)
                && *existing != strategy_id
            {
                anyhow::bail!(
                    "External order claim for {instrument_id} already exists for {existing}"
                );
            }
        }

        self.external_order_claims
            .retain(|_, owner| *owner != strategy_id);
        self.external_order_claims.extend(
            requested
                .into_iter()

View on GitHub (pinned to 18893faf8b)