nautechsystems/nautilus_trader · error
failed to construct initial margin: {e}
Error message
failed to construct initial margin: {e} What it means
margin_balance_from_user_stats_with_currency derives the initial margin as collateral minus available balance (floored at zero) and wraps it in a Money value in the settlement currency. Money::from_decimal rejects decimals whose precision exceeds the currency's precision, so if the Lighter user-stats values carry more decimal places than the settlement currency supports (e.g. more than 6 dp for USDC), the conversion fails and this error is raised.
Source
Thrown at crates/adapters/lighter/src/websocket/parse.rs:1143
/// # Errors
///
/// 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,View on GitHub (pinned to 18893faf8b)
Solutions
- Round the computed decimal to the settlement currency's precision before conversion: initial_dec = initial_dec.round_dp(settlement_currency.precision).
- Verify the settlement Currency instance used for the account matches Lighter's actual asset precision (e.g. USDC at 6 dp).
- Log stats.collateral, stats.available_balance and the currency precision when this fires to confirm which side has excess precision.
- If the currency is newly listed, register it with the correct precision instead of reusing a generic Currency::USD placeholder.
Example fix
// before
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}"))?;
// after
let initial_dec = ((stats.collateral - stats.available_balance).max(Decimal::ZERO))
.round_dp(settlement_currency.precision);
let initial = Money::from_decimal(initial_dec, settlement_currency)
.map_err(|e| anyhow::anyhow!("failed to construct initial margin: {e}"))?; Defensive patterns
Strategy: validation
Validate before calling
if initial_dec.scale() > settlement_currency.precision as u32 {
initial_dec = initial_dec.round_dp(settlement_currency.precision as u32);
} Try / catch
match margin_balance_from_user_stats_with_currency(&stats, currency) {
Ok(mb) => apply(mb),
Err(e) => log::error!("margin conversion failed, check currency precision: {e}"),
} Prevention
- Configure settlement currencies with the same precision the exchange reports (e.g. USDC at 6 dp).
- Round derived decimals to currency precision before any Money conversion.
- Fail fast at account setup if the currency precision is implausible.
When it happens
Trigger: A Lighter account-stats websocket snapshot has collateral or available_balance with a decimal precision greater than the settlement currency's configured precision, making Money::from_decimal(initial_dec, settlement_currency) fail.
Common situations: Using a settlement Currency whose precision was configured too low (e.g. precision 2) while Lighter reports 6+ decimal places; a new settlement asset added on Lighter with a precision not matching the local Currency catalog; raw exchange values with unexpected extra decimals after a API change.
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 maintenance margin: {e}
- Instrument not found in cache: {symbol}
- No margin data returned from BitMEX
- Invalid leverage {leverage} for {}
- AX initial_margin_pct must be positive, was {initial_margin_
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/687e2811cc2337ac.
Report an issue: GitHub.