nautechsystems/nautilus_trader · error · anyhow::Error

Invalid BAG contract: must have security_type=Spread and non

Error message

Invalid BAG contract: must have security_type=Spread and non-empty combo_legs

What it means

fetch_bag_contract loads a BAG (combo/spread) contract from IB and first validates the input contract. IB represents spreads as security type 'Spread' (BAG) with a non-empty combo_legs list; anything else cannot be decomposed into legs, so the call fails with this validation error.

Source

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

    /// Returns the number of spread instruments loaded (0 or 1).
    ///
    /// # Errors
    ///
    /// Returns an error if fetching fails.
    ///
    /// # Notes
    ///
    /// This method now auto-loads all leg instruments from combo_legs and creates
    /// a proper spread instrument, matching Python's `_load_bag_contract` behavior.
    pub async fn fetch_bag_contract(
        &self,
        client: &ibapi::Client,
        bag_contract: &Contract,
    ) -> anyhow::Result<usize> {
        // Validate BAG contract
        if bag_contract.security_type != SecurityType::Spread || bag_contract.combo_legs.is_empty()
        {
            anyhow::bail!(
                "Invalid BAG contract: must have security_type=Spread and non-empty combo_legs"
            );
        }

        tracing::debug!(
            "Loading BAG contract with {} legs",
            bag_contract.combo_legs.len()
        );

        // First, load all individual leg instruments and collect their details
        let mut leg_contract_details = Vec::new();
        let mut leg_tuples = Vec::new();

        for combo_leg in &bag_contract.combo_legs {
            // Create a leg contract using information from the combo leg
            let leg_contract = Contract {
                contract_id: combo_leg.contract_id,  // Use conId from combo_leg
                symbol: bag_contract.symbol.clone(), // Use underlying symbol from BAG

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the contract has security_type = SecurityType::Spread before calling the BAG load path.
  2. Populate combo_legs with at least one leg (con_id, ratio, action).
  3. Check upstream code isn't misclassifying the contract as BAG when it is a single instrument.
  4. Fetch contract details from IB to populate legs automatically rather than constructing by hand.

Example fix

// before: mislabeled BAG
let bag = Contract { symbol: "SPY".into(), security_type: SecurityType::Stock, ..Default::default() };
provider.fetch_bag_contract(&client, &bag).await?;
// after: proper BAG definition
let bag = Contract {
    symbol: "SPY,VND".into(),
    security_type: SecurityType::Spread,
    combo_legs: vec![leg_buy, leg_sell],
    ..Default::default()
};
provider.fetch_bag_contract(&client, &bag).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_bag(contract: &Contract) -> bool {
    contract.security_type == SecurityType::Spread && !contract.combo_legs.is_empty()
}

Prevention

When it happens

Trigger: get_instrument calls fetch_bag_contract with a contract whose security_type is not SecurityType::Spread or whose combo_legs is empty.

Common situations: Passing a single-leg contract or a plain future/option to the BAG loading path, or a BAG contract built without legs (missing con_id/ratio/action data).

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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