nautechsystems/nautilus_trader · error · anyhow::Error

Cannot calculate average price: both quantities are zero

Error message

Cannot calculate average price: both quantities are zero

What it means

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.

Source

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

    /// - `last_qty` is zero (prevents division by zero).
    /// - `total_qty` is zero or negative (arithmetic error).
    fn calculate_avg_px(
        &self,
        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}"

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check that the position has at least one nonzero quantity (position.quantity != 0 or the event quantity > 0) before requesting average prices.
  2. Return Option/None for avg px when the position is flat instead of forcing the calculation.
  3. Inspect why a zero-quantity fill event reached the position — zero-qty fills usually should be filtered upstream.

Example fix

// before
let avg = position.calculate_avg_px_open_px();
// after
let avg = if position.quantity().as_f64() != 0.0 {
    Some(position.calculate_avg_px_open_px())
} else { None };
Defensive patterns

Strategy: validation

Validate before calling

if qty == 0.0 && last_qty == 0.0 { return Ok(None); /* undefined average price */ }

Try / catch

match std::panic::catch_unwind(|| position.calculate_avg_px_open_px()) { ... }
// or preferably: check quantities before calling and return None for flat positions

Prevention

When it happens

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

Common situations: Querying average open price on a flat position that has never had a fill, or processing generated/placeholder order-filled events with zero quantity.

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/1e572bf0a7304213. Report an issue: GitHub.