nautechsystems/nautilus_trader · error

invalid quantity `{value}`: {e}

Error message

invalid quantity `{value}`: {e}

What it means

parse_quantity parses the input string with Decimal::from_str before any quantity checks. This error means the string is not a valid decimal number — bad characters, empty string, thousands separators, multiple decimal points, or other formats rust_decimal cannot parse. The raw value is embedded in the message.

Source

Thrown at crates/adapters/lighter/src/common/parse.rs:118

}

/// 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

  1. Normalize the string first: trim, strip currency/ticker suffixes and thousands separators, convert locale decimal commas to dots.
  2. Confirm you are passing the raw wire value exactly as Lighter sends it (plain decimal string).
  3. If the field is optional, check for the sentinel/absent case before calling and skip parsing.
  4. Deserialize to Decimal via the adapter's deserialize_decimal helper and use Quantity::from_decimal_dp-based paths instead of strings.

Example fix

// before
let qty = parse_quantity(raw_size, precision)?;
// after
if raw_size.is_empty() || raw_size == "none" { return Ok(Quantity::zero(precision)); }
let qty = parse_quantity(raw_size.trim(), precision)?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn is_plain_decimal(s: &str) -> bool {
    let s = s.trim();
    !s.is_empty()
        && s.chars().all(|c| c.is_ascii_digit() || c == '.' || c == '-' || c == '+')
        && s.matches('.').count() <= 1
}

Try / catch

match parse_quantity(raw, precision) {
    Ok(q) => q,
    Err(e) if e.to_string().starts_with("invalid quantity") && !e.to_string().contains("negative") => {
        tracing::warn!(raw = %raw, "unparseable quantity string; skipping");
        return Ok(None);
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling parse_quantity with a non-decimal string — e.g. "", "none", "1.5e3" variants rejected by rust_decimal, "1,000.5", "0.5 BTC", or a null-like sentinel string from the payload.

Common situations: Upstream responses where optional size fields are serialized as sentinel strings; display-formatted quantities; locale-formatted numbers (comma decimal separator); wiring the wrong field into the parser.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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