nautechsystems/nautilus_trader · error · anyhow::Error

AX scale must not exceed 26 for exact percent conversion…

Error message

AX {field} scale must not exceed 26 for exact percent conversion, was {scale}

What it means

margin_percent_to_rate converts a percentage (e.g. 10 meaning 10%) to a rate (0.10) by scaling the Decimal mantissa by 2. rust_decimal supports at most 28-29 significant digits with scale <= 28; adding 2 to the scale requires the normalized scale to be <= 26 to keep the conversion exact. Larger scales would overflow or lose precision, so the adapter fails fast.

Solutions

  1. Round the margin percentage at the data source to a sane precision (e.g. 8 decimal places) before parsing.
  2. Check whether the venue value is meant to be a fraction already (0.0001) rather than a high-precision percent.
  3. Fix float-to-Decimal fixture generation to quantize values.
  4. Only if genuinely needed, raise precision handling in the adapter with maintainer guidance.

Example fix

// before
let init = Decimal::from_str("0.100000000000000000000000000001")?;
// after
let init = Decimal::from_str("0.10")?;
Defensive patterns

Strategy: validation

Validate before calling

fn margin_scale_ok(v: Decimal) -> bool { v.normalize().scale() <= 26 }

Prevention

When it happens

Trigger: parse_margin_rates (invoked by parse_instrument) on margin percentages whose normalized Decimal representation has more than 26 fractional digits — e.g. a value like 0.1234567890123456789012345678 normalized to a scale above 26.

Common situations: Venue sending absurdly precise margin values, a feed bug producing repeated decimal expansions, or fixtures generated with full-precision floats serialized to Decimal.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at crates/adapters/architect_ax/src/http/parse.rs:399

    anyhow::ensure!(
        maintenance_margin_pct > Decimal::ZERO,
        "AX maintenance_margin_pct must be positive, was {maintenance_margin_pct}"
    );
    anyhow::ensure!(
        maintenance_margin_pct <= initial_margin_pct,
        "AX maintenance_margin_pct {maintenance_margin_pct} exceeds initial_margin_pct {initial_margin_pct}"
    );

    Ok((
        margin_percent_to_rate(initial_margin_pct, "initial_margin_pct")?,
        margin_percent_to_rate(maintenance_margin_pct, "maintenance_margin_pct")?,
    ))
}

fn margin_percent_to_rate(value: Decimal, field: &str) -> anyhow::Result<Decimal> {
    let normalized = value.normalize();
    let scale = normalized.scale();
    anyhow::ensure!(
        scale <= 26,
        "AX {field} scale must not exceed 26 for exact percent conversion, was {scale}"
    );
    Decimal::try_from_i128_with_scale(normalized.mantissa(), scale + 2)
        .with_context(|| format!("Failed to convert AX {field} percentage to a rate"))
}

/// Parses an Ax balances response into a Nautilus [`AccountState`].
///
/// Ax provides a simple balance structure with symbol and amount.
/// The amount is treated as both total and free balance (no locked funds tracking).
///
/// # Errors
///
/// Returns an error if balance amount parsing fails.
pub fn parse_account_state(
    response: &AxBalancesResponse,
    account_id: AccountId,

View on GitHub (pinned to 18893faf8b)