nautechsystems/nautilus_trader · error

{model_name} requires an option instrument

Error message

{model_name} requires an option instrument

What it means

`check_option_instrument` guards option fee models by verifying the instrument is a CryptoOption or OptionContract. Any other instrument type (spot, futures, binary option, etc.) bails with '{model_name} requires an option instrument', since the model's fee math assumes option contracts.

Source

Thrown at crates/execution/src/models/fee.rs:658

        Some(LiquiditySide::NoLiquiditySide) | None => anyhow::bail!("Liquidity side not set"),
    };
    check_fee_rate(Some(rate), "fee_rate")?;
    Ok(rate)
}

fn check_fee_rate(rate: Option<Decimal>, name: &str) -> anyhow::Result<()> {
    if rate.is_some_and(|rate| rate < Decimal::ZERO) {
        anyhow::bail!("`{name}` must be greater than or equal to zero");
    }
    Ok(())
}

fn check_option_instrument(instrument: &InstrumentAny, model_name: &str) -> anyhow::Result<()> {
    if !matches!(
        instrument,
        InstrumentAny::CryptoOption(_) | InstrumentAny::OptionContract(_)
    ) {
        anyhow::bail!("{model_name} requires an option instrument");
    }
    Ok(())
}

fn commission_currency(instrument: &InstrumentAny) -> Currency {
    if instrument.is_inverse() {
        instrument.settlement_currency()
    } else {
        instrument.quote_currency()
    }
}

#[cfg(test)]
mod tests {
    use std::{cell::Cell, rc::Rc};

    use nautilus_model::{
        enums::{LiquiditySide, OrderSide, OrderType},

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Wire the option fee model only to CryptoOption/OptionContract instruments
  2. Dispatch fee models per instrument type when building the backtest/live config
  3. Add a startup validation pass that checks each fee model against its instruments

Example fix

// before
let fee = option_fee_model.get_commission(&order, qty, px, &fx_pair_instrument)?;
// after
if matches!(instrument, InstrumentAny::CryptoOption(_) | InstrumentAny::OptionContract(_)) {
    let fee = option_fee_model.get_commission(&order, qty, px, &instrument)?;
} else {
    let fee = maker_taker_model.get_commission(&order, qty, px, &instrument)?;
}
Defensive patterns

Strategy: validation

Validate before calling

if !matches!(instrument, InstrumentAny::CryptoOption(_) | InstrumentAny::OptionContract(_)) {
    // route to a non-option fee model
}

Type guard

fn is_option_instrument(i: &InstrumentAny) -> bool {
    matches!(i, InstrumentAny::CryptoOption(_) | InstrumentAny::OptionContract(_))
}

Try / catch

match option_model.get_commission(&order, qty, px, &instrument) {
    Ok(fee) => fee,
    Err(e) if e.to_string().contains("option instrument") => generic_model_fee(),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling get_commission/get_commission_with_context on an option fee model with an InstrumentAny that is not CryptoOption or OptionContract — e.g. CurrencyPair, CryptoPerpetual, FuturesContract, or BinaryOption.

Common situations: Assigning one fee model to a multi-instrument portfolio; misconfigured backtest where the instrument cache returns the wrong instrument; copy-pasted setup reused across markets.

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