nautechsystems/nautilus_trader · error · anyhow::Error

Invalid `price_type`, was '{price_type}'

Error message

Invalid `price_type`, was '{price_type}'

What it means

get_exchange_rate only supports Bid, Ask, and Mid price types for computing conversion rates. Any other PriceType value bails with this message. The check was relocated next to the quote-map validation so the identical-currency shortcut keeps its original precedence.

Source

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

) -> 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 {
            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.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Use PriceType::Mid (typical for conversion rates), Bid, or Ask.
  2. For last-trade-based rates, compute the rate from trade ticks instead of the xrate API.
  3. Validate the price_type in Python before calling py_get_exchange_rate.

Example fix

// before
rate = py_get_exchange_rate(usd, eur, PriceType.LAST, bids, asks)
// after
rate = py_get_exchange_rate(usd, eur, PriceType.MID, bids, asks)
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = {PriceType.BID, PriceType.ASK, PriceType.MID}
if price_type not in ALLOWED:
    raise ValueError(f"price_type must be BID, ASK or MID, got {price_type}")

Type guard

def is_fx_price_type(pt: PriceType) -> bool:
    return pt in (PriceType.BID, PriceType.ASK, PriceType.MID)

Try / catch

try:
    rate = py_get_exchange_rate(base, quote, price_type, bids, asks)
except Exception as e:
    if "Invalid `price_type`" in str(e):
        rate = py_get_exchange_rate(base, quote, PriceType.MID, bids, asks)

Prevention

When it happens

Trigger: Calling get_exchange_rate (or py_get_exchange_rate) with a PriceType outside Bid/Ask/Mid — e.g. Last or an out-of-enum value from Python bindings.

Common situations: Passing PriceType.LAST from Python strategy code that assumes all price types work for FX conversion; an enum value serialized from config that maps to an unsupported variant.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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