{"record":{"id":"8acb8f487f79f5e9","repo":"nautechsystems/nautilus_trader","slug":"quote-maps-must-not-be-empty","errorCode":null,"errorMessage":"Quote maps must not be empty","messagePattern":"Quote maps must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/common/src/xrate.rs","lineNumber":54,"sourceCode":"/// - `quotes_bid` or `quotes_ask` is empty.\n/// - `quotes_bid` and `quotes_ask` lengths are not equal.\n/// - `price_type` is equal to `Last` or `Mark` (cannot calculate from quotes).\n/// - The bid or ask side of a pair is missing.\npub 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)","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/common/src/xrate.rs#L36-L72","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Ensure quote data for the relevant currency pairs is loaded/cached before requesting the rate.","Check that the data client or cache actually contains quotes (e.g. cache.quotes_count() > 0) before calling.","Return a None/handle-missing-rate path in the caller when maps can legitimately be empty."],"exampleFix":"// before\nlet rate = get_exchange_rate(base, quote, PriceType::Mid, &bid_map, &ask_map)?;\n// after\nif bid_map.is_empty() || ask_map.is_empty() {\n    return Ok(None); // no quotes yet\n}\nlet rate = get_exchange_rate(base, quote, PriceType::Mid, &bid_map, &ask_map)?;","handlingStrategy":"validation","validationCode":"if not bid_quotes or not ask_quotes:\n    return None  # or skip the conversion entirely","typeGuard":null,"tryCatchPattern":"match get_exchange_rate(base, quote, pt, &bids, &asks) {\n    Ok(Some(rate)) => Some(rate),\n    Ok(None) | Err(_) => None, // treat missing quotes as 'no rate available'\n}","preventionTips":["Check that quotes for all needed pairs are cached before requesting rates.","Monitor feed health so empty quote caches are detected upstream.","In backtests, prime the quote cache from the dataset before strategy start."],"tags":["validation","exchange-rate","empty-input","quotes"],"backgroundTag":"empty-required-field","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"}