nautechsystems/nautilus_trader · error

inverse instrument without base_currency

Error message

inverse instrument without base_currency

What it means

The `Instrument` trait's `cost_currency` implementation panics when the instrument is inverse (is_inverse() == true) but `base_currency()` returns None. Inverse instruments derive their cost currency from the base currency, so an inverse instrument missing a base currency is an internally inconsistent configuration. This happens when an instrument is built (e.g. via CryptoPerpetual with base_currency None) and then used in cost/notional calculations.

Source

Thrown at crates/model/src/instruments/mod.rs:238

        self.id().venue
    }

    fn raw_symbol(&self) -> Symbol;
    fn asset_class(&self) -> AssetClass;
    fn instrument_class(&self) -> InstrumentClass;

    fn underlying(&self) -> Option<Ustr>;
    fn base_currency(&self) -> Option<Currency>;
    fn quote_currency(&self) -> Currency;
    fn settlement_currency(&self) -> Currency;

    /// # Panics
    ///
    /// Panics if the instrument is inverse and does not have a base currency.
    fn cost_currency(&self) -> Currency {
        if self.is_inverse() {
            self.base_currency()
                .expect("inverse instrument without base_currency")
        } else if self.is_quanto() {
            self.settlement_currency()
        } else {
            self.quote_currency()
        }
    }

    fn isin(&self) -> Option<Ustr>;
    fn option_kind(&self) -> Option<OptionKind>;
    fn exchange(&self) -> Option<Ustr>;
    fn strike_price(&self) -> Option<Price>;
    fn strategy_type(&self) -> Option<Ustr> {
        None
    }

    fn activation_ns(&self) -> Option<UnixNanos>;
    fn expiration_ns(&self) -> Option<UnixNanos>;
    fn has_expiration(&self) -> bool {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set a base currency when constructing the inverse instrument (e.g. BTC for BTCUSD inverse perpetual).
  2. Verify the adapter/deserializer that produced the instrument actually populates base_currency.
  3. Guard the calculation site: check `instrument.base_currency().is_some()` for inverse instruments before computing cost.
  4. Use a non-inverse instrument type if base currency is genuinely not applicable.

Example fix

// before
let inst = CryptoPerpetual::new(..., None, quote, settlement, ...);
let ccy = inst.cost_currency(); // panics (inverse, no base)
// after
let inst = CryptoPerpetual::new(..., Some(currency_btc()), quote, settlement, ...);
let ccy = inst.cost_currency(); // BTC
Defensive patterns

Strategy: type-guard

Validate before calling

// Rust caller
if instrument.is_inverse() && instrument.base_currency().is_none() {
    return Err("inverse instrument missing base currency");
}
let ccy = instrument.cost_currency();

Type guard

fn has_base_currency(inst: &dyn Instrument) -> bool {
    !inst.is_inverse() || inst.base_currency().is_some()
}

Prevention

When it happens

Trigger: Calling cost_currency() (directly or via notional/PnL computations) on an instrument where is_inverse() is true and the instrument was constructed without a base currency, e.g. a CryptoPerpetual whose base currency was never set.

Common situations: Loading instrument definitions from an exchange adapter that leaves base currency null for inverse perpetuals, hand-constructed instrument objects in tests, or deserialization that dropped the base_currency field.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/209faab533df680a. Report an issue: GitHub.