{"record":{"id":"1e572bf0a7304213","repo":"nautechsystems/nautilus_trader","slug":"cannot-calculate-average-price-both-quantities-ar","errorCode":null,"errorMessage":"Cannot calculate average price: both quantities are zero","messagePattern":"Cannot calculate average price: both quantities are zero","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":1074,"sourceCode":"    /// - `last_qty` is zero (prevents division by zero).\n    /// - `total_qty` is zero or negative (arithmetic error).\n    fn calculate_avg_px(\n        &self,\n        qty: f64,\n        avg_pg: f64,\n        last_px: f64,\n        last_qty: f64,\n    ) -> anyhow::Result<f64> {\n        // Prices can be negative for options and spreads, so only quantities\n        // are checked for non-negativity here.\n        debug_assert!(\n            qty >= 0.0 && last_qty >= 0.0,\n            \"Invariant: average price calc requires non-negative quantities \\\n             (qty={qty}, last_qty={last_qty})\"\n        );\n\n        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}\"","sourceCodeStart":1056,"sourceCodeEnd":1092,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L1056-L1092","documentation":"Position::calculate_avg_px computes a weighted-average price from an existing quantity/avg price and a new fill; if both the existing quantity and the fill quantity are zero there is no denominator and no meaningful average, so it bails.","triggerScenarios":"Calling calculate_avg_px_open_px or calculate_avg_px_close_px on a position/flow where qty == 0.0 and last_qty == 0.0 — e.g. computing open/close average price for an empty position with an empty (zero-quantity) event.","commonSituations":"Querying average open price on a flat position that has never had a fill, or processing generated/placeholder order-filled events with zero quantity.","solutions":["Check that the position has at least one nonzero quantity (position.quantity != 0 or the event quantity > 0) before requesting average prices.","Return Option/None for avg px when the position is flat instead of forcing the calculation.","Inspect why a zero-quantity fill event reached the position — zero-qty fills usually should be filtered upstream."],"exampleFix":"// before\nlet avg = position.calculate_avg_px_open_px();\n// after\nlet avg = if position.quantity().as_f64() != 0.0 {\n    Some(position.calculate_avg_px_open_px())\n} else { None };","handlingStrategy":"validation","validationCode":"if qty == 0.0 && last_qty == 0.0 { return Ok(None); /* undefined average price */ }","typeGuard":null,"tryCatchPattern":"match std::panic::catch_unwind(|| position.calculate_avg_px_open_px()) { ... }\n// or preferably: check quantities before calling and return None for flat positions","preventionTips":["Return Option<Price> for averages on flat positions instead of forcing computation.","Drop zero-quantity fill events before position updates."],"tags":["position","average-price","zero-quantity"],"backgroundTag":"invalid-state-transition","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"}