nautechsystems/nautilus_trader · error
Cannot add a multi-currency spot instrument {instrument_id}
Error message
Cannot add a multi-currency spot instrument {instrument_id} for a venue with a single-currency CASH account What it means
A CASH account with a base currency is single-currency: it cannot settle instruments quoted/settled in multiple currencies. When adding an instrument to a simulated exchange, the engine rejects multi-currency spot instruments (CurrencyPair or TokenizedAsset) if the venue's account is single-currency CASH, preventing account/SETTLEMENT inconsistencies during the backtest.
Source
Thrown at crates/backtest/src/engine.rs:355
/// Adds an instrument to the backtest engine for the specified venue.
///
/// # Errors
///
/// Returns an error if:
/// - The instrument's associated venue has not been added via `add_venue`.
/// - Attempting to add a `CurrencyPair` instrument for a single-currency CASH account.
pub fn add_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
let instrument_id = instrument.id();
if let Some(exchange) = self.venues.get(&instrument.id().venue) {
let previous_expiration_ns = exchange.borrow().instrument_expiration(instrument_id);
if matches!(
instrument,
InstrumentAny::CurrencyPair(_) | InstrumentAny::TokenizedAsset(_)
) && exchange.borrow().account_type != AccountType::Margin
&& exchange.borrow().base_currency.is_some()
{
anyhow::bail!(
"Cannot add a multi-currency spot instrument {instrument_id} for a venue with a single-currency CASH account"
)
}
exchange.borrow_mut().add_instrument(instrument.clone())?;
if let Some(expiration_ns) = instrument.expiration_ns() {
self.set_instrument_expiration_timer(exchange, instrument_id, expiration_ns)?;
}
if let Some(previous_expiration_ns) = previous_expiration_ns
&& instrument.expiration_ns() != Some(previous_expiration_ns)
&& !exchange
.borrow()
.has_unprocessed_instrument_expiration(previous_expiration_ns)
{
let timer_name = Self::instrument_expiration_timer_name(
instrument_id.venue,
previous_expiration_ns,
);View on GitHub (pinned to 18893faf8b)
Solutions
- Set the venue's account to Margin (account_type: AccountType::Margin) for multi-currency crypto spot instruments
- Remove base_currency from the venue config so the CASH account is not single-currency, if multi-currency CASH is intended
- Add only single-currency instruments matching the venue's base_currency
- Split venues: one single-currency CASH venue per currency pair group
Example fix
// before
let account = AccountConfig::cash(base_currency: Some(USD)); // single-currency
engine.add_instrument(venue, currency_pair_btcusdt)?;
// after
let account = AccountConfig::margin(); // multi-currency capable
engine.add_venue(SimulatedVenueConfig{ account, .. });
engine.add_instrument(venue, currency_pair_btcusdt)?; Defensive patterns
Strategy: validation
Validate before calling
fn can_accept(instrument: &InstrumentAny, account_type: AccountType, base_currency: Option<Currency>) -> bool {
let multi_ccy = matches!(instrument, InstrumentAny::CurrencyPair(_) | InstrumentAny::TokenizedAsset(_));
!(multi_ccy && account_type == AccountType::Cash && base_currency.is_some())
}
// call before engine.add_instrument(...) Try / catch
if let Err(e) = engine.add_instrument(venue, instrument) {
if e.to_string().contains("single-currency CASH account") {
anyhow::bail!("Set the venue account to Margin (or remove base_currency) for {}", instrument.id());
}
return Err(e);
} Prevention
- Use Margin accounts for multi-currency crypto spot venues
- Only assign base_currency to single-currency FX-style venues
- Validate instrument currency-set against account type in config loading
When it happens
Trigger: Calling add_instrument with an InstrumentAny::CurrencyPair or TokenizedAsset (e.g. BTC/USDT) on a venue configured with a CASH account that has Some(base_currency) — only Margin accounts (or single-currency quote) accept it.
Common situations: Configuring the venue account as CASH with base_currency=USD but loading crypto spot pairs (multi-currency by nature); reusing a single-currency FX venue config for crypto instruments; forgetting to set account_type=Margin for crypto spots.
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
- `tick_sz` is empty for {}
- `lot_sz` is empty for {}
- Empty underlying for {inst_id}: instrument may be pre-open o
- AX instrument product must be non-empty without surrounding
- AX minimum_order_size must be a positive whole number, was {
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/d171c7ee346fd758.
Report an issue: GitHub.