nautechsystems/nautilus_trader · error · anyhow::Error

Cannot cache futures spread: non-option instruments provided

Error message

Cannot cache futures spread: non-option instruments provided call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}

What it means

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.

Source

Thrown at crates/common/src/greeks.rs:1074

            anyhow::bail!(
                "Cannot cache futures spread: missing option instrument {call_instrument_id}"
            );
        };
        let Some(put_instrument) = put_instrument else {
            anyhow::bail!(
                "Cannot cache futures spread: missing option instrument {put_instrument_id}"
            );
        };
        let Some(reference_future_instrument) = reference_future_instrument else {
            anyhow::bail!(
                "Cannot cache futures spread: no reference futures instrument for {futures_instrument_id}"
            );
        };

        if call_instrument.instrument_class() != InstrumentClass::Option
            || put_instrument.instrument_class() != InstrumentClass::Option
        {
            anyhow::bail!(
                "Cannot cache futures spread: non-option instruments provided call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        if call_instrument.option_kind() != Some(OptionKind::Call)
            || put_instrument.option_kind() != Some(OptionKind::Put)
        {
            anyhow::bail!(
                "Cannot cache futures spread: expected call/put pair call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        let Some(call_underlying) = call_instrument.underlying() else {
            anyhow::bail!(
                "Cannot cache futures spread: missing call underlying for {call_instrument_id}"
            );
        };
        let Some(put_underlying) = put_instrument.underlying() else {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check instrument_class() == InstrumentClass::Option for both IDs before calling cache_futures_spread.
  2. Verify argument order: call_instrument_id and put_instrument_id must both be options; the future goes in the third parameter.
  3. Inspect the cached instrument for the offending ID to confirm its actual class and correct the ID source.

Example fix

// before
let price = greeks.cache_futures_spread(future_id, put_id, future_id)?;
// after
assert_eq!(cache.instrument(&call_id).unwrap().instrument_class(), InstrumentClass::Option);
assert_eq!(cache.instrument(&put_id).unwrap().instrument_class(), InstrumentClass::Option);
let price = greeks.cache_futures_spread(call_id, put_id, future_id)?;
Defensive patterns

Strategy: validation

Validate before calling

// rust
fn is_option_pair(cache: &Cache, call_id: &InstrumentId, put_id: &InstrumentId) -> bool {
    matches!(
        (cache.instrument(call_id), cache.instrument(put_id)),
        (Some(c), Some(p)) if c.instrument_class() == InstrumentClass::Option && p.instrument_class() == InstrumentClass::Option
    )
}

Type guard

fn as_option(cache: &Cache, id: &InstrumentId) -> Option<&InstrumentAny> {
    cache.instrument(id).filter(|i| i.instrument_class() == InstrumentClass::Option)
}

Try / catch

let res = greeks.cache_futures_spread(call_id, put_id, future_id);
if let Err(e) = &res {
    if e.to_string().contains("non-option instruments") {
        tracing::warn!("bad pair {call_id}/{put_id}: {e}");
    }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

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