nautechsystems/nautilus_trader · error

canonical run field '{field}' is not a canonical unsigned de

Error message

canonical run field '{field}' is not a canonical unsigned decimal

What it means

After confirming the field is a string, `validate_unsigned_decimal` enforces canonical formatting: the value must be exactly "0" or a non-empty digit-only string with no leading zero. This error is thrown for strings like "007", "1.5", "-3", " 12", "", or "1e3" — i.e. non-canonical representations of unsigned decimals. This keeps the canonical document byte-stable and deterministic.

Source

Thrown at crates/backtest/src/result.rs:479

    );
    anyhow::ensure!(
        matches!(
            object.get("outcome").and_then(Value::as_str),
            Some("completed" | "failed" | "incomplete" | "stopped")
        ),
        "unsupported canonical run outcome"
    );
    Ok(())
}

fn validate_unsigned_decimal(value: &Value, field: &str, nullable: bool) -> anyhow::Result<()> {
    if nullable && value.is_null() {
        return Ok(());
    }
    let value = value
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("canonical run field '{field}' must be a decimal string"))?;
    anyhow::ensure!(
        value == "0"
            || (!value.is_empty()
                && !value.starts_with('0')
                && value.bytes().all(|byte| byte.is_ascii_digit())),
        "canonical run field '{field}' is not a canonical unsigned decimal"
    );
    Ok(())
}

fn validate_statistics(value: &Value) -> anyhow::Result<()> {
    let object = value
        .as_object()
        .ok_or_else(|| anyhow::anyhow!("canonical result statistics must be an object"))?;
    validate_fields(
        object,
        &["general", "pnls", "returns", "returns_series"],
        "canonical result statistics",
    )?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Rewrite the value in canonical form: "0" or digits-only with no leading zeros (e.g. "042" -> "42")
  2. Regenerate the document with the canonical writer instead of hand-editing
  3. Normalize the string in a pre-processing step (strip leading zeros, reject non-digit characters) before validation
  4. Fix the producing code so it serializes integers with plain decimal formatting

Example fix

// before
{"run": {"total_orders": "0042"}}
// after
{"run": {"total_orders": "42"}}
Defensive patterns

Strategy: validation

Validate before calling

fn is_canonical_unsigned_decimal(s: &str) -> bool {
    s == "0" || (!s.is_empty() && !s.starts_with('0') && s.bytes().all(|b| b.is_ascii_digit()))
}

Type guard

fn value_is_canonical_decimal(v: &serde_json::Value) -> bool {
    v.as_str().map_or(false, is_canonical_unsigned_decimal)
}

Try / catch

match validate_document(&doc) {
    Err(e) if e.to_string().contains("canonical unsigned decimal") => {
        // strip leading zeros / reformat the offending field and retry
    }
    other => other,
}

Prevention

When it happens

Trigger: Validating a run document whose iterations/total_events/total_orders/total_positions or backtest_start_ns/backtest_end_ns string is not a canonical unsigned decimal: leading zeros ("042"), floats ("3.14"), negatives ("-1"), whitespace, exponent notation, or scientific notation.

Common situations: Timestamps formatted with float seconds then stringified ("1720000000.5"); values formatted with thousands separators; numbers produced via toFixed/percent formatting; manual edits adding padding zeros; negative values from buggy counters.

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/81a004d9da406d99. Report an issue: GitHub.