{"record":{"id":"97fd7bc1e737f58f","repo":"nautechsystems/nautilus_trader","slug":"cannot-calculate-return-open-price-is-zero-close","errorCode":null,"errorMessage":"Cannot calculate return: open price is zero (close price: {avg_px_close})","messagePattern":"Cannot calculate return: open price is zero \\(close price: (.+?)\\)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":1160,"sourceCode":"            anyhow::bail!(\n                \"Cannot calculate inverse points: close price is not positive or is too small ({avg_px_close})\"\n            );\n        }\n\n        let inverse_open = 1.0 / avg_px_open;\n        let inverse_close = 1.0 / avg_px_close;\n        let result = match self.side {\n            PositionSide::Long => inverse_open - inverse_close,\n            PositionSide::Short => inverse_close - inverse_open,\n            PositionSide::Flat => 0.0,\n        };\n        Ok(result)\n    }\n\n    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","sourceCodeStart":1142,"sourceCodeEnd":1178,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L1142-L1178","documentation":"calculate_return computes (points between open and close) / avg_px_open, so a zero open price would divide by zero. The method explicitly bails with this message when avg_px_open == 0.0, including the close price in the message for debugging.","triggerScenarios":"handle_buy_order_fill or handle_sell_order_fill on a Position whose avg_px_open is 0.0 — i.e. a fill processed before the position's open price was initialized, or a position constructed with a zero entry price.","commonSituations":"Flat-position accounting bugs where return is computed for a position that never opened; zero-price fills from a broken venue adapter; deserialized positions missing their open price.","solutions":["Ensure the first fill sets avg_px_open correctly before return/PnL is calculated.","Check the position lifecycle: only calculate_return on positions that actually opened.","Validate fill prices > 0 at the strategy/adapter boundary.","Log and inspect the close price in the message to trace where the zero originated."],"exampleFix":"// before\nlet ret = position.calculate_return(avg_px_open, avg_px_close).unwrap();\n// after\nlet ret = if avg_px_open != 0.0 { position.calculate_return(avg_px_open, avg_px_close).unwrap() } else { 0.0 };","handlingStrategy":"validation","validationCode":"// Rust: skip return calc for positions that never opened\nif avg_px_open == 0.0 {\n    log::debug!(\"position not opened; return = 0\");\n    return Ok(0.0);\n}","typeGuard":null,"tryCatchPattern":"let ret = position.calculate_return(avg_px_open, avg_px_close)\n    .unwrap_or_else(|e| { log::warn!(\"return calc failed: {e}\"); 0.0 });","preventionTips":["Enforce position lifecycle ordering: open fill before PnL/return.","Reject zero-price fills at the strategy boundary.","Check adapter fill-price mapping when migrating venues.","Add an invariant check that every filled position has avg_px_open > 0."],"tags":["rust","position","return","division-by-zero"],"backgroundTag":"invalid-argument-value","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"}