nautechsystems/nautilus_trader · error

Lighter spot symbol must contain one nonempty BASE/QUOTE pai

Error message

Lighter spot symbol must contain one nonempty BASE/QUOTE pair

What it means

Lighter spot symbols must be a single BASE/QUOTE pair, both sides nonempty, with no further '/'. spot_symbol_currencies validates this before creating currencies; any violation raises this ensure! error so a malformed symbol cannot silently create wrong currency pairs.

Source

Thrown at crates/adapters/lighter/src/http/parse.rs:552

        .build()
        .map_err(|e| anyhow::anyhow!("{e}"))?;

    registry.insert(
        order_book.market_id,
        order_book.symbol.as_str(),
        order_book.market_type,
    );

    Ok(InstrumentAny::CurrencyPair(instrument))
}

fn spot_symbol_currencies(symbol: &str) -> anyhow::Result<(Currency, Currency)> {
    let (base, quote) = symbol
        .split_once('/')
        .context("Lighter spot symbol must use BASE/QUOTE format")?;
    let base = base.trim();
    let quote = quote.trim();
    anyhow::ensure!(
        !base.is_empty() && !quote.is_empty() && !quote.contains('/'),
        "Lighter spot symbol must contain one nonempty BASE/QUOTE pair",
    );

    Ok((
        Currency::get_or_create_crypto(base),
        Currency::get_or_create_crypto(quote),
    ))
}

fn symbol_currencies(symbol: &str, default_quote: &str) -> (Currency, Currency) {
    let (base, quote) = symbol.split_once('/').unwrap_or((symbol, default_quote));
    (
        Currency::get_or_create_crypto(base),
        Currency::get_or_create_crypto(quote),
    )
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the market is actually SPOT type before routing to parse_spot_instrument.
  2. Check the Lighter API response's base/quote fields are populated and joined as 'BASE/QUOTE'.
  3. Add/refresh the adapter mapping if the venue changed its symbol convention.
  4. Skip the malformed market and log it for investigation.

Example fix

// before: assuming every market id maps to a spot pair
let (base, quote) = spot_symbol_currencies(&order_book.symbol)?;
// after: guard by market type first
if order_book.market_type == MarketType::SPOT {
    let (base, quote) = spot_symbol_currencies(&order_book.symbol)?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Rust: pre-validate symbol format
fn is_valid_spot_symbol(s: &str) -> bool {
    let parts: Vec<&str> = s.split('/').collect();
    parts.len() == 2 && !parts[0].trim().is_empty() && !parts[1].trim().is_empty()
}

Type guard

fn is_valid_spot_symbol(symbol: &str) -> bool {
    match symbol.split_once('/') {
        Some((b, q)) => !b.trim().is_empty() && !q.trim().is_empty() && !q.contains('/'),
        None => false,
    }
}

Try / catch

let (base, quote) = match spot_symbol_currencies(&order_book.symbol) {
    Ok(c) => c,
    Err(e) => { warn!("skipping market {}: {e:#}", order_book.symbol); continue; }
};

Prevention

When it happens

Trigger: parse_spot_instrument receives an OrderBookDetails entry whose base_currency/quote_currency-derived symbol is like 'ETH/', '/USDC', 'ETH/USDC/extra', or has no '/' at all (that case surfaces as the sibling 'must use BASE/QUOTE format' context).

Common situations: Venue API changes symbol format; market type misclassified as spot when it is actually a perp; empty base or quote fields in OrderBookDetails.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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