nautechsystems/nautilus_trader · error

Underflow occurred when subtracting `Price`

Error message

Underflow occurred when subtracting `Price`

What it means

Price implements Sub by checked_sub on raw fixed-point integers; if the result is below the representable range (most commonly a negative result, since Price is unsigned in normal builds), checked_sub returns None and this expect panics. Subtracting a larger Price from a smaller one is the typical cause.

Source

Thrown at crates/model/src/types/price.rs:700

                .checked_add(rhs.raw)
                .expect("Overflow occurred when adding `Price`"),
            precision: self.precision.max(rhs.precision),
        }
    }
}

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

impl Add<Decimal> for Price {
    type Output = Decimal;
    fn add(self, rhs: Decimal) -> Self::Output {
        self.as_decimal() + rhs
    }
}

impl Sub<Decimal> for Price {
    type Output = Decimal;
    fn sub(self, rhs: Decimal) -> Self::Output {
        self.as_decimal() - rhs
    }
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Order operands: compute (max - min) or take .abs() semantics explicitly via Decimal before constructing a Price.
  2. Compute differences in Decimal (price.as_decimal()) which supports negatives, and only build a Price from non-negative results.
  3. Guard with a comparison (if a >= b { a - b } else { ... }) before using the - operator.
  4. For signed deltas, use a dedicated signed type rather than Price.

Example fix

// before
let spread = ask - bid; // panics when bid > ask (negative spread)
// after
let spread = if ask >= bid { ask - bid } else { (bid - ask).neg_price() /* or track sign separately */ };
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

// Compare before subtracting:
let spread = if ask >= bid { ask - bid } else { Decimal::ZERO }; // or track sign via Decimal

Prevention

When it happens

Trigger: Using the - operator (impl Sub for Price) where lhs.raw < rhs.raw (negative difference) or the difference falls below PRICE_RAW_MIN, with matching precision required beforehand.

Common situations: Computing price deltas without knowing which side is larger; spread calculations where the spread can legitimately be negative; sorting/branching bugs that swap operands.

Related errors


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