{"record":{"id":"c453eaea68aabe94","repo":"nautechsystems/nautilus_trader","slug":"cannot-calculate-inverse-points-close-price-is-no","errorCode":null,"errorMessage":"Cannot calculate inverse points: close price is not positive or is too small ({avg_px_close})","messagePattern":"Cannot calculate inverse points: close price is not positive or is too small \\((.+?)\\)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":1142,"sourceCode":"        match self.side {\n            PositionSide::Long => avg_px_close - avg_px_open,\n            PositionSide::Short => avg_px_open - avg_px_close,\n            PositionSide::Flat => 0.0,\n        }\n    }\n\n    fn calculate_points_inverse(&self, avg_px_open: f64, avg_px_close: f64) -> anyhow::Result<f64> {\n        // Epsilon at the limit of IEEE f64 precision before rounding errors (f64::EPSILON ≈ 2.22e-16)\n        const EPSILON: f64 = 1e-15;\n\n        if avg_px_open <= 0.0 || avg_px_open.abs() < EPSILON {\n            anyhow::bail!(\n                \"Cannot calculate inverse points: open price is not positive or is too small ({avg_px_open})\"\n            );\n        }\n\n        if avg_px_close <= 0.0 || avg_px_close.abs() < EPSILON {\n            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!(","sourceCodeStart":1124,"sourceCodeEnd":1160,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L1124-L1160","documentation":"The inverse-points PnL formula requires both open and close prices to be strictly positive (and at least 1e-15) because it takes reciprocals of each. calculate_points_inverse bails when avg_px_close is zero, negative, or below the epsilon floor.","triggerScenarios":"calculate_pnl_raw invoked with avg_px_close <= 0.0 or |avg_px_close| < 1e-15 on an inverse instrument — e.g. closing a position using a fill price of 0.0 or an unset close price.","commonSituations":"Downstream code passing 0.0 as the close price when the position is not yet closed; backtests with malformed tick data; adapters emitting 0.0 instead of Option::None for missing close prices.","solutions":["Verify the close/fill price passed to calculate_pnl_raw is a valid positive market price.","Use Option/None semantics for 'not closed' instead of 0.0 sentinel values.","Add ingestion-time validation rejecting non-positive prices in market data.","Confirm you are not passing quantity or PnL where a price is expected."],"exampleFix":"// before\nlet pnl = position.calculate_pnl_raw(&instrument, 0.0).unwrap();\n// after\nlet pnl = position.calculate_pnl_raw(&instrument, last_close_price).unwrap(); // last_close_price > 0.0 verified","handlingStrategy":"validation","validationCode":"// Rust: verify the close price before PnL\nassert!(avg_px_close > 0.0 && avg_px_close.abs() >= 1e-15, \"invalid close price {avg_px_close}\");\nlet pnl = position.calculate_pnl_raw(&instrument, avg_px_close)?;","typeGuard":"fn valid_close_px(px: f64) -> bool { px > 0.0 && px.abs() >= 1e-15 }","tryCatchPattern":"match position.calculate_pnl_raw(&instrument, avg_px_close) {\n    Ok(pnl) => pnl,\n    Err(e) => { log::error!(\"pnl failed: {e}\"); 0.0 },\n}","preventionTips":["Only compute PnL for positions with a real closing fill.","Filter malformed ticks/quotes before they become fill prices.","Keep close-price state as Option until filled.","Test adapters against venues that emit 0.0 placeholders."],"tags":["rust","position","pnl","inverse-instrument","numeric"],"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"}