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
- Guard before subtracting: only subtract when self.raw >= rhs.raw (or self >= rhs via comparison), otherwise clamp to zero or reject.
- Track available quantity and validate the decrement against it before applying (standard position-management check).
- Compute deltas in Decimal (which supports negatives) when a signed remainder is meaningful.
- 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
- Always validate sufficient quantity before subtracting (position/order decrement checks).
- Make fill and cancel handlers idempotent to avoid double subtraction.
- Use Decimal for signed quantity deltas when a negative remainder is meaningful.
- Add tests for over-reduction and duplicate-fill scenarios.
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
- Underflow occurred when subtracting `Price`
- Liquidity subtraction underflow: x={current}, y={y}, delta={
- raw wei value exceeds unsigned 128-bit range
- fill raw bounds pre-checked
- Whole raw quantity must fit in Decimal
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/3d1b3ec30ff8cc27.
Report an issue: GitHub.