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

  1. Set the venue's account to Margin (account_type: AccountType::Margin) for multi-currency crypto spot instruments
  2. Remove base_currency from the venue config so the CASH account is not single-currency, if multi-currency CASH is intended
  3. Add only single-currency instruments matching the venue's base_currency
  4. 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

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


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