{"record":{"id":"21f3ba8c5810b8d8","repo":"nautechsystems/nautilus_trader","slug":"betting-account-balance-would-become-negative","errorCode":null,"errorMessage":"Betting account balance would become negative: {} {} ({})","messagePattern":"Betting account balance would become negative: (.+?) (.+?) \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/accounts/betting.rs","lineNumber":114,"sourceCode":"\n    /// Clears all locked balances for the given instrument ID.\n    pub fn clear_balance_locked(&mut self, instrument_id: InstrumentId) {\n        base::clear_balance_locked(\n            &mut self.base.balances,\n            &mut self.balances_locked,\n            instrument_id,\n        );\n    }\n\n    /// Updates the account balances, rejecting negative totals.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if any balance has a negative total.\n    pub fn update_balances(&mut self, balances: &[AccountBalance]) -> anyhow::Result<()> {\n        for balance in balances {\n            if balance.total.raw < 0 {\n                anyhow::bail!(\n                    \"Betting account balance would become negative: {} {} ({})\",\n                    balance.total.as_decimal(),\n                    balance.currency.code,\n                    self.id\n                );\n            }\n        }\n        self.base.update_balances(balances);\n        Ok(())\n    }\n\n    #[must_use]\n    pub const fn is_unleveraged(&self) -> bool {\n        true\n    }\n\n    /// Returns the balance impact for a betting order.\n    ///","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/accounts/betting.rs#L96-L132","documentation":"`BettingAccount::update_balances` rejects any `AccountBalance` whose `total.raw` is negative, because a betting account total can never go below zero. It bails reporting the attempted total, currency and account id. This is a domain invariant check on balance updates.","triggerScenarios":"Calling `update_balances` with balances where any total is negative — e.g. applying an AccountState produced by subtracting stakes/payouts that overdraw the balance.","commonSituations":"Betting integration computing balance as balance = balance - stake where cumulative stakes exceed funds; currency/precision mistakes producing negative raw values; replaying out-of-order balance events.","solutions":["Clamp or validate balance computations upstream so totals never go negative before calling update_balances.","Fix the stake/payout arithmetic that overdrew the balance (check cumulative exposure vs available funds).","Verify currency and precision handling — negative raw values often indicate sign or scaling bugs.","Check event ordering; applying an old state after withdrawals can produce negative totals."],"exampleFix":"// before\nlet new_total = total - stake;\naccount.update_balances(&[balance_with_total(new_total)])?;\n// after\nanyhow::ensure!(new_total >= Decimal::ZERO, \"stake would overdraw balance\");\naccount.update_balances(&[balance_with_total(new_total)])?;","handlingStrategy":"validation","validationCode":"for b in balances {\n    if b.total.raw < 0 {\n        anyhow::bail!(\"balance total for {} would be negative\", b.currency.code);\n    }\n}\naccount.update_balances(&balances)?;","typeGuard":"fn balances_non_negative(balances: &[AccountBalance]) -> bool {\n    balances.iter().all(|b| b.total.raw >= 0)\n}","tryCatchPattern":"if let Err(e) = account.update_balances(&balances) {\n    if e.to_string().contains(\"would become negative\") {\n        // reject stake/exposure that overdrew the account; reconcile upstream\n    }\n    return Err(e.into());\n}","preventionTips":["Validate stake <= available total before applying balance updates.","Check sign and scaling of raw balance arithmetic in your adapter.","Apply balance events in order and reject stale/out-of-order states."],"tags":["account","balance","betting","invariant-violation"],"backgroundTag":"value-out-of-range","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}