nautechsystems/nautilus_trader · error

Missing ask quote for pair {pair}

Error message

Missing ask quote for pair {pair}

What it means

The exchange-rate graph builder pairs each bid quote with its matching ask quote from quotes_ask. This error means a pair exists in the bid map but has no corresponding entry in the ask map, so a two-sided quote cannot be constructed for that currency pair.

Source

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

    }

    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)
            .ok_or_else(|| anyhow::anyhow!("Missing ask quote for pair {pair}"))?;
        let parts: Vec<&str> = pair.split('/').collect();

        if parts.len() != 2 {
            log::warn!("Skipping invalid pair string: {pair}");
            continue;
        }

        if bid <= Decimal::ZERO || ask <= Decimal::ZERO {
            // Both sides are required to build valid forward and reverse edges.
            log::warn!("Skipping pair with non-positive bid or ask rate: {pair}");
            continue;
        }

        let base = Ustr::from(parts[0]);
        let quote = Ustr::from(parts[1]);
        let (forward_rate, reverse_rate) = directional_rates(bid, ask, price_type);

        graph.entry(base).or_default().push((quote, forward_rate));

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure quotes_bid and quotes_ask contain identical key sets before calling get_exchange_rate.
  2. Filter quotes_bid to only pairs also present in quotes_ask (and vice versa) and log the skipped pairs.
  3. Check the data pipeline/venue for why the ask side is missing for the affected pair.

Example fix

// before
let rate = get_exchange_rate(&bid_quotes, &ask_quotes, "EUR/USD")?;
// after
let rate = if bid_quotes.keys().all(|k| ask_quotes.contains_key(k)) {
    get_exchange_rate(&bid_quotes, &ask_quotes, "EUR/USD")?
} else {
    let pairs: Vec<_> = bid_quotes.keys().filter(|k| !ask_quotes.contains_key(*k)).collect();
    anyhow::bail!("ask quotes missing for pairs: {pairs:?}")
};
Defensive patterns

Strategy: validation

Validate before calling

let missing: Vec<_> = quotes_bid.keys().filter(|p| !quotes_ask.contains_key(*p)).collect();
if !missing.is_empty() {
    return Err(format!("ask quotes missing for pairs: {missing:?}"));
}

Try / catch

match get_exchange_rate(&bids, &asks, pair) {
    Ok(rate) => rate,
    Err(e) if e.to_string().contains("Missing ask quote") => { log::warn!("skipping {pair}: {e}"); continue; }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling get_exchange_rate with quotes_bid and quotes_ask maps of different pair coverage — e.g. a pair present in quotes_bid but absent from quotes_ask.

Common situations: Partial market data snapshots where one side of the book was not received; FX feeds that only publish one side for illiquid pairs; building the two maps in separate loops where one silently skipped an instrument.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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