nautechsystems/nautilus_trader · error
failed to construct maintenance margin: {e}
Error message
failed to construct maintenance margin: {e} What it means
The same conversion function also constructs the maintenance margin as a fixed zero Money value via Money::from_decimal(Decimal::ZERO, settlement_currency). This should always succeed for any valid Currency, so this error only fires when the settlement currency itself is degenerate (e.g. zero/invalid precision) and from_decimal cannot represent even zero in it. It mirrors error 3401 as a defensive map_err on the maintenance-margin construction.
Source
Thrown at crates/adapters/lighter/src/websocket/parse.rs:1145
/// Returns an error if either `Money::from_decimal` call rejects the value.
pub fn margin_balance_from_user_stats(stats: &LighterUserStats) -> anyhow::Result<MarginBalance> {
margin_balance_from_user_stats_with_currency(stats, Currency::get_or_create_crypto("USDC"))
}
/// Builds the cross-margin [`MarginBalance`] in the supplied settlement currency.
///
/// # Errors
///
/// Returns an error if either `Money::from_decimal` call rejects the value.
pub fn margin_balance_from_user_stats_with_currency(
stats: &LighterUserStats,
settlement_currency: Currency,
) -> anyhow::Result<MarginBalance> {
let initial_dec = (stats.collateral - stats.available_balance).max(Decimal::ZERO);
let initial = Money::from_decimal(initial_dec, settlement_currency)
.map_err(|e| anyhow::anyhow!("failed to construct initial margin: {e}"))?;
let maintenance = Money::from_decimal(Decimal::ZERO, settlement_currency)
.map_err(|e| anyhow::anyhow!("failed to construct maintenance margin: {e}"))?;
Ok(MarginBalance::new(initial, maintenance, None))
}
/// Assembles the unified [`AccountState`] from already-parsed components.
///
/// The reconciler in the `websocket::account_state` module owns the latest
/// snapshot of each input stream and calls this once per emission.
/// `AccountType::Margin` is invariant for Lighter; `base_currency` is `None`
/// because the account holds multiple spot currencies.
#[must_use]
pub fn build_unified_account_state(
balances: Vec<AccountBalance>,
margin: Option<MarginBalance>,
account_id: AccountId,
ts_event: UnixNanos,
ts_init: UnixNanos,
) -> AccountState {
let margins = margin.map(|m| vec![m]).unwrap_or_default();View on GitHub (pinned to 18893faf8b)
Solutions
- Inspect the settlement_currency passed in — confirm its precision is valid (>= 1) and it is the correct asset for the Lighter account.
- Fix the Currency construction/source (currency catalog or config) rather than the parse code; zero itself is always representable in a valid currency.
- Add an assertion/early check on currency.precision when building the AccountState so a bad currency fails fast with a clearer message.
- If this error appears together with 3401, the root cause is the currency, not the arithmetic — prioritize fixing the Currency instance.
Example fix
// before
let maintenance = Money::from_decimal(Decimal::ZERO, settlement_currency)
.map_err(|e| anyhow::anyhow!("failed to construct maintenance margin: {e}"))?;
// after
assert!(settlement_currency.precision > 0, "invalid settlement currency precision");
let maintenance = Money::from_decimal(Decimal::ZERO, settlement_currency)
.map_err(|e| anyhow::anyhow!("failed to construct maintenance margin: {e}"))?; Defensive patterns
Strategy: validation
Validate before calling
assert!(
settlement_currency.precision > 0,
"settlement currency {} has invalid precision",
settlement_currency.code
); Try / catch
let maintenance = Money::from_decimal(Decimal::ZERO, settlement_currency)
.unwrap_or_else(|e| panic!("invalid settlement currency {:?}: {e}", settlement_currency)); Prevention
- Never construct Currency objects with precision 0 for crypto/fiat settlement assets.
- Load currencies from the project's currency catalog instead of ad-hoc construction.
- Validate currency configuration once at startup, not per-message.
When it happens
Trigger: Money::from_decimal(Decimal::ZERO, settlement_currency) fails, which in practice requires an invalid settlement Currency (e.g. precision 0 treated as invalid by Money construction rules) passed into margin_balance_from_user_stats_with_currency via build_state or margin_balance_from_user_stats.
Common situations: A Currency was constructed with precision 0 or otherwise malformed (e.g. loaded from bad config or a placeholder currency); a refactor passes the wrong type/units into the settlement currency argument.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- failed to construct initial margin: {e}
- {FAILED}: {e}
- No margin data returned from BitMEX
- Invalid leverage {leverage} for {}
- AX dated contract {} has different quote and settlement curr
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/e2ede37c9fcd5637.
Report an issue: GitHub.