{"record":{"id":"26b74958d5f2c185","repo":"nautechsystems/nautilus_trader","slug":"ax-field-scale-must-not-exceed-26-for-exact-perc","errorCode":null,"errorMessage":"AX {field} scale must not exceed 26 for exact percent conversion, was {scale}","messagePattern":"AX (.+?) scale must not exceed 26 for exact percent conversion, was (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/http/parse.rs","lineNumber":406,"sourceCode":"    anyhow::ensure!(\n        maintenance_margin_pct > Decimal::ZERO,\n        \"AX maintenance_margin_pct must be positive, was {maintenance_margin_pct}\"\n    );\n    anyhow::ensure!(\n        maintenance_margin_pct <= initial_margin_pct,\n        \"AX maintenance_margin_pct {maintenance_margin_pct} exceeds initial_margin_pct {initial_margin_pct}\"\n    );\n\n    Ok((\n        margin_percent_to_rate(initial_margin_pct, \"initial_margin_pct\")?,\n        margin_percent_to_rate(maintenance_margin_pct, \"maintenance_margin_pct\")?,\n    ))\n}\n\nfn margin_percent_to_rate(value: Decimal, field: &str) -> anyhow::Result<Decimal> {\n    let normalized = value.normalize();\n    let scale = normalized.scale();\n    anyhow::ensure!(\n        scale <= 26,\n        \"AX {field} scale must not exceed 26 for exact percent conversion, was {scale}\"\n    );\n    Decimal::try_from_i128_with_scale(normalized.mantissa(), scale + 2)\n        .with_context(|| format!(\"Failed to convert AX {field} percentage to a rate\"))\n}\n\n/// Parses an Ax balances response into a Nautilus [`AccountState`].\n///\n/// Ax provides a simple balance structure with symbol and amount.\n/// The amount is treated as both total and free balance (no locked funds tracking).\n///\n/// # Errors\n///\n/// Returns an error if balance amount parsing fails.\npub fn parse_account_state(\n    response: &AxBalancesResponse,\n    account_id: AccountId,","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/http/parse.rs#L388-L424","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (AX contract metadata)\n\"initial_margin_pct\": \"1.0000000000000000000000000001\"\n// after\n\"initial_margin_pct\": \"1.0\"","handlingStrategy":"validation","validationCode":"// Check decimal scale before percent conversion (mirror of the adapter's guard)\nfn margin_scale_ok(v: Decimal) -> bool {\n    v.normalize().scale() <= 26\n}\n\nif !margin_scale_ok(initial_margin_pct) || !margin_scale_ok(maintenance_margin_pct) {\n    anyhow::bail!(\"margin percent scale exceeds 26 digits; round before submitting\");\n}","typeGuard":null,"tryCatchPattern":"match parse_instrument(&definition, ts_event, ts_init) {\n    Ok(instrument) => Ok(instrument),\n    Err(e) if e.to_string().contains(\"scale must not exceed 26\") => {\n        log::error!(\"margin percent serialized with >26 fractional digits; fix upstream precision\");\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["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"],"tags":["architect-ax","instrument","margin","decimal","precision"],"backgroundTag":"decimal-precision-overflow","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}