{"record":{"id":"891db56a023a40bb","repo":"nautechsystems/nautilus_trader","slug":"quote-maps-must-have-equal-lengths","errorCode":null,"errorMessage":"Quote maps must have equal lengths","messagePattern":"Quote maps must have equal lengths","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/common/src/xrate.rs","lineNumber":58,"sourceCode":"pub fn get_exchange_rate(\n    from_currency: Ustr,\n    to_currency: Ustr,\n    price_type: PriceType,\n    quotes_bid: AHashMap<Ustr, Decimal>,\n    mut quotes_ask: AHashMap<Ustr, Decimal>,\n) -> anyhow::Result<Option<Decimal>> {\n    if from_currency == to_currency {\n        // When the source and target currencies are identical,\n        // no conversion is needed; return an exchange rate of one.\n        return Ok(Some(Decimal::ONE));\n    }\n\n    if quotes_bid.is_empty() || quotes_ask.is_empty() {\n        anyhow::bail!(\"Quote maps must not be empty\");\n    }\n\n    if quotes_bid.len() != quotes_ask.len() {\n        anyhow::bail!(\"Quote maps must have equal lengths\");\n    }\n\n    // Validated here, in the same position as the price-type match this replaced, so the\n    // identical-currency shortcut and the quote-map errors keep their original precedence.\n    if !matches!(price_type, PriceType::Bid | PriceType::Ask | PriceType::Mid) {\n        anyhow::bail!(\"Invalid `price_type`, was '{price_type}'\");\n    }\n\n    // Construct a graph: each currency maps to its neighbors and corresponding conversion rate\n    let mut graph: AHashMap<Ustr, Vec<(Ustr, Decimal)>> = AHashMap::new();\n\n    for (pair, bid) in quotes_bid {\n        let ask = quotes_ask\n            .remove(&pair)\n            .ok_or_else(|| anyhow::anyhow!(\"Missing ask quote for pair {pair}\"))?;\n        let parts: Vec<&str> = pair.split('/').collect();\n\n        if parts.len() != 2 {","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/common/src/xrate.rs#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Build both maps from the same pair set — filter bid and ask maps with identical criteria.","Log/inspect which pairs exist in only one map and repair the data source for the missing side.","Defensively intersect the key sets before calling, ensuring lengths match."],"exampleFix":"// before\nlet rate = get_exchange_rate(usd, eur, pt, &all_bids, &all_asks)?; // lengths differ\n// after\nlet pairs: HashSet<_> = all_bids.keys().collect::<HashSet<_>>()\n    .intersection(&all_asks.keys().collect()).cloned().collect();\nlet bids: AHashMap<_,_> = pairs.iter().map(|k| (*k, all_bids[k])).collect();\nlet asks: AHashMap<_,_> = pairs.iter().map(|k| (*k, all_asks[k])).collect();\nlet rate = get_exchange_rate(usd, eur, pt, &bids, &asks)?;","handlingStrategy":"validation","validationCode":"if set(bid_quotes) != set(ask_quotes):\n    raise ValueError(\"bid and ask quote maps must cover identical currency pairs\")\nif len(bid_quotes) != len(ask_quotes):\n    raise ValueError(\"bid and ask quote maps must have equal lengths\")","typeGuard":null,"tryCatchPattern":"match get_exchange_rate(base, quote, pt, &bids, &asks) {\n    Err(e) if e.to_string().contains(\"equal lengths\") => {\n        log::warn!(\"bid/ask quote maps out of sync: {e}\");\n        None\n    }\n    other => other.ok().flatten(),\n}","preventionTips":["Build both maps from one snapshot pass so they always share the same key set.","Filter bid and ask maps with identical staleness/price criteria.","Assert map key equality in tests for any code that prepares quote maps."],"tags":["validation","exchange-rate","length-mismatch","quotes"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}