{"record":{"id":"51ed1cc4e889e7e7","repo":"nautechsystems/nautilus_trader","slug":"total-quantity-unexpectedly-zero-or-negative-in-av","errorCode":null,"errorMessage":"Total quantity unexpectedly zero or negative in average price calculation: qty={qty}, last_qty={last_qty}, total_qty={total_qty}","messagePattern":"Total quantity unexpectedly zero or negative in average price calculation: qty=(.+?), last_qty=(.+?), total_qty=(.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":1091,"sourceCode":"        if qty == 0.0 && last_qty == 0.0 {\n            anyhow::bail!(\"Cannot calculate average price: both quantities are zero\");\n        }\n\n        if last_qty == 0.0 {\n            anyhow::bail!(\"Cannot calculate average price: fill quantity is zero\");\n        }\n\n        if qty == 0.0 {\n            return Ok(last_px);\n        }\n\n        let start_cost = avg_pg * qty;\n        let event_cost = last_px * last_qty;\n        let total_qty = qty + last_qty;\n\n        // Runtime check to prevent division by zero even in release builds\n        if total_qty <= 0.0 {\n            anyhow::bail!(\n                \"Total quantity unexpectedly zero or negative in average price calculation: qty={qty}, last_qty={last_qty}, total_qty={total_qty}\"\n            );\n        }\n\n        Ok((start_cost + event_cost) / total_qty)\n    }\n\n    fn calculate_avg_px_open_px(&self, last_px: f64, last_qty: f64) -> f64 {\n        self.calculate_avg_px(self.quantity.as_f64(), self.avg_px_open, last_px, last_qty)\n            .unwrap_or_else(|e| {\n                log::error!(\"Error calculating average open price: {e}\");\n                last_px\n            })\n    }\n\n    fn calculate_avg_px_close_px(&self, last_px: f64, last_qty: f64) -> f64 {\n        let Some(avg_px_close) = self.avg_px_close else {\n            return last_px;","sourceCodeStart":1073,"sourceCodeEnd":1109,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L1073-L1109","documentation":"A runtime guard in Position::calculate_avg_px: after the earlier zero checks, the summed total_qty must still be positive before division. Negative inputs that slipped past the non-negative invariant, or floating-point anomalies, would otherwise cause a division by zero or sign-inverted average price, so the method bails with all three quantities in the message.","triggerScenarios":"Calling calculate_avg_px_open_px / calculate_avg_px_close_px when qty + last_qty <= 0.0 — only possible with negative quantities that violate the debug_assert invariant, since both inputs are checked >= 0 in debug builds.","commonSituations":"Corrupted position state with negative quantities (e.g. from buggy external position reconciliation), NaN quantities propagating through arithmetic (NaN comparisons are false, bypassing both guards), or release-build paths where debug assertions are off.","solutions":["Inspect qty, last_qty and total_qty printed in the message to find which input is negative or NaN.","Validate quantities are finite and non-negative at the position/event boundary before applying fills.","Reconcile the position against venue state to repair corrupted quantity data."],"exampleFix":"// before\nlet avg = position.calculate_avg_px_open_px();\n// after\nlet (q, lq) = (position.quantity().as_f64(), event_qty.as_f64());\nif !q.is_finite() || !lq.is_finite() || q < 0.0 || lq < 0.0 {\n    anyhow::bail!(\"invalid quantities for avg px: qty={q}, last_qty={lq}\");\n}\nlet avg = position.calculate_avg_px_open_px();","handlingStrategy":"validation","validationCode":"fn valid_qty(q: f64) -> bool { q.is_finite() && q > 0.0 }","typeGuard":"fn is_valid_positive_finite(q: f64) -> bool {\n    q.is_finite() && q > 0.0\n}","tryCatchPattern":"match position.calculate_avg_px_open_px() {\n    Ok(px) => px,\n    Err(e) if e.to_string().contains(\"Total quantity unexpectedly\") => {\n        // reconcile/repair corrupted position state\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate fills are finite and positive before applying them to positions.","Periodically reconcile position quantities against venue data to catch corruption early."],"tags":["position","average-price","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"}