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

  1. Inspect the settlement_currency passed in — confirm its precision is valid (>= 1) and it is the correct asset for the Lighter account.
  2. Fix the Currency construction/source (currency catalog or config) rather than the parse code; zero itself is always representable in a valid currency.
  3. Add an assertion/early check on currency.precision when building the AccountState so a bad currency fails fast with a clearer message.
  4. 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

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


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