nautechsystems/nautilus_trader · error · anyhow::Error
AX {field} scale must not exceed 26 for exact percent conver
Error message
AX {field} scale must not exceed 26 for exact percent conversion, was {scale} What it means
margin_percent_to_rate converts a percent to a rate by shifting the decimal scale up by 2 (equivalent to dividing by 100); rust_decimal supports at most 28 fractional digits, so the source percent must have scale <= 26. A margin percent serialized with 27+ decimal places fails this check before the conversion would overflow.
Source
Thrown at crates/adapters/architect_ax/src/http/parse.rs:406
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 a4b06ed870)
Solutions
- Round or normalize the margin percent values in AX venue config to a sane precision (a few decimal places is plenty for percents)
- Fix the upstream serializer so it emits fixed-precision decimals instead of full float expansions
- If high precision is genuinely required, quantize in the adapter before conversion (e.g. rescale to <= 26) via a PR with tests
Example fix
// before (AX contract metadata) "initial_margin_pct": "1.0000000000000000000000000001" // after "initial_margin_pct": "1.0"
Defensive patterns
Strategy: validation
Validate before calling
// Check decimal scale before percent conversion (mirror of the adapter's guard)
fn margin_scale_ok(v: Decimal) -> bool {
v.normalize().scale() <= 26
}
if !margin_scale_ok(initial_margin_pct) || !margin_scale_ok(maintenance_margin_pct) {
anyhow::bail!("margin percent scale exceeds 26 digits; round before submitting");
} Try / catch
match parse_instrument(&definition, ts_event, ts_init) {
Ok(instrument) => Ok(instrument),
Err(e) if e.to_string().contains("scale must not exceed 26") => {
log::error!("margin percent serialized with >26 fractional digits; fix upstream precision");
Err(e)
}
Err(e) => Err(e),
} Prevention
- Serialize margin percents from Decimal, never f64 — float round-trips emit 27+ digit expansions
- Round margin values to a fixed small precision (e.g. 4 dp) at the source system
- Unit-test serializers with values like 1E-27 that normalize to high scale
When it happens
Trigger: A margin percent value like 1.0000000000000000000000000001 (scale 27) reaches parse_margin_rates — typically from a float round-trip that emits full f64 precision, or an upstream system generating unnormalized decimals.
Common situations: Integrations that build definitions from f64 and serialize without rounding; JSON numbers with absurd precision from spreadsheets or ETL pipelines; normalizing a value like 1E-27 leaves a high scale even though the mantissa is tiny.
Related errors
- AX initial_margin_pct must be positive, was {initial_margin_
- AX maintenance_margin_pct must be positive, was {maintenance
- AX maintenance_margin_pct {maintenance_margin_pct} exceeds i
- Failed to resolve AX instrument {symbol} via GET /instrument
- AX instrument product must be non-empty without surrounding
AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16).
Data as JSON: /api/errors/26b74958d5f2c185.
Report an issue: GitHub.