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
- Ensure each leg instrument's contract details are loaded into contract_details before calling fetch_spread_instrument
- Check that the spread instrument definition actually contains legs (non-empty ratios/leg list)
- Log leg_instrument_ids prior to fetching to confirm the legs collection is populated
- 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
- Always load individual legs before requesting a spread instrument
- Validate spread definitions contain at least one leg at config load time
- Handle leg-load errors instead of swallowing them so failures propagate early
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
- Leg instrument {} not found in contract details after loadin
- Contract details not found for leg {} after loading
- Failed to connect after {max_attempts} attempts
- Unknown IB security type: {value}
- Unknown IB option right: {value}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/8c1fac1e11e17818.
Report an issue: GitHub.