{"record":{"id":"209faab533df680a","repo":"nautechsystems/nautilus_trader","slug":"inverse-instrument-without-base-currency","errorCode":null,"errorMessage":"inverse instrument without base_currency","messagePattern":"inverse instrument without base_currency","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/instruments/mod.rs","lineNumber":238,"sourceCode":"        self.id().venue\n    }\n\n    fn raw_symbol(&self) -> Symbol;\n    fn asset_class(&self) -> AssetClass;\n    fn instrument_class(&self) -> InstrumentClass;\n\n    fn underlying(&self) -> Option<Ustr>;\n    fn base_currency(&self) -> Option<Currency>;\n    fn quote_currency(&self) -> Currency;\n    fn settlement_currency(&self) -> Currency;\n\n    /// # Panics\n    ///\n    /// Panics if the instrument is inverse and does not have a base currency.\n    fn cost_currency(&self) -> Currency {\n        if self.is_inverse() {\n            self.base_currency()\n                .expect(\"inverse instrument without base_currency\")\n        } else if self.is_quanto() {\n            self.settlement_currency()\n        } else {\n            self.quote_currency()\n        }\n    }\n\n    fn isin(&self) -> Option<Ustr>;\n    fn option_kind(&self) -> Option<OptionKind>;\n    fn exchange(&self) -> Option<Ustr>;\n    fn strike_price(&self) -> Option<Price>;\n    fn strategy_type(&self) -> Option<Ustr> {\n        None\n    }\n\n    fn activation_ns(&self) -> Option<UnixNanos>;\n    fn expiration_ns(&self) -> Option<UnixNanos>;\n    fn has_expiration(&self) -> bool {","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/instruments/mod.rs#L220-L256","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet inst = CryptoPerpetual::new(..., None, quote, settlement, ...);\nlet ccy = inst.cost_currency(); // panics (inverse, no base)\n// after\nlet inst = CryptoPerpetual::new(..., Some(currency_btc()), quote, settlement, ...);\nlet ccy = inst.cost_currency(); // BTC","handlingStrategy":"type-guard","validationCode":"// Rust caller\nif instrument.is_inverse() && instrument.base_currency().is_none() {\n    return Err(\"inverse instrument missing base currency\");\n}\nlet ccy = instrument.cost_currency();","typeGuard":"fn has_base_currency(inst: &dyn Instrument) -> bool {\n    !inst.is_inverse() || inst.base_currency().is_some()\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["rust","panic","instruments","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}