nautechsystems/nautilus_trader · error · anyhow::Error

Cannot calculate average price: fill quantity is zero

Error message

Cannot calculate average price: fill quantity is zero

What it means

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.

Source

Thrown at crates/model/src/position.rs:1078

        qty: f64,
        avg_pg: f64,
        last_px: f64,
        last_qty: f64,
    ) -> anyhow::Result<f64> {
        // Prices can be negative for options and spreads, so only quantities
        // are checked for non-negativity here.
        debug_assert!(
            qty >= 0.0 && last_qty >= 0.0,
            "Invariant: average price calc requires non-negative quantities \
             (qty={qty}, last_qty={last_qty})"
        );

        if qty == 0.0 && last_qty == 0.0 {
            anyhow::bail!("Cannot calculate average price: both quantities are zero");
        }

        if last_qty == 0.0 {
            anyhow::bail!("Cannot calculate average price: fill quantity is zero");
        }

        if qty == 0.0 {
            return Ok(last_px);
        }

        let start_cost = avg_pg * qty;
        let event_cost = last_px * last_qty;
        let total_qty = qty + last_qty;

        // Runtime check to prevent division by zero even in release builds
        if total_qty <= 0.0 {
            anyhow::bail!(
                "Total quantity unexpectedly zero or negative in average price calculation: qty={qty}, last_qty={last_qty}, total_qty={total_qty}"
            );
        }

        Ok((start_cost + event_cost) / total_qty)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter out order-filled events with zero quantity before applying them to the position.
  2. When qty is zero you only need the existing avg price — read it directly instead of calling calculate_avg_px.
  3. Fix the event source so real fills carry their actual quantity.

Example fix

// before
let avg = position.calculate_avg_px_close_px();
// after
if last_event_qty() == 0.0 {
    return position.avg_px_close(); // no fill to blend
}
let avg = position.calculate_avg_px_close_px();
Defensive patterns

Strategy: validation

Validate before calling

if last_qty == 0.0 { return Ok(existing_avg_px); }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/80c3b7dfebb1899c. Report an issue: GitHub.