{"record":{"id":"ab2d3b02418b391c","repo":"nautechsystems/nautilus_trader","slug":"cannot-cache-futures-spread-non-option-instrument","errorCode":null,"errorMessage":"Cannot cache futures spread: non-option instruments provided call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}","messagePattern":"Cannot cache futures spread: non-option instruments provided call_instrument_id=(.+?) put_instrument_id=(.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/common/src/greeks.rs","lineNumber":1074,"sourceCode":"            anyhow::bail!(\n                \"Cannot cache futures spread: missing option instrument {call_instrument_id}\"\n            );\n        };\n        let Some(put_instrument) = put_instrument else {\n            anyhow::bail!(\n                \"Cannot cache futures spread: missing option instrument {put_instrument_id}\"\n            );\n        };\n        let Some(reference_future_instrument) = reference_future_instrument else {\n            anyhow::bail!(\n                \"Cannot cache futures spread: no reference futures instrument for {futures_instrument_id}\"\n            );\n        };\n\n        if call_instrument.instrument_class() != InstrumentClass::Option\n            || put_instrument.instrument_class() != InstrumentClass::Option\n        {\n            anyhow::bail!(\n                \"Cannot cache futures spread: non-option instruments provided call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}\"\n            );\n        }\n\n        if call_instrument.option_kind() != Some(OptionKind::Call)\n            || put_instrument.option_kind() != Some(OptionKind::Put)\n        {\n            anyhow::bail!(\n                \"Cannot cache futures spread: expected call/put pair call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}\"\n            );\n        }\n\n        let Some(call_underlying) = call_instrument.underlying() else {\n            anyhow::bail!(\n                \"Cannot cache futures spread: missing call underlying for {call_instrument_id}\"\n            );\n        };\n        let Some(put_underlying) = put_instrument.underlying() else {","sourceCodeStart":1056,"sourceCodeEnd":1092,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/common/src/greeks.rs#L1056-L1092","documentation":"cache_futures_spread validates that both provided instruments are of InstrumentClass::Option after resolving them from the Cache. If either the call or put argument resolves to a non-option instrument (e.g. a future, spot, or spread instrument), it bails with this error. The method only computes an implied-future spread from an option pair, so any other class is rejected up front.","triggerScenarios":"Passing a futures or cash instrument ID in either call_instrument_id or put_instrument_id; swapping arguments so a future lands in the call slot; a cached instrument whose class differs from what the caller assumed (e.g. a generic 'spread' or 'continued' contract).","commonSituations":"Building IDs programmatically from a symbol table where option and future IDs share a prefix; feeding the reference future ID into the call/put slots by mistake; vendor data classifying an instrument differently than expected.","solutions":["Check instrument_class() == InstrumentClass::Option for both IDs before calling cache_futures_spread.","Verify argument order: call_instrument_id and put_instrument_id must both be options; the future goes in the third parameter.","Inspect the cached instrument for the offending ID to confirm its actual class and correct the ID source."],"exampleFix":"// before\nlet price = greeks.cache_futures_spread(future_id, put_id, future_id)?;\n// after\nassert_eq!(cache.instrument(&call_id).unwrap().instrument_class(), InstrumentClass::Option);\nassert_eq!(cache.instrument(&put_id).unwrap().instrument_class(), InstrumentClass::Option);\nlet price = greeks.cache_futures_spread(call_id, put_id, future_id)?;","handlingStrategy":"validation","validationCode":"// rust\nfn is_option_pair(cache: &Cache, call_id: &InstrumentId, put_id: &InstrumentId) -> bool {\n    matches!(\n        (cache.instrument(call_id), cache.instrument(put_id)),\n        (Some(c), Some(p)) if c.instrument_class() == InstrumentClass::Option && p.instrument_class() == InstrumentClass::Option\n    )\n}","typeGuard":"fn as_option(cache: &Cache, id: &InstrumentId) -> Option<&InstrumentAny> {\n    cache.instrument(id).filter(|i| i.instrument_class() == InstrumentClass::Option)\n}","tryCatchPattern":"let res = greeks.cache_futures_spread(call_id, put_id, future_id);\nif let Err(e) = &res {\n    if e.to_string().contains(\"non-option instruments\") {\n        tracing::warn!(\"bad pair {call_id}/{put_id}: {e}\");\n    }\n}","preventionTips":["Keep option IDs and futures IDs in separate typed collections so they cannot be passed in the wrong slots.","Assert instrument classes once when loading instruments into cache, not per call.","Never reuse a single ID variable for both the option legs and the reference future."],"tags":["rust","validation","instrument-class","options"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}