{"record":{"id":"26fadf91894d4b26","repo":"nautechsystems/nautilus_trader","slug":"inverse-position-has-no-base-currency","errorCode":null,"errorMessage":"inverse position {} has no base currency","messagePattern":"inverse position (.+?) has no base currency","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":1175,"sourceCode":"    fn calculate_return(&self, avg_px_open: f64, avg_px_close: f64) -> anyhow::Result<f64> {\n        // Prevent division by zero in return calculation\n        if avg_px_open == 0.0 {\n            anyhow::bail!(\n                \"Cannot calculate return: open price is zero (close price: {avg_px_close})\"\n            );\n        }\n        Ok(self.calculate_points(avg_px_open, avg_px_close) / avg_px_open)\n    }\n\n    fn calculate_pnl_raw(\n        &self,\n        avg_px_open: f64,\n        avg_px_close: f64,\n        quantity: f64,\n    ) -> anyhow::Result<f64> {\n        let quantity = quantity.min(self.signed_qty.abs());\n        let result = if self.is_inverse {\n            anyhow::ensure!(\n                self.base_currency.is_some(),\n                \"inverse position {} has no base currency\",\n                self.instrument_id\n            );\n            let points = self.calculate_points_inverse(avg_px_open, avg_px_close)?;\n            quantity * self.multiplier.as_f64() * points\n        } else {\n            quantity * self.multiplier.as_f64() * self.calculate_points(avg_px_open, avg_px_close)\n        };\n        Ok(result)\n    }\n\n    /// Calculates profit and loss from the given prices and quantity.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if inverse P&L cannot be calculated or the result cannot be represented as\n    /// [`Money`].","sourceCodeStart":1157,"sourceCodeEnd":1193,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L1157-L1193","documentation":"calculate_pnl_raw requires an inverse position to have a base_currency set, because inverse PnL is computed in the base currency via point valuation. A position constructed as inverse (is_inverse == true) with base_currency == None is internally inconsistent, so PnL calculation fails rather than returning a wrong value.","triggerScenarios":"Creating a Position for an inverse instrument (e.g. crypto-margined futures like BTCUSD inverses) without supplying base_currency, then calling try_calculate_pnl / unrealized or realized PnL paths (handle_buy_order_fill, handle_sell_order_fill).","commonSituations":"Building positions programmatically where the instrument's base currency was not resolved (e.g. synthetic or custom instruments); loading a position from a snapshot serialized before base_currency existed or with it omitted; configuring inverse instruments with only quote/settlement currency defined.","solutions":["Set base_currency on the position (or its instrument definition) for every inverse instrument before fill handling.","Use the instrument's defined base asset to populate base_currency when constructing the Position.","If the instrument is genuinely not inverse, construct the position with is_inverse = false so PnL uses the standard formula."],"exampleFix":"// before\nlet position = Position::new(instrument_id, /* ... */ is_inverse: true, base_currency: None);\n// after\nlet position = Position::new(\n    instrument_id,\n    /* ... */\n    is_inverse: true,\n    base_currency: Some(instrument.base_currency().expect(\"inverse instrument needs base\")),\n);","handlingStrategy":"validation","validationCode":"if position.is_inverse && position.base_currency.is_none() {\n    return Err(anyhow!(\"inverse position {} requires base currency\", position.instrument_id));\n}\nlet pnl = position.try_calculate_pnl(avg_px_open, avg_px_close, qty)?;","typeGuard":"fn pnl_ready(p: &Position) -> bool {\n    !p.is_inverse || p.base_currency.is_some()\n}","tryCatchPattern":"match position.try_calculate_pnl(open, close, qty) {\n    Ok(pnl) => /* use pnl */,\n    Err(e) if e.to_string().contains(\"no base currency\") =>\n        log::error!(\"configure base_currency for inverse position {}\", position.instrument_id),\n    Err(e) => return Err(e),\n}","preventionTips":["Always populate base_currency from the instrument definition for inverse instruments.","Validate instrument completeness (base, quote, settlement currencies) before creating positions.","Check snapshot schemas when upgrading so base_currency is not dropped."],"tags":["position","pnl","inverse-instrument","rust"],"backgroundTag":"missing-required-config-field","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}