{"record":{"id":"bd3f622742621821","repo":"nautechsystems/nautilus_trader","slug":"cannot-cache-futures-spread-missing-call-underlyi","errorCode":null,"errorMessage":"Cannot cache futures spread: missing call underlying for {call_instrument_id}","messagePattern":"Cannot cache futures spread: missing call underlying for (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/common/src/greeks.rs","lineNumber":1088,"sourceCode":"\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 {\n            anyhow::bail!(\n                \"Cannot cache futures spread: missing put underlying for {put_instrument_id}\"\n            );\n        };\n\n        if call_underlying != put_underlying {\n            anyhow::bail!(\n                \"Cannot cache futures spread: option underlyings differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}\"\n            );\n        }\n\n        if call_instrument.strike_price() != put_instrument.strike_price() {\n            anyhow::bail!(\n                \"Cannot cache futures spread: strike prices differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}\"","sourceCodeStart":1070,"sourceCodeEnd":1106,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/common/src/greeks.rs#L1070-L1106","documentation":"cache_futures_spread reads the call option's underlying symbol to build the underlying futures instrument ID. If the call instrument has no underlying set (underlying() returns None), the method cannot construct the underlying ID and bails with this error naming the call instrument.","triggerScenarios":"Calling cache_futures_spread with a call option whose Instrument was constructed without an underlying field — typically a hand-built test fixture or an adapter that omits underlying for options.","commonSituations":"Custom/dummy instrument definitions in tests missing the underlying; provider adapters for equity options where underlying is implied and left unset; manually parsed instrument definitions.","solutions":["Set the underlying field on the call instrument definition before adding it to the Cache.","Check the adapter/loader that produced the InstrumentAny and ensure it maps the provider's underlying symbol into Instrument::underlying.","Guard the call site: verify cache.instrument(&call_id).unwrap().underlying().is_some() before invoking."],"exampleFix":"// before\nInstrument::new(... /* underlying omitted */ ...)\n// after\nInstrument::new(..., Some(underlying_symbol), ...) // e.g. Some(\"ES\") for ES options","handlingStrategy":"validation","validationCode":"// rust\nfn has_underlying(cache: &Cache, id: &InstrumentId) -> bool {\n    cache.instrument(id).and_then(|i| i.underlying()).is_some()\n}\nassert!(has_underlying(&cache, &call_id), \"call {call_id} missing underlying\");","typeGuard":"fn underlying_of(cache: &Cache, id: &InstrumentId) -> Option<Ustr> {\n    cache.instrument(id).and_then(|i| i.underlying())\n}","tryCatchPattern":"match greeks.cache_futures_spread(call_id, put_id, future_id) {\n    Ok(p) => use(p),\n    Err(e) if e.to_string().contains(\"missing call underlying\") => fix_instrument_definition(call_id),\n    Err(e) => return Err(e),\n}","preventionTips":["Validate instrument definitions (underlying present) at load time before adding to cache.","Use provider adapters that map underlying symbols rather than hand-built fixtures in production paths.","Add a startup sanity pass that rejects option instruments without underlying."],"tags":["rust","instrument","underlying","options"],"backgroundTag":"missing-required-argument","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"}