{"record":{"id":"565559405acbc41d","repo":"nautechsystems/nautilus_trader","slug":"spot-instrument-should-have-base-currency","errorCode":null,"errorMessage":"SPOT instrument should have base currency","messagePattern":"SPOT instrument should have base currency","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/bybit/src/http/client.rs","lineNumber":2452,"sourceCode":"\n        for wallet in &response.result.list {\n            for coin_balance in &wallet.coin {\n                let balance = coin_balance.wallet_balance - coin_balance.spot_borrow;\n                *wallet_by_coin\n                    .entry(coin_balance.coin)\n                    .or_insert(Decimal::ZERO) += balance;\n            }\n        }\n\n        let mut reports = Vec::new();\n\n        if let Some(instrument) = self\n            .instruments_cache\n            .get_cloned(&instrument_id.symbol.inner())\n        {\n            let base_currency = instrument\n                .base_currency()\n                .expect(\"SPOT instrument should have base currency\");\n            let coin = base_currency.code;\n            let wallet_balance = wallet_by_coin.get(&coin).copied().unwrap_or(Decimal::ZERO);\n\n            let side = if wallet_balance > Decimal::ZERO {\n                PositionSide::Long\n            } else if wallet_balance < Decimal::ZERO {\n                PositionSide::Short\n            } else {\n                PositionSide::Flat\n            };\n\n            let abs_balance = wallet_balance.abs();\n            let quantity = Quantity::from_decimal_dp(abs_balance, instrument.size_precision())?;\n\n            let report = PositionStatusReport::new(\n                account_id,\n                instrument_id,\n                side,","sourceCodeStart":2434,"sourceCodeEnd":2470,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/bybit/src/http/client.rs#L2434-L2470","documentation":"When parsing a SPOT wallet/balance response, the code looks up the instrument in `instruments_cache` and calls `.base_currency().expect(...)` because SPOT instruments are required to carry a base currency. Panicking means the cached entry is not a SPOT instrument with a base currency — e.g. the cache holds a wrong/missing instrument for the symbol, or the symbol being iterated is not actually SPOT.","triggerScenarios":"Requesting SPOT account balances when the instruments cache lacks the symbol or contains a non-SPOT/stale instrument entry for it, so `base_currency()` returns `None`.","commonSituations":"Instruments cache not loaded/refreshed before balance parsing; a delisted or renamed symbol still present in wallet data; a bug where non-SPOT symbols leak into the SPOT wallet parsing path.","solutions":["Ensure the instruments cache is fully loaded (exchange-info fetch) before requesting SPOT wallet data","Verify the symbol in the cache corresponds to a live SPOT instrument and refresh the cache if stale","Skip or log symbols missing from the cache instead of expecting","Report a bug if a valid SPOT balance request triggers the panic"],"exampleFix":"// before\nlet base_currency = instrument.base_currency().expect(\"SPOT instrument should have base currency\");\n// after\nlet Some(base_currency) = instrument.base_currency() else {\n    log::warn!(\"SPOT instrument {instrument_id} missing base currency; skipping\");\n    continue;\n};","handlingStrategy":"validation","validationCode":"// Before requesting SPOT balances, ensure the instruments cache covers the symbol\nassert!(client.has_instrument(&instrument_id.symbol.inner()), \"instrument not cached; load instruments first\");","typeGuard":"fn is_spot_with_base(i: &dyn AnyInstrument) -> bool {\n    i.instrument_class() == InstrumentClass::Spot && i.base_currency().is_some()\n}","tryCatchPattern":"// If extending the client, replace expect with graceful skip:\nlet Some(base) = instrument.base_currency() else { log::warn!(\"missing base for {sym}\"); return Ok(()); };","preventionTips":["Load/refresh the instruments cache before any wallet parsing","Filter wallet symbols to known SPOT instruments only","Handle delisted/renamed symbols explicitly instead of assuming cache hits"],"tags":["panic","instrument-cache","spot","balance-parsing"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}