nautechsystems/nautilus_trader · error

locked balance was negative: {locked}

Error message

locked balance was negative: {locked}

What it means

`update_balance_locked` reserves a portion of an account balance for a pending order, and a negative locked amount is nonsensical — it would imply releasing more than was reserved or creating balance from nothing. The function rejects the update before mutating any state.

Source

Thrown at crates/model/src/accounts/base.rs:432

/// Updates the locked balance for the given instrument and currency, then recalculates the
/// account balance for that currency from all per-(instrument, currency) locks.
///
/// The reservation is recorded without a balance when the currency has no observed balance yet,
/// so a later balance report derives from it.
///
/// # Errors
///
/// Returns an error if `locked` is negative, its precision differs from the balance precision,
/// or the reservations cannot produce a valid balance. Balances and reservations are left
/// unchanged when an error is returned.
pub(crate) fn update_balance_locked(
    balances: &mut IndexMap<Currency, AccountBalance>,
    balances_locked: &mut AHashMap<(InstrumentId, Currency), Money>,
    instrument_id: InstrumentId,
    locked: Money,
) -> anyhow::Result<()> {
    anyhow::ensure!(locked.raw >= 0, "locked balance was negative: {locked}");

    let currency = locked.currency;
    let key = (instrument_id, currency);

    let Some(current_balance) = balances.get(&currency).copied() else {
        balances_locked.insert(key, locked);
        return Ok(());
    };

    anyhow::ensure!(
        current_balance.currency.precision == currency.precision,
        "Cannot update {currency} reservation: precision {} differed from balance precision {}",
        currency.precision,
        current_balance.currency.precision
    );

    let previous = balances_locked.insert(key, locked);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Fix the caller to pass a non-negative locked amount
  2. Check adapter logic that derives locked balance from total vs available so signs are correct
  3. If reducing a reservation, use the release/unlock path instead of passing a negative value

Example fix

// before
update_balance_locked(balances, locks, instrument_id, Money(-100, USD))
// after
update_balance_locked(balances, locks, instrument_id, Money(100, USD))
Defensive patterns

Strategy: validation

Validate before calling

if locked.raw < 0:
    raise ValueError(f"cannot lock negative balance: {locked}")

Try / catch

try:
    update_balance_locked(balances, locks, instrument_id, locked)
except Exception as e:
    log.warning(f"balance lock rejected: {e}")  # state unmutated

Prevention

When it happens

Trigger: Calling update_balance_locked (or the account's balance-locking path) with a locked Money whose raw value is negative, e.g. when an adapter computes locked balance as a negative delta.

Common situations: Adapter balance-sync bugs where held/margin amounts are subtracted twice, sign errors when converting broker 'available' vs 'locked' fields, or test code constructing negative Money.

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/caff85a88e3fb732. Report an issue: GitHub.