{"record":{"id":"db3f133e652eedc6","repo":"nautechsystems/nautilus_trader","slug":"instrument-ratios-list-needs-to-have-at-least-2-le","errorCode":null,"errorMessage":"instrument_ratios list needs to have at least 2 legs","messagePattern":"instrument_ratios list needs to have at least 2 legs","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/interactive_brokers/src/common/parse.rs","lineNumber":1159,"sourceCode":"///\n/// This implements the same logic as Python's `InstrumentId.new_spread`:\n/// - Creates a symbol string like `(1)SYMBOL1_((2))SYMBOL2`\n/// - Positive ratios: `(ratio)SYMBOL`\n/// - Negative ratios: `((abs(ratio)))SYMBOL`\n/// - Sorts legs alphabetically by symbol\n/// - All legs must have the same venue\n///\n/// # Errors\n///\n/// Returns an error if:\n/// - Less than 2 legs provided\n/// - Any ratio is zero\n/// - Venues don't match across legs\npub fn create_spread_instrument_id(\n    leg_tuples: &[(InstrumentId, i32)],\n) -> anyhow::Result<InstrumentId> {\n    if leg_tuples.len() < 2 {\n        anyhow::bail!(\"instrument_ratios list needs to have at least 2 legs\");\n    }\n\n    let first_venue = leg_tuples[0].0.venue;\n\n    for (instrument_id, ratio) in leg_tuples {\n        if *ratio == 0 {\n            anyhow::bail!(\"ratio cannot be zero\");\n        }\n\n        if instrument_id.venue != first_venue {\n            anyhow::bail!(\n                \"All venues must match. Expected {}, was {}\",\n                first_venue,\n                instrument_id.venue\n            );\n        }\n    }\n","sourceCodeStart":1141,"sourceCodeEnd":1177,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/interactive_brokers/src/common/parse.rs#L1141-L1177","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the source contract has >= 2 combo legs before constructing the spread instrument ID","Check that leg extraction from the IB BAG contract is not dropping legs (filters, leg type checks)","Guard the call site: skip single/zero-leg bags instead of attempting to build a spread ID"],"exampleFix":"// before\nlet id = create_spread_instrument_id(&legs)?;\n// after\nif legs.len() < 2 {\n    tracing::warn!(\"BAG contract has <2 legs, skipping\");\n    return Ok(None);\n}\nlet id = create_spread_instrument_id(&legs)?;","handlingStrategy":"validation","validationCode":"if legs.len() < 2 { return Err(anyhow::anyhow!(\"spread needs >= 2 legs\")); }","typeGuard":null,"tryCatchPattern":"match create_spread_instrument_id(&legs) {\n    Ok(id) => id,\n    Err(e) if e.to_string().contains(\"at least 2 legs\") => {\n        tracing::warn!(\"bag has insufficient legs ({})\", legs.len());\n        return Ok(None);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate combo leg count before constructing spread IDs","Log raw IB comboLegs when a BAG yields <2 legs to catch extraction bugs","Treat single-leg bags as invalid data and skip them"],"tags":["rust","options-spreads","validation","interactive-brokers"],"backgroundTag":"empty-required-field","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}