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
- Set a base currency when constructing the inverse instrument (e.g. BTC for BTCUSD inverse perpetual).
- Verify the adapter/deserializer that produced the instrument actually populates base_currency.
- Guard the calculation site: check `instrument.base_currency().is_some()` for inverse instruments before computing cost.
- 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
- Validate instrument definitions (all currencies populated) right after loading from an adapter.
- Add a startup integrity check that every inverse instrument has a base currency.
- Never hand-construct inverse instruments in tests without full currency fields.
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
- instrument update lock poisoned
- Close command should not be drained
- Flush command should not be drained
- Order invariant violated: first event must be OrderInitializ
- API key is valid UTF-8
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/209faab533df680a.
Report an issue: GitHub.