nautechsystems/nautilus_trader · error · anyhow::Error

Cannot create BAG contract without leg details

Error message

Cannot create BAG contract without leg details

What it means

This error is thrown when creating an IB BAG (combo/spread) contract but the leg_contract_details slice passed in is empty. A BAG contract requires at least one ComboLeg built from ContractDetails, so an empty legs list is an invariant violation. The library throws it defensively in create_bag_contract_from_legs before dereferencing the first leg.

Source

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

            }
        }

        Ok(loaded_ids)
    }

    fn create_bag_contract_from_legs(
        &self,
        leg_contract_details: &[(ibapi::contracts::ContractDetails, i32)],
        instrument_id: Option<InstrumentId>,
        bag_contract: Option<&Contract>,
    ) -> anyhow::Result<Contract> {
        if let Some(bag_contract) = bag_contract {
            return Ok(bag_contract.clone());
        }

        let (first_details, _) = leg_contract_details
            .first()
            .ok_or_else(|| anyhow::anyhow!("Cannot create BAG contract without leg details"))?;

        let combo_legs = leg_contract_details
            .iter()
            .map(|(details, ratio)| ibapi::contracts::ComboLeg {
                contract_id: details.contract.contract_id,
                ratio: ratio.abs(),
                action: if *ratio > 0 {
                    LegAction::Buy
                } else {
                    LegAction::Sell
                },
                exchange: details.contract.exchange.to_string(),
                open_close: ComboLegOpenClose::Same,
                short_sale_slot: 0,
                designated_location: String::new(),
                exempt_code: -1,
            })
            .collect();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure each leg instrument's contract details are loaded into contract_details before calling fetch_spread_instrument
  2. Check that the spread instrument definition actually contains legs (non-empty ratios/leg list)
  3. Log leg_instrument_ids prior to fetching to confirm the legs collection is populated
  4. Verify the earlier leg-loading call returned Ok and was not swallowed upstream

Example fix

// before
let legs: Vec<(ContractDetails, i64)> = Vec::new();
let bag = create_bag_contract_from_legs(&legs)?;
// after
assert!(!legs.is_empty(), "spread must define at least one leg");
let bag = create_bag_contract_from_legs(&legs)?;
Defensive patterns

Strategy: validation

Validate before calling

if leg_contract_details.is_empty() {
    return Err(anyhow::anyhow!("refusing to create BAG: no leg details"));
}

Type guard

fn has_legs(legs: &[(ContractDetails, i64)]) -> bool { !legs.is_empty() }

Try / catch

match provider.fetch_spread_instrument(...) {
    Ok(inst) => inst,
    Err(e) if e.to_string().contains("without leg details") => {
        eprintln!("spread legs missing: {e}");
        return None;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: fetch_spread_instrument resolves a spread whose component legs have no cached contract details, or the spread definition has an empty legs list; the earlier per-leg lookup stage silently produced zero entries.

Common situations: Requesting a spread instrument before its legs were individually resolved/cached; a corrupted or partially loaded instrument definition whose legs vector is empty; a race where leg loads were skipped due to an earlier error.

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