nautechsystems/nautilus_trader · error · anyhow::Error

instrument_ratios list needs to have at least 2 legs

Error message

instrument_ratios list needs to have at least 2 legs

What it means

create_spread_instrument_id builds a spread (combo/bag) instrument ID from a list of (InstrumentId, ratio) legs and requires at least two legs; a single-leg 'spread' is meaningless so the call fails fast.

Source

Thrown at crates/adapters/interactive_brokers/src/common/parse.rs:1159

///
/// This implements the same logic as Python's `InstrumentId.new_spread`:
/// - Creates a symbol string like `(1)SYMBOL1_((2))SYMBOL2`
/// - Positive ratios: `(ratio)SYMBOL`
/// - Negative ratios: `((abs(ratio)))SYMBOL`
/// - Sorts legs alphabetically by symbol
/// - All legs must have the same venue
///
/// # Errors
///
/// Returns an error if:
/// - Less than 2 legs provided
/// - Any ratio is zero
/// - Venues don't match across legs
pub fn create_spread_instrument_id(
    leg_tuples: &[(InstrumentId, i32)],
) -> anyhow::Result<InstrumentId> {
    if leg_tuples.len() < 2 {
        anyhow::bail!("instrument_ratios list needs to have at least 2 legs");
    }

    let first_venue = leg_tuples[0].0.venue;

    for (instrument_id, ratio) in leg_tuples {
        if *ratio == 0 {
            anyhow::bail!("ratio cannot be zero");
        }

        if instrument_id.venue != first_venue {
            anyhow::bail!(
                "All venues must match. Expected {}, was {}",
                first_venue,
                instrument_id.venue
            );
        }
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the source contract has >= 2 combo legs before constructing the spread instrument ID
  2. Check that leg extraction from the IB BAG contract is not dropping legs (filters, leg type checks)
  3. Guard the call site: skip single/zero-leg bags instead of attempting to build a spread ID

Example fix

// before
let id = create_spread_instrument_id(&legs)?;
// after
if legs.len() < 2 {
    tracing::warn!("BAG contract has <2 legs, skipping");
    return Ok(None);
}
let id = create_spread_instrument_id(&legs)?;
Defensive patterns

Strategy: validation

Validate before calling

if legs.len() < 2 { return Err(anyhow::anyhow!("spread needs >= 2 legs")); }

Try / catch

match create_spread_instrument_id(&legs) {
    Ok(id) => id,
    Err(e) if e.to_string().contains("at least 2 legs") => {
        tracing::warn!("bag has insufficient legs ({})", legs.len());
        return Ok(None);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling create_spread_instrument_id (directly or via resolve_spread_instrument_id_for_contract / fetch_bag_contract) with an empty or one-element slice, e.g. when cached combo legs for a BAG contract contain only one leg or the legs list failed to populate.

Common situations: Parsing IB BAG contracts whose comboLegs were truncated or only partially received; building a spread instrument from a single leg due to a filtering bug or cache miss.

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