nautechsystems/nautilus_trader · error · anyhow::Error

All venues must match. Expected {}, was {}

Error message

All venues must match. Expected {}, was {}

What it means

All legs of a spread must belong to the same venue because the resulting spread InstrumentId encodes a single venue; mixed venues cannot be represented. The error names the expected (first leg's) venue and the mismatching leg's venue.

Source

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

/// - 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
            );
        }
    }

    let mut sorted_ratios = leg_tuples.to_vec();
    sorted_ratios.sort_by(|a, b| a.0.symbol.as_str().cmp(b.0.symbol.as_str()));

    let symbol_parts = sorted_ratios
        .iter()
        .map(|(instrument_id, ratio)| {
            if *ratio > 0 {
                format!("({}){}", ratio, instrument_id.symbol.as_str())
            } else {
                format!("(({})){}", ratio.abs(), instrument_id.symbol.as_str())
            }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure every leg InstrumentId uses the same venue suffix before building the spread
  2. Fix the venue assignment when constructing leg InstrumentIds (e.g. wrong exchange mapping)
  3. Filter out legs from other venues instead of mixing them

Example fix

// before
let legs = vec![(InstrumentId::from("AAPL  251212C00250000.OPRA"), -1),
                (InstrumentId::from("MSFT  251212P00400000.OPRA"), 1)]; // venue must match, MSFT leg wrong here
// after
let legs = vec![(InstrumentId::from("AAPL  251212C00250000.OPRA"), -1),
                (InstrumentId::from("AAPL  251212P00250000.OPRA"), 1)];
Defensive patterns

Strategy: validation

Validate before calling

let first = legs.first().map(|(id, _)| id.venue);
if legs.iter().any(|(id, _)| Some(id.venue) != first) {
    return Err(anyhow::anyhow!("spread legs span multiple venues"));
}

Try / catch

match create_spread_instrument_id(&legs) {
    Ok(id) => id,
    Err(e) if e.to_string().contains("All venues must match") => {
        tracing::error!("mixed-venue legs: {e}");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling create_spread_instrument_id with legs from different venues, e.g. mixing '.OPRA' option legs with another venue's instruments, or leg InstrumentIds parsed with inconsistent/incorrect venue suffixes.

Common situations: Combining option legs from different data sources, misconfigured venue mappings during parsing, or spreading across venues in multi-venue setups.

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