{"record":{"id":"5b22419a2a66e0fc","repo":"nautechsystems/nautilus_trader","slug":"closing-quantity-was-zero","errorCode":null,"errorMessage":"closing quantity was zero","messagePattern":"closing quantity was zero","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/events/order/filled.rs","lineNumber":168,"sourceCode":"    #[must_use]\n    pub fn is_sell(&self) -> bool {\n        self.order_side == OrderSide::Sell\n    }\n\n    /// Splits an overfill into the fragment which closes the current position and the\n    /// fragment which opens the flipped position.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error when `closing_qty` is zero, is not smaller than the fill quantity,\n    /// or the proportional commission cannot be represented.\n    pub fn split_for_position_flip(\n        &self,\n        closing_qty: Quantity,\n        opening_position_id: Option<PositionId>,\n        opening_event_id: UUID4,\n    ) -> anyhow::Result<(Self, Self)> {\n        anyhow::ensure!(!closing_qty.is_zero(), \"closing quantity was zero\");\n        anyhow::ensure!(\n            closing_qty.raw < self.last_qty.raw,\n            \"closing quantity {closing_qty} must be smaller than fill quantity {}\",\n            self.last_qty,\n        );\n\n        let opening_qty =\n            Quantity::from_raw(self.last_qty.raw - closing_qty.raw, closing_qty.precision);\n        let closing_fraction = closing_qty.as_decimal() / self.last_qty.as_decimal();\n        let (closing_commission, opening_commission) = match self.commission {\n            Some(commission) => {\n                let closing = Money::from_decimal(\n                    commission.as_decimal() * closing_fraction,\n                    commission.currency,\n                )?;\n                (Some(closing), Some(commission - closing))\n            }\n            None => (None, None),","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/events/order/filled.rs#L150-L186","documentation":"split_for_position_flip splits an overfilling OrderFilled into a closing fragment and an opening (flipped position) fragment. The closing quantity must be a positive amount strictly smaller than the fill quantity, otherwise the split is meaningless. This error is thrown when closing_qty is zero, i.e. the caller attempted a position flip with nothing to close — an internal arithmetic/logic error in the position-flip code path.","triggerScenarios":"Calling split_for_position_flip (directly or via flip_position / apply_orderless_flip_fill) with a Quantity whose raw value is 0 — typically because the computed close amount against the current position was zero.","commonSituations":"Calling flip_position when there is no open position to close (position already flat); a signed/unsigned conversion that truncated the closing quantity to zero; an order-size calculation bug where the overfill quantity was computed as fill minus open quantity incorrectly.","solutions":["Check the current position quantity before flipping; only call flip when the position is non-flat.","Verify the closing quantity computation (fill_qty - position_open_qty) uses the correct signed side logic.","Ensure the Quantity passed was constructed from the correct raw value and precision.","At the call site, skip the split entirely when closing_qty.is_zero() — nothing needs to close."],"exampleFix":"// before\nlet (closing, opening) = fill.split_for_position_flip(closing_qty, Some(open_id), event_id)?;\n// after\nif closing_qty.is_zero() {\n    // nothing to close: the fill entirely opens/extends the position\n    return apply_open_fill(fill);\n}\nlet (closing, opening) = fill.split_for_position_flip(closing_qty, Some(open_id), event_id)?;","handlingStrategy":"validation","validationCode":"if closing_qty.is_zero() {\n    // nothing to close; handle as a pure open/extend instead of a flip\n}\nif closing_qty.raw >= fill.last_qty.raw {\n    // not an overfill; handle as a normal fill application\n}","typeGuard":"fn is_valid_flip_split(fill_qty: Quantity, closing_qty: Quantity) -> bool {\n    !closing_qty.is_zero() && closing_qty.raw < fill_qty.raw\n}","tryCatchPattern":"match fill.split_for_position_flip(closing_qty, open_id, event_id) {\n    Ok((closing, opening)) => { /* apply both fragments */ }\n    Err(e) if e.to_string().contains(\"closing quantity was zero\") => {\n        // no position to close; apply the fill as open-only\n        apply_open_fill(fill)\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Check the position is non-flat before attempting a flip","Compute closing_qty as position_open_qty (signed-side aware), never from raw fill math that can zero it","Add a zero-quantity guard at the flip call site to take the open-only path"],"tags":["rust","orders","positions","validation"],"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"}