nautechsystems/nautilus_trader · error

wallet has no observed balance for {source_currency}

Error message

wallet has no observed balance for {source_currency}

What it means

calculate_balance_locked computes the locked balance for a prospective order in the source currency, but requires an already-observed balance for that currency to validate sufficient funds. If the wallet has no observed balance entry for source_currency, this error is raised because free/locked amounts cannot be determined.

Source

Thrown at crates/model/src/accounts/wallet.rs:644

        let base_currency = instrument
            .base_currency()
            .unwrap_or(instrument.quote_currency());
        let source_currency = if instrument.is_inverse() && !use_quote_for_inverse.unwrap_or(false)
        {
            base_currency
        } else {
            match side {
                OrderSide::Buy => instrument.quote_currency(),
                OrderSide::Sell => base_currency,
            }
        };
        let current_balance = self
            .base
            .balances
            .get(&source_currency)
            .copied()
            .ok_or_else(|| {
                anyhow::anyhow!("wallet has no observed balance for {source_currency}")
            })?;
        Self::validate_observed_balance(current_balance)?;

        if side == OrderSide::Sell {
            return Self::money_from_quantity(quantity, current_balance.currency)
                .map_err(Into::into);
        }

        Self::validate_quantity(quantity)?;
        Self::validate_price(price)?;

        if !instrument.is_inverse() && !instrument.is_quanto() {
            return Self::calculate_notional_exact(
                instrument,
                quantity,
                price,
                current_balance.currency,
            )

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Apply the initial account balance state before querying calculate_balance_locked
  2. Check observed balances for the currency before the call and return a clear 'not ready' state
  3. Verify the adapter emits balances for every currency used in orders

Example fix

// before
let locked = wallet.calculate_balance_locked(&currency, side, qty, &instrument).unwrap();
// after
let locked = wallet
    .calculate_balance_locked(&currency, side, qty, &instrument)
    .ok()  // or map to "balance not yet observed"
    .unwrap_or_default();
Defensive patterns

Strategy: validation

Validate before calling

if wallet.balances().get(&source_currency).is_none() {
    bail!("source currency {} not yet observed", source_currency);
}

Type guard

fn has_observed_balance(wallet: &AccountWallet, c: &Currency) -> bool {
    wallet.balances().contains_key(c)
}

Try / catch

match wallet.calculate_balance_locked(&cur, side, qty, &inst) {
    Ok(v) => v,
    Err(e) if e.to_string().contains("no observed balance") => default_locked,
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling calculate_balance_locked (or its Python wrapper py_calculate_balance_locked) with a source_currency that has no entry in the wallet's observed balances — typically before any balance event was applied for that currency.

Common situations: Querying margin/locked balance at startup before the exchange adapter delivers balance snapshots; asking about a currency the account never trades or reports; side=Buy paths needing the base currency which was never observed.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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