nautechsystems/nautilus_trader · error

Underflow occurred when subtracting `Quantity`

Error message

Underflow occurred when subtracting `Quantity`

What it means

Quantity implements Sub via checked_sub on raw fixed-point values; a None result (result negative or below the representable minimum) makes the expect panic. Since quantities are unsigned in normal builds, subtracting a larger Quantity from a smaller one is the primary cause.

Source

Thrown at crates/model/src/types/quantity.rs:732

impl<'a> Sum<&'a Self> for Quantity {
    fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
        iter.copied().sum()
    }
}

impl Sub for Quantity {
    type Output = Self;
    fn sub(self, rhs: Self) -> Self::Output {
        assert!(
            raw_scales_match(self.precision, rhs.precision),
            "Cannot subtract `Quantity` values with mismatched decimal scales"
        );
        Self {
            raw: self
                .raw
                .checked_sub(rhs.raw)
                .expect("Underflow occurred when subtracting `Quantity`"),
            precision: self.precision.max(rhs.precision),
        }
    }
}

impl Mul for Quantity {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self::Output {
        let result_raw = if self.raw != QUANTITY_UNDEF
            && rhs.raw != QUANTITY_UNDEF
            && self.precision <= FIXED_PRECISION
            && rhs.precision <= FIXED_PRECISION
        {
            checked_mul_div_fixed(self.raw, rhs.raw)
        } else {
            let scalar = QuantityRaw::try_from(raw_scale(self.precision.min(rhs.precision)))
                .expect("Fixed-point scale fits QuantityRaw");
            checked_mul_div_raw(self.raw, rhs.raw, scalar)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Guard before subtracting: only subtract when self.raw >= rhs.raw (or self >= rhs via comparison), otherwise clamp to zero or reject.
  2. Track available quantity and validate the decrement against it before applying (standard position-management check).
  3. Compute deltas in Decimal (which supports negatives) when a signed remainder is meaningful.
  4. Make fill/adjustment events idempotent (dedupe by trade ID) to avoid double subtraction.

Example fix

// before
let remaining = filled - cancel_qty; // panics when cancel_qty > filled
// after
let remaining = if filled >= cancel_qty { filled - cancel_qty } else { Quantity::zero(filled.precision) };
Defensive patterns

Strategy: validation

Validate before calling

// Rust
fn checked_quantity_sub(a: Quantity, b: Quantity) -> Option<Quantity> {
    (a.precision == b.precision && a.raw >= b.raw)
        .then(|| Quantity { raw: a.raw - b.raw, precision: a.precision })
}

Try / catch

// Check size before decrement:
let remaining = if position_qty >= reduce_qty {
    position_qty - reduce_qty
} else {
    return Err("insufficient quantity to reduce");
};

Prevention

When it happens

Trigger: Using - on Quantity values with matching precision where lhs.raw < rhs.raw (would go negative), e.g. reducing a position by more than its size.

Common situations: Position/order quantity decrements without sufficient-size checks; double-subtraction from retries or duplicated fill events; fee/quantity bookkeeping bugs that over-deduct.

Related errors


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