nautechsystems/nautilus_trader · error
SPOT instrument should have base currency
Error message
SPOT instrument should have base currency
What it means
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.
Source
Thrown at crates/adapters/bybit/src/http/client.rs:2452
for wallet in &response.result.list {
for coin_balance in &wallet.coin {
let balance = coin_balance.wallet_balance - coin_balance.spot_borrow;
*wallet_by_coin
.entry(coin_balance.coin)
.or_insert(Decimal::ZERO) += balance;
}
}
let mut reports = Vec::new();
if let Some(instrument) = self
.instruments_cache
.get_cloned(&instrument_id.symbol.inner())
{
let base_currency = instrument
.base_currency()
.expect("SPOT instrument should have base currency");
let coin = base_currency.code;
let wallet_balance = wallet_by_coin.get(&coin).copied().unwrap_or(Decimal::ZERO);
let side = if wallet_balance > Decimal::ZERO {
PositionSide::Long
} else if wallet_balance < Decimal::ZERO {
PositionSide::Short
} else {
PositionSide::Flat
};
let abs_balance = wallet_balance.abs();
let quantity = Quantity::from_decimal_dp(abs_balance, instrument.size_precision())?;
let report = PositionStatusReport::new(
account_id,
instrument_id,
side,View on GitHub (pinned to 18893faf8b)
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
Example fix
// before
let base_currency = instrument.base_currency().expect("SPOT instrument should have base currency");
// after
let Some(base_currency) = instrument.base_currency() else {
log::warn!("SPOT instrument {instrument_id} missing base currency; skipping");
continue;
}; Defensive patterns
Strategy: validation
Validate before calling
// Before requesting SPOT balances, ensure the instruments cache covers the symbol assert!(client.has_instrument(&instrument_id.symbol.inner()), "instrument not cached; load instruments first");
Type guard
fn is_spot_with_base(i: &dyn AnyInstrument) -> bool {
i.instrument_class() == InstrumentClass::Spot && i.base_currency().is_some()
} Try / catch
// If extending the client, replace expect with graceful skip:
let Some(base) = instrument.base_currency() else { log::warn!("missing base for {sym}"); return Ok(()); }; Prevention
- 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
When it happens
Trigger: 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`.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Instrument not found in cache: {symbol}
- in-flight mutex poisoned
- wallet balance mutex poisoned
- OCM state lock poisoned
- slots lock poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/565559405acbc41d.
Report an issue: GitHub.