{"record":{"id":"acb978e7034e6032","repo":"nautechsystems/nautilus_trader","slug":"cannot-calculate-inverse-points-open-price-is-not","errorCode":null,"errorMessage":"Cannot calculate inverse points: open price is not positive or is too small ({avg_px_open})","messagePattern":"Cannot calculate inverse points: open price is not positive or is too small \\((.+?)\\)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":1136,"sourceCode":"                log::error!(\"Error calculating average close price: {e}\");\n                last_px\n            })\n    }\n\n    fn calculate_points(&self, avg_px_open: f64, avg_px_close: f64) -> f64 {\n        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)","sourceCodeStart":1118,"sourceCodeEnd":1154,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L1118-L1154","documentation":"Position PnL calculation for inverse instruments converts prices via reciprocal (1/px), which is undefined or numerically garbage for non-positive or denormal prices. calculate_points_inverse bails when avg_px_open is <= 0 or smaller than 1e-15 (just above f64 epsilon) before it can divide by the open price.","triggerScenarios":"Calling position.calculate_pnl_raw (via calculate_points_inverse) with an inverse-instrument position whose stored avg_px_open is zero, negative, or below 1e-15 — typically from an uninitialized position, a fill recorded with px=0.0, or a bad data feed.","commonSituations":"Inverse contracts (e.g. BTCUSD coin-margined perps) fed zero prices from a stale/misconfigured market data source; positions built programmatically without setting an open price; data pipelines that null-coalesce missing prices to 0.0.","solutions":["Ensure fills and position open prices are strictly positive decimals before PnL calculation; validate at ingestion.","Check the market data source for zero/placeholder prices and filter them out or halt on them.","Guard the call site: only compute PnL when position.avg_px_open > 0.0.","If prices legitimately approach zero, reject or rescale them upstream; 1e-15 is the library's hard floor."],"exampleFix":"// before\nlet pnl = position.calculate_pnl_raw(instrument, avg_px_close).unwrap();\n// after\nif position.avg_px_open <= 0.0 {\n    log::warn!(\"skipping PnL: open price {} invalid\", position.avg_px_open);\n    return;\n}\nlet pnl = position.calculate_pnl_raw(instrument, avg_px_close).unwrap();","handlingStrategy":"validation","validationCode":"// Rust: guard before calling calculate_pnl_raw\nif position.avg_px_open <= 0.0 || position.avg_px_open.abs() < 1e-15 {\n    log::warn!(\"skip inverse PnL: invalid open price {}\", position.avg_px_open);\n    return Ok(0.0);\n}\nlet pnl = position.calculate_pnl_raw(&instrument, avg_px_close)?;","typeGuard":"fn valid_inverse_px(px: f64) -> bool { px > 0.0 && px.abs() >= 1e-15 }","tryCatchPattern":null,"preventionTips":["Validate fill and position prices are strictly positive at ingestion.","Never use 0.0 as a sentinel for missing prices; use Option.","Add unit tests for inverse PnL with boundary prices.","Monitor data feeds for zero-price events."],"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"}