nautechsystems/nautilus_trader · error · anyhow::Error

External order claim for {instrument_id} appears more than o

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.

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)

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.