{"record":{"id":"80c3b7dfebb1899c","repo":"nautechsystems/nautilus_trader","slug":"cannot-calculate-average-price-fill-quantity-is-z","errorCode":null,"errorMessage":"Cannot calculate average price: fill quantity is zero","messagePattern":"Cannot calculate average price: fill quantity is zero","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":1078,"sourceCode":"        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}\"\n            );\n        }\n\n        Ok((start_cost + event_cost) / total_qty)","sourceCodeStart":1060,"sourceCodeEnd":1096,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L1060-L1096","documentation":"Position::calculate_avg_px requires a nonzero fill quantity (last_qty) to blend into the average; when only the fill quantity is zero it bails, since there is no new price contribution to average and semantics would be ambiguous.","triggerScenarios":"Calling calculate_avg_px (via calculate_avg_px_open_px / calculate_avg_px_close_px) with a zero-quantity fill event while the existing quantity is nonzero.","commonSituations":"Processing canceled/partially-filled events misreported as zero-quantity fills, synthetic test events with qty 0, or fee-only adjustments modeled as zero-qty fills.","solutions":["Filter out order-filled events with zero quantity before applying them to the position.","When qty is zero you only need the existing avg price — read it directly instead of calling calculate_avg_px.","Fix the event source so real fills carry their actual quantity."],"exampleFix":"// before\nlet avg = position.calculate_avg_px_close_px();\n// after\nif last_event_qty() == 0.0 {\n    return position.avg_px_close(); // no fill to blend\n}\nlet avg = position.calculate_avg_px_close_px();","handlingStrategy":"validation","validationCode":"if last_qty == 0.0 { return Ok(existing_avg_px); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Filter zero-quantity fills at the event-ingestion layer.","Model fees/adjustments separately from quantity-bearing fills."],"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-14T05:17:10.506Z"}