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
- Round the margin percentage at the data source to a sane precision (e.g. 8 decimal places) before parsing.
- Check whether the venue value is meant to be a fraction already (0.0001) rather than a high-precision percent.
- Fix float-to-Decimal fixture generation to quantize values.
- 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
- Quantize margin percentages (e.g. scale 8) at ingestion
- Avoid converting raw f64 with full binary precision into Decimal
- Round venue-provided percentages to a sane precision before parsing
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
- is not exactly representable with quantity precision
- AX initial_margin_pct must be positive, was
- AX maintenance_margin_pct must be positive, was
- AX minimum_order_size must be a positive whole number, was
- derived market price
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)