nautechsystems/nautilus_trader · error

Failed to parse decimal from '{s}': {e}

Error message

Failed to parse decimal from '{s}': {e}

What it means

parse_decimal converts a string to a fixed-point Decimal via Decimal::from_str. If the string is not a syntactically valid decimal (bad characters, multiple dots, empty, out-of-scale exponent), the underlying parse error is wrapped with the offending input. This is a strict format-validation error for numeric strings.

Source

Thrown at crates/core/src/serialization.rs:705

    serializer: S,
) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let mut seq = serializer.serialize_seq(Some(decimals.len()))?;
    for decimal in decimals {
        seq.serialize_element(&decimal.to_string())?;
    }
    seq.end()
}

/// Parses a string to `Decimal`, returning an error if parsing fails.
///
/// # Errors
///
/// Returns an error if the string cannot be parsed as a Decimal.
pub fn parse_decimal(s: &str) -> anyhow::Result<Decimal> {
    Decimal::from_str(s).map_err(|e| anyhow::anyhow!("Failed to parse decimal from '{s}': {e}"))
}

/// Parses an optional string to `Decimal`, returning `None` if the string is `None` or empty.
///
/// # Errors
///
/// Returns an error if the string cannot be parsed as a Decimal.
pub fn parse_optional_decimal(s: &Option<String>) -> anyhow::Result<Option<Decimal>> {
    match s {
        None => Ok(None),
        Some(s) if s.is_empty() => Ok(None),
        Some(s) => parse_decimal(s).map(Some),
    }
}

/// Deserializes an empty string into `None`.
///
/// Many exchange APIs represent null string fields as an empty string (`""`).

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Sanitize the string first: trim whitespace, strip currency symbols and thousands separators.
  2. Use parse_optional_decimal if the value may legitimately be empty, so None is handled.
  3. Fix the data source/serialization so numbers arrive as plain decimal text (e.g. "1000.50").

Example fix

// before
let price = parse_decimal(raw.trim())?; // raw = "1,234.50"
// after
let cleaned = raw.trim().trim_start_matches('$').replace(',', "");
let price = parse_decimal(&cleaned)?;
Defensive patterns

Strategy: validation

Validate before calling

fn can_parse_decimal(s: &str) -> bool {
    let cleaned = s.trim().trim_start_matches('$').replace(',', "");
    !cleaned.is_empty() && rust_decimal::Decimal::from_str(&cleaned).is_ok()
}

Type guard

fn is_plain_decimal(s: &str) -> bool {
    !s.trim().is_empty() && s.trim().chars().all(|c| c.is_ascii_digit() || c == '.' || c == '-')
}

Try / catch

let price = parse_decimal(&cleaned)
    .with_context(|| format!("bad price field: {raw:?}"))?;

Prevention

When it happens

Trigger: parse_decimal(s) with any string not parseable as Decimal: "1,000.5" (comma separators), "$12.50" (currency symbols), "" (empty), "1.2.3", scientific notation beyond Decimal support, or locale-formatted numbers.

Common situations: Parsing prices/quantities from CSV, exchange responses, or user config where values carry thousands separators, currency symbols, or whitespace; locale differences; empty cells in a file.

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/93edef42d4156087. Report an issue: GitHub.