nautechsystems/nautilus_trader · error
negative quantity `{value}`
Error message
negative quantity `{value}` What it means
Nautilus's Quantity type is strictly non-negative, so parse_quantity rejects any decimal string whose value is negative (checked via Decimal::is_sign_positive). Signed amounts must have their sign extracted by the caller (e.g. as a side/direction) before parsing the magnitude. Note that zero is intentionally allowed because Lighter sends zero-size book levels to delete existing orders.
Source
Thrown at crates/adapters/lighter/src/common/parse.rs:119
/// Converts a decimal string into a non-negative Nautilus [`Quantity`].
///
/// Zero is allowed because Lighter sends zero-size book levels to delete
/// existing orders.
///
/// # Errors
///
/// Returns an error if the string is not a decimal, if `precision` exceeds
/// [`MAX_DECIMALS`], if the value is negative, or if the resulting quantity
/// is out of range.
pub fn parse_quantity(value: &str, precision: u8) -> anyhow::Result<Quantity> {
anyhow::ensure!(
precision <= MAX_DECIMALS,
"size precision {precision} exceeds maximum {MAX_DECIMALS}",
);
let decimal =
Decimal::from_str(value).map_err(|e| anyhow::anyhow!("invalid quantity `{value}`: {e}"))?;
anyhow::ensure!(decimal.is_sign_positive(), "negative quantity `{value}`");
Quantity::from_decimal_dp(decimal, precision)
.map_err(|e| anyhow::anyhow!("invalid quantity `{value}` at precision {precision}: {e}"))
}
/// Converts a [`Decimal`] into a Nautilus [`Price`] at the requested precision.
///
/// Use this when the wire value has already been deserialized as a [`Decimal`]
/// (the standard pattern for model fields tagged with `deserialize_decimal`).
///
/// # Errors
///
/// Returns an error if `precision` exceeds [`MAX_DECIMALS`] or if the value
/// is out of [`Price`] range.
pub fn price_from_decimal(value: Decimal, precision: u8) -> anyhow::Result<Price> {
anyhow::ensure!(
precision <= MAX_DECIMALS,
"price precision {precision} exceeds maximum {MAX_DECIMALS}",
);View on GitHub (pinned to 18893faf8b)
Solutions
- Take the magnitude before parsing: parse_quantity(&value.trim_start_matches('-'), precision) or better, parse to Decimal first, take .abs(), and use the sign for side/direction decisions.
- Route signed deltas through parse_quantity_from_ticks after .abs() and handle the sign explicitly (sell/buy, long/short).
- Verify the payload field is genuinely meant to be unsigned; if the source can be negative, this is the wrong parser.
- Treat unexpected negatives as malformed data: log the raw value and drop the message rather than coercing.
Example fix
// before
let qty = parse_quantity(delta_str, precision)?;
// after
let magnitude = delta_str.trim_start_matches('-');
let qty = parse_quantity(magnitude, precision)?;
let side = if delta_str.starts_with('-') { OrderSide::Sell } else { OrderSide::Buy }; Defensive patterns
Strategy: validation
Validate before calling
fn is_non_negative_decimal_str(s: &str) -> bool {
s.trim().parse::<rust_decimal::Decimal>()
.map(|d| d.is_sign_positive())
.unwrap_or(false)
} Try / catch
match parse_quantity(raw, precision) {
Ok(q) => q,
Err(e) if e.to_string().starts_with("negative quantity") => {
tracing::warn!(raw = %raw, "signed size; extract sign and parse magnitude");
return Ok(None);
}
Err(e) => return Err(e.into()),
} Prevention
- Extract the sign into side/direction before parsing the magnitude of any signed wire field
- Route signed deltas through parse_quantity_from_ticks(ticks.abs(), decimals)
- Confirm the field is genuinely unsigned before using parse_quantity on it
- Log and drop unexpected negatives rather than coercing to zero
When it happens
Trigger: Calling parse_quantity with a string like "-0.5" — e.g. a signed base_amount from a fill/delta/position payload passed directly, or a sign forgotten when reformatting a value.
Common situations: Order-delta processing where decreases arrive as negative sizes; position-change events with signed amounts; sign lost/added during manual string formatting; mixing bid-side and ask-side signed conventions.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- negative tick count {ticks} for Quantity
- size precision {precision} exceeds maximum {MAX_DECIMALS}
- AX requires whole contract quantities, was {}
- Order quantity must be at least 1 contract
- quantity size={} cannot be represented with precision={}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/8a990c18e541dbd4.
Report an issue: GitHub.