nautechsystems/nautilus_trader · error · anyhow::Error

Cannot cache futures spread: strike prices differ call_instr

Error message

Cannot cache futures spread: strike prices differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}

What it means

cache_futures_spread enforces put-call parity, so both legs must share the same strike. When call_instrument.strike_price() differs from put_instrument.strike_price() the method bails, since combining different strikes would not yield a valid implied future price.

Source

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

        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 {
            anyhow::bail!(
                "Cannot cache futures spread: missing put underlying for {put_instrument_id}"
            );
        };

        if call_underlying != put_underlying {
            anyhow::bail!(
                "Cannot cache futures spread: option underlyings differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        if call_instrument.strike_price() != put_instrument.strike_price() {
            anyhow::bail!(
                "Cannot cache futures spread: strike prices differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        if call_instrument.expiration_ns() != put_instrument.expiration_ns() {
            anyhow::bail!(
                "Cannot cache futures spread: expiration dates differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        let reference_future_price = self.get_price_object(&futures_instrument_id).ok_or_else(|| {
            anyhow::anyhow!(
                "Cannot cache futures spread: no reference futures price for {futures_instrument_id}"
            )
        })?;
        let call_price = self.get_price(&call_instrument_id).ok_or_else(|| {
            anyhow::anyhow!(
                "Cannot cache futures spread: missing option price for {call_instrument_id}"

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Select the pair at the same strike (typically at-the-money) before calling cache_futures_spread.
  2. Compare strike_price() on both instruments before invoking and skip unequal pairs.
  3. Fix the strike parser if strikes were extracted incorrectly from instrument symbols.

Example fix

// before
let price = greeks.cache_futures_spread(call_6000_id, put_6050_id, future_id)?;
// after
if call.strike_price() != put.strike_price() {
    tracing::warn!("strike mismatch: {} vs {}", call.strike_price().unwrap(), put.strike_price().unwrap());
    return Ok(None);
}
let price = greeks.cache_futures_spread(call_6000_id, put_6000_id, future_id)?;
Defensive patterns

Strategy: validation

Validate before calling

// rust
fn same_strike(cache: &Cache, call_id: &InstrumentId, put_id: &InstrumentId) -> bool {
    cache.instrument(call_id).map(|i| i.strike_price()) == cache.instrument(put_id).map(|i| i.strike_price())
}

Type guard

fn strike_of(cache: &Cache, id: &InstrumentId) -> Option<Price> {
    cache.instrument(id).and_then(|i| i.strike_price())
}

Try / catch

let res = greeks.cache_futures_spread(call_id, put_id, future_id);
if let Err(e) = res {
    if e.to_string().contains("strike prices differ") {
        reselect_pair_at_same_strike();
    }
}

Prevention

When it happens

Trigger: Passing a call and put with different strikes (e.g. from a strangle rather than an at-the-money parity pair); mis-parsing strike from symbol strings (ES 6000 call vs ES 6050 put); rounding differences in strike parsing.

Common situations: Symbol-to-ID parsing bugs where the strike field is truncated or misread; pairing logic that matches by expiry only; feeds that quote strikes in different tick sizes.

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