nautechsystems/nautilus_trader · error · anyhow::Error

Contract details not found for leg {} after loading

Error message

Contract details not found for leg {} after loading

What it means

fetch_bag_contract reads each combo leg's ContractDetails from the provider cache, expecting it to have been cached during an earlier load. This error means the leg is still absent from contract_details after the load phase, so the BAG contract cannot be built.

Source

Thrown at crates/adapters/interactive_brokers/src/providers/instruments.rs:2439

                    self.price_magnifiers
                        .insert(leg_instrument_id, leg_details.price_magnifier);

                    leg_instrument_id
                };

            // Determine ratio (positive for BUY, negative for SELL)
            let ratio = IbAction::from_str(combo_leg.action.as_str())
                .context("Invalid combo leg action")?
                .signed_multiplier()
                * combo_leg.ratio;

            // Get the contract details for this leg (should be cached now)
            let leg_details_clone = self
                .contract_details
                .get(&leg_instrument_id)
                .map(|entry| entry.value().clone())
                .ok_or_else(|| {
                    anyhow::anyhow!(
                        "Contract details not found for leg {} after loading",
                        leg_instrument_id
                    )
                })?;

            leg_contract_details.push((leg_details_clone, ratio));
            leg_tuples.push((leg_instrument_id, ratio));
        }

        if leg_tuples.is_empty() {
            anyhow::bail!("No valid legs loaded for BAG contract");
        }

        // Create spread instrument ID from leg tuples
        let spread_instrument_id = create_spread_instrument_id(&leg_tuples)
            .context("Failed to create spread instrument ID from leg tuples")?;

        // Fetch BAG contract details (for storing the mapping)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Load each leg's contract details individually and verify success before requesting the BAG contract
  2. Validate leg definitions (symbol, exchange, currency) resolve to real IB contracts
  3. Serialize spread fetches so legs are guaranteed cached before combination
  4. Inspect for cache eviction or non-insertion paths when IB returns empty contract details

Example fix

// before
let bag = self.fetch_bag_contract(&spread).await?;
// after
for leg in spread.legs() {
    self.ensure_contract_details_loaded(leg).await?;
}
let bag = self.fetch_bag_contract(&spread).await?;
Defensive patterns

Strategy: validation

Validate before calling

if !provider.contract_details.contains_key(&leg_id) {
    provider.load_instrument(leg_id).await?;
    anyhow::ensure!(provider.contract_details.contains_key(&leg_id), "leg {leg_id} failed to load");
}

Type guard

fn leg_cached(provider: &IbInstrumentsProvider, id: &InstrumentId) -> bool {
    provider.contract_details.contains_key(id)
}

Try / catch

match provider.get_instrument(instrument_id).await {
    Ok(i) => i,
    Err(e) if e.to_string().contains("not found for leg") => {
        eprintln!("leg cache miss: {e}");
        Default::default()
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: get_instrument resolves a spread whose leg instrument_id was never inserted into the contract_details cache — the preceding leg load found no matching IB contract, or cache insertion was skipped for that leg.

Common situations: Leg symbol/venue/currency doesn't match a real IB contract so the load yields nothing; spread fetched concurrently before leg loads complete; provider restarted with an empty cache but stale spread definitions.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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