{"record":{"id":"2e00c0f10f86b7f7","repo":"nautechsystems/nautilus_trader","slug":"cannot-cache-futures-spread-missing-option-price","errorCode":null,"errorMessage":"Cannot cache futures spread: missing option price for {call_instrument_id}","messagePattern":"Cannot cache futures spread: missing option price for (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/common/src/greeks.rs","lineNumber":1122,"sourceCode":"        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}\"\n            );\n        }\n\n        if call_instrument.expiration_ns() != put_instrument.expiration_ns() {\n            anyhow::bail!(\n                \"Cannot cache futures spread: expiration dates differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}\"\n            );\n        }\n\n        let reference_future_price = self.get_price_object(&futures_instrument_id).ok_or_else(|| {\n            anyhow::anyhow!(\n                \"Cannot cache futures spread: no reference futures price for {futures_instrument_id}\"\n            )\n        })?;\n        let call_price = self.get_price(&call_instrument_id).ok_or_else(|| {\n            anyhow::anyhow!(\n                \"Cannot cache futures spread: missing option price for {call_instrument_id}\"\n            )\n        })?;\n        let put_price = self.get_price(&put_instrument_id).ok_or_else(|| {\n            anyhow::anyhow!(\n                \"Cannot cache futures spread: missing option price for {put_instrument_id}\"\n            )\n        })?;\n\n        let underlying_instrument_id =\n            InstrumentId::from(format!(\"{call_underlying}.{}\", call_instrument_id.venue));\n\n        // Reject if the underlying is present in cache but is not a future\n        {\n            let cache = self.cache.borrow();\n            if let Some(underlying) = cache.instrument(&underlying_instrument_id)\n                && underlying.instrument_class() != InstrumentClass::Future\n            {","sourceCodeStart":1104,"sourceCodeEnd":1140,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/common/src/greeks.rs#L1104-L1140","documentation":"After fetching the reference futures price, `cache_futures_spread` fetches the CALL option's price via `get_price`. If no price is cached for the call instrument the spread cannot be computed and it bails with this error.","triggerScenarios":"cache_futures_spread called when the call option instrument has no quote/trade price in the cache (options not subscribed, illiquid strike with no market, wrong call instrument ID, or call expired).","commonSituations":"Deep OTM/ITM strikes with no quotes; subscribing to a subset of the chain; passing swapped call/put IDs; running during the first milliseconds after subscription before quotes arrive.","solutions":["Subscribe to quotes/trades for the call instrument and confirm a price exists before caching the spread.","Verify call/put instrument IDs are not swapped and match the same expiration.","Pick a strike with active market data, or retry after the first quote tick.","Handle the error at call sites and skip the synthetic-price caching for strikes without data."],"exampleFix":"// before\nengine.cache_futures_spread(&fut_id, &call_id, &put_id, &underlying)?;\n// after\nif engine.cache.price(&call_id).is_none() {\n    return Ok(()); // skip strikes without market data\n}\nengine.cache_futures_spread(&fut_id, &call_id, &put_id, &underlying)?;","handlingStrategy":"fallback","validationCode":"// Rust: check call leg before computing the spread\nif cache.price(&call_instrument_id).is_none() {\n    return Ok(()); // skip strikes without call quotes\n}","typeGuard":"fn price_or_skip(cache: &Cache, id: &InstrumentId) -> Option<f64> { cache.price(id) }","tryCatchPattern":"match engine.cache_futures_spread(&fut_id, &call_id, &put_id, &u) {\n    Err(e) if e.to_string().contains(&call_id.to_string()) => debug!(\"no call price {call_id}\"),\n    other => other?,\n}","preventionTips":["Subscribe to quotes for every strike you intend to use.","Filter strikes to those with live two-sided quotes before spread caching.","Validate call/put IDs come from the same expiry to avoid leg mismatches.","Retry after the next quote tick; first-tick races are common."],"tags":["rust","options","missing-price","cache"],"backgroundTag":"resource-not-found","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"}