nautechsystems/nautilus_trader · error · anyhow::Error

Quote maps must not be empty

Error message

Quote maps must not be empty

What it means

get_exchange_rate builds a currency-graph conversion from parallel bid and ask quote maps. If either map is empty there is no rate data to traverse, so the function bails before doing any graph work. The library throws it early to give a precise diagnosis rather than returning no rate.

Source

Thrown at crates/common/src/xrate.rs:54

/// - `quotes_bid` or `quotes_ask` is empty.
/// - `quotes_bid` and `quotes_ask` lengths are not equal.
/// - `price_type` is equal to `Last` or `Mark` (cannot calculate from quotes).
/// - The bid or ask side of a pair is missing.
pub fn get_exchange_rate(
    from_currency: Ustr,
    to_currency: Ustr,
    price_type: PriceType,
    quotes_bid: AHashMap<Ustr, Decimal>,
    mut quotes_ask: AHashMap<Ustr, Decimal>,
) -> anyhow::Result<Option<Decimal>> {
    if from_currency == to_currency {
        // When the source and target currencies are identical,
        // no conversion is needed; return an exchange rate of one.
        return Ok(Some(Decimal::ONE));
    }

    if quotes_bid.is_empty() || quotes_ask.is_empty() {
        anyhow::bail!("Quote maps must not be empty");
    }

    if quotes_bid.len() != quotes_ask.len() {
        anyhow::bail!("Quote maps must have equal lengths");
    }

    // Validated here, in the same position as the price-type match this replaced, so the
    // identical-currency shortcut and the quote-map errors keep their original precedence.
    if !matches!(price_type, PriceType::Bid | PriceType::Ask | PriceType::Mid) {
        anyhow::bail!("Invalid `price_type`, was '{price_type}'");
    }

    // Construct a graph: each currency maps to its neighbors and corresponding conversion rate
    let mut graph: AHashMap<Ustr, Vec<(Ustr, Decimal)>> = AHashMap::new();

    for (pair, bid) in quotes_bid {
        let ask = quotes_ask
            .remove(&pair)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure quote data for the relevant currency pairs is loaded/cached before requesting the rate.
  2. Check that the data client or cache actually contains quotes (e.g. cache.quotes_count() > 0) before calling.
  3. Return a None/handle-missing-rate path in the caller when maps can legitimately be empty.

Example fix

// before
let rate = get_exchange_rate(base, quote, PriceType::Mid, &bid_map, &ask_map)?;
// after
if bid_map.is_empty() || ask_map.is_empty() {
    return Ok(None); // no quotes yet
}
let rate = get_exchange_rate(base, quote, PriceType::Mid, &bid_map, &ask_map)?;
Defensive patterns

Strategy: validation

Validate before calling

if not bid_quotes or not ask_quotes:
    return None  # or skip the conversion entirely

Try / catch

match get_exchange_rate(base, quote, pt, &bids, &asks) {
    Ok(Some(rate)) => Some(rate),
    Ok(None) | Err(_) => None, // treat missing quotes as 'no rate available'
}

Prevention

When it happens

Trigger: Calling get_exchange_rate (directly, via try_get_xrate, or via py_get_exchange_rate) with an empty quotes_bid or quotes_ask map, when source and target currencies differ (the same-currency shortcut returns first).

Common situations: Querying an exchange rate before any quotes for the involved pairs have been cached; a data feed outage leaving the quote cache empty; constructing the maps from an empty backtest dataset.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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