nautechsystems/nautilus_trader · error
closing quantity was zero
Error message
closing quantity was zero
What it means
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.
Source
Thrown at crates/model/src/events/order/filled.rs:168
#[must_use]
pub fn is_sell(&self) -> bool {
self.order_side == OrderSide::Sell
}
/// Splits an overfill into the fragment which closes the current position and the
/// fragment which opens the flipped position.
///
/// # Errors
///
/// Returns an error when `closing_qty` is zero, is not smaller than the fill quantity,
/// or the proportional commission cannot be represented.
pub fn split_for_position_flip(
&self,
closing_qty: Quantity,
opening_position_id: Option<PositionId>,
opening_event_id: UUID4,
) -> anyhow::Result<(Self, Self)> {
anyhow::ensure!(!closing_qty.is_zero(), "closing quantity was zero");
anyhow::ensure!(
closing_qty.raw < self.last_qty.raw,
"closing quantity {closing_qty} must be smaller than fill quantity {}",
self.last_qty,
);
let opening_qty =
Quantity::from_raw(self.last_qty.raw - closing_qty.raw, closing_qty.precision);
let closing_fraction = closing_qty.as_decimal() / self.last_qty.as_decimal();
let (closing_commission, opening_commission) = match self.commission {
Some(commission) => {
let closing = Money::from_decimal(
commission.as_decimal() * closing_fraction,
commission.currency,
)?;
(Some(closing), Some(commission - closing))
}
None => (None, None),View on GitHub (pinned to 18893faf8b)
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.
Example fix
// before
let (closing, opening) = fill.split_for_position_flip(closing_qty, Some(open_id), event_id)?;
// after
if closing_qty.is_zero() {
// nothing to close: the fill entirely opens/extends the position
return apply_open_fill(fill);
}
let (closing, opening) = fill.split_for_position_flip(closing_qty, Some(open_id), event_id)?; Defensive patterns
Strategy: validation
Validate before calling
if closing_qty.is_zero() {
// nothing to close; handle as a pure open/extend instead of a flip
}
if closing_qty.raw >= fill.last_qty.raw {
// not an overfill; handle as a normal fill application
} Type guard
fn is_valid_flip_split(fill_qty: Quantity, closing_qty: Quantity) -> bool {
!closing_qty.is_zero() && closing_qty.raw < fill_qty.raw
} Try / catch
match fill.split_for_position_flip(closing_qty, open_id, event_id) {
Ok((closing, opening)) => { /* apply both fragments */ }
Err(e) if e.to_string().contains("closing quantity was zero") => {
// no position to close; apply the fill as open-only
apply_open_fill(fill)
}
Err(e) => return Err(e),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/5b22419a2a66e0fc.
Report an issue: GitHub.