nautechsystems/nautilus_trader · error

Cannot apply account state: balance would be negative {} {}

Error message

Cannot apply account state: balance would be negative {} {} (borrowing not allowed for {})

What it means

CashAccount::apply validates AccountState events the same way update_balances does: when allow_borrowing is false, any balance with a negative total causes the event application to fail. This keeps account state consistent with the no-borrowing constraint.

Source

Thrown at crates/model/src/accounts/cash.rs:190

impl Account for CashAccount {
    impl_account_base_members!();

    fn is_cash_account(&self) -> bool {
        self.account_type == AccountType::Cash
    }

    fn is_margin_account(&self) -> bool {
        self.account_type == AccountType::Margin
    }

    fn apply(&mut self, event: AccountState) -> anyhow::Result<()> {
        self.check_event_account_id(&event)?;

        if !self.allow_borrowing {
            for balance in &event.balances {
                if balance.total.raw < 0 {
                    anyhow::bail!(
                        "Cannot apply account state: balance would be negative {} {} \
                        (borrowing not allowed for {})",
                        balance.total.as_decimal(),
                        balance.currency.code,
                        self.id
                    );
                }
            }
        }

        // Only clear locks when the venue reports a fresh balance snapshot
        if event.is_reported && !event.balances.is_empty() {
            self.balances_locked.clear();
        }

        self.base_apply(event);
        Ok(())
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-generate or clamp the AccountState balances to non-negative values
  2. Create the CashAccount with allow_borrowing=true if negative totals are legitimate
  3. Check that events are being routed to the correct account type

Example fix

// before: applying event to non-borrowing account
if !allow_borrowing { /* negative totals bail */ }
// after: guard before applying
let ok = event.balances.iter().all(|b| b.total.raw >= 0 || allow_borrowing);
anyhow::ensure!(ok, "negative balance with borrowing disabled");
account.apply(event)?;
Defensive patterns

Strategy: validation

Validate before calling

if !acct.allows_borrowing() && event.balances.iter().any(|b| b.total.raw < 0) { return Err(anyhow!("negative balance event")); }
acct.apply(event)?;

Type guard

fn event_appliable(acct: &CashAccount, event: &AccountState) -> bool {
    acct.allows_borrowing() || event.balances.iter().all(|b| b.total.raw >= 0)
}

Try / catch

if let Err(e) = acct.apply(event) {
    if e.to_string().contains("borrowing not allowed") { /* regenerate event or enable borrowing */ }
}

Prevention

When it happens

Trigger: Calling apply() (or py_apply) with an AccountState event containing a negative balance total while the CashAccount has allow_borrowing=false.

Common situations: Replaying account state events generated under different account config (borrowing later disabled), restoring snapshots that include overdrafts, wiring events from a margin context into a cash account.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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