{"record":{"id":"17b6cd8b9b21b1b2","repo":"nautechsystems/nautilus_trader","slug":"missing-ask-quote-for-pair-pair","errorCode":null,"errorMessage":"Missing ask quote for pair {pair}","messagePattern":"Missing ask quote for pair (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/common/src/xrate.rs","lineNumber":73,"sourceCode":"    }\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 {\n            log::warn!(\"Skipping invalid pair string: {pair}\");\n            continue;\n        }\n\n        if bid <= Decimal::ZERO || ask <= Decimal::ZERO {\n            // Both sides are required to build valid forward and reverse edges.\n            log::warn!(\"Skipping pair with non-positive bid or ask rate: {pair}\");\n            continue;\n        }\n\n        let base = Ustr::from(parts[0]);\n        let quote = Ustr::from(parts[1]);\n        let (forward_rate, reverse_rate) = directional_rates(bid, ask, price_type);\n\n        graph.entry(base).or_default().push((quote, forward_rate));","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/common/src/xrate.rs#L55-L91","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure quotes_bid and quotes_ask contain identical key sets before calling get_exchange_rate.","Filter quotes_bid to only pairs also present in quotes_ask (and vice versa) and log the skipped pairs.","Check the data pipeline/venue for why the ask side is missing for the affected pair."],"exampleFix":"// before\nlet rate = get_exchange_rate(&bid_quotes, &ask_quotes, \"EUR/USD\")?;\n// after\nlet rate = if bid_quotes.keys().all(|k| ask_quotes.contains_key(k)) {\n    get_exchange_rate(&bid_quotes, &ask_quotes, \"EUR/USD\")?\n} else {\n    let pairs: Vec<_> = bid_quotes.keys().filter(|k| !ask_quotes.contains_key(*k)).collect();\n    anyhow::bail!(\"ask quotes missing for pairs: {pairs:?}\")\n};","handlingStrategy":"validation","validationCode":"let missing: Vec<_> = quotes_bid.keys().filter(|p| !quotes_ask.contains_key(*p)).collect();\nif !missing.is_empty() {\n    return Err(format!(\"ask quotes missing for pairs: {missing:?}\"));\n}","typeGuard":null,"tryCatchPattern":"match get_exchange_rate(&bids, &asks, pair) {\n    Ok(rate) => rate,\n    Err(e) if e.to_string().contains(\"Missing ask quote\") => { log::warn!(\"skipping {pair}: {e}\"); continue; }\n    Err(e) => return Err(e),\n}","preventionTips":["Build bid and ask quote maps from the same iteration over instruments.","Log and drop one-sided quotes before invoking exchange-rate conversion.","Monitor venue feeds for asymmetric book depth on the pairs you trade."],"tags":["rust","fx","market-data","missing-data"],"backgroundTag":"resource-not-found","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"}