nautechsystems/nautilus_trader · error · anyhow::Error

Quote maps must have equal lengths

Error message

Quote maps must have equal lengths

What it means

get_exchange_rate requires the bid and ask quote maps to be parallel: the same currency-pair keys in the same quantities. Mismatched lengths mean the two price series cannot be zipped into consistent graph edges, so the function bails. This preserves the invariant that each pair has both a bid and an ask quote.

Source

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

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

        if parts.len() != 2 {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Build both maps from the same pair set — filter bid and ask maps with identical criteria.
  2. Log/inspect which pairs exist in only one map and repair the data source for the missing side.
  3. Defensively intersect the key sets before calling, ensuring lengths match.

Example fix

// before
let rate = get_exchange_rate(usd, eur, pt, &all_bids, &all_asks)?; // lengths differ
// after
let pairs: HashSet<_> = all_bids.keys().collect::<HashSet<_>>()
    .intersection(&all_asks.keys().collect()).cloned().collect();
let bids: AHashMap<_,_> = pairs.iter().map(|k| (*k, all_bids[k])).collect();
let asks: AHashMap<_,_> = pairs.iter().map(|k| (*k, all_asks[k])).collect();
let rate = get_exchange_rate(usd, eur, pt, &bids, &asks)?;
Defensive patterns

Strategy: validation

Validate before calling

if set(bid_quotes) != set(ask_quotes):
    raise ValueError("bid and ask quote maps must cover identical currency pairs")
if len(bid_quotes) != len(ask_quotes):
    raise ValueError("bid and ask quote maps must have equal lengths")

Try / catch

match get_exchange_rate(base, quote, pt, &bids, &asks) {
    Err(e) if e.to_string().contains("equal lengths") => {
        log::warn!("bid/ask quote maps out of sync: {e}");
        None
    }
    other => other.ok().flatten(),
}

Prevention

When it happens

Trigger: Calling get_exchange_rate with quotes_bid.len() != quotes_ask.len() — e.g. bid quotes cached for pairs A/B, C/D but ask quotes only for A/B; building the maps from different time windows or instruments.

Common situations: Partial feed failures where one side of quotes is missing; filtering one map by price type or staleness without filtering the other; mixing snapshot times so one map has extra pairs.

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/891db56a023a40bb. Report an issue: GitHub.