nautechsystems/nautilus_trader · error

Failed to parse instrument ID from key '{key}': {e}

Error message

Failed to parse instrument ID from key '{key}': {e}

What it means

After successfully stripping the prefix, parse_instrument_key attempts InstrumentId::from_str on the remainder. If the remainder is not a valid 'SYMBOL.VENUE' identifier, the parse error is wrapped as 'Failed to parse instrument ID from key'. The key had the right prefix but a malformed instrument portion.

Source

Thrown at crates/infrastructure/src/redis/queries.rs:1023

    async fn read_hset(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
        let result: HashMap<String, String> = conn.hgetall(key).await?;
        let json = serde_json::to_string(&result)?;
        Ok(vec![Bytes::from(json.into_bytes())])
    }

    async fn read_list(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
        let result: Vec<Bytes> = conn.lrange(key, 0, -1).await?;
        Ok(result)
    }
}

fn parse_instrument_key(key: &str, prefix: &str) -> anyhow::Result<InstrumentId> {
    let value = key
        .strip_prefix(prefix)
        .filter(|value| !value.is_empty())
        .ok_or_else(|| anyhow::anyhow!("Invalid instrument key '{key}'"))?;
    InstrumentId::from_str(value)
        .map_err(|e| anyhow::anyhow!("Failed to parse instrument ID from key '{key}': {e}"))
}

fn is_timestamp_field(key: &str) -> bool {
    let expire_match = key == "expire_time_ns";
    let ts_match = key.starts_with("ts_");
    expire_match || ts_match
}

fn convert_timestamps(value: &mut Value) {
    match value {
        Value::Object(map) => {
            for (key, v) in map {
                if is_timestamp_field(key)
                    && let Value::Number(n) = v
                    && let Some(n) = n.as_u64()
                {
                    let dt = Timestamp::from_nanosecond(i128::from(n))
                        .expect("UnixNanos is within Jiff's timestamp range");

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Fix or delete the offending Redis key so it ends with a valid '<SYMBOL>.<VENUE>' suffix.
  2. Validate instrument IDs with InstrumentId::from_str before writing them to Redis in custom tooling.
  3. Check for symbols containing characters incompatible with the identifier format and re-map them.
  4. Re-snapshot the cache to regenerate conforming keys from in-memory InstrumentIds.

Example fix

// before
con.set("instruments:EURUSD", payload); // missing venue -> parse fails
// after
con.set("instruments:EURUSD.IDEAL", payload);
Defensive patterns

Strategy: validation

Validate before calling

// Rust: validate instrument id portion before writing keys
fn instrument_key_is_valid(key: &str, prefix: &str) -> bool {
    key.strip_prefix(prefix)
        .map(|rest| InstrumentId::from_str(rest).is_ok())
        .unwrap_or(false)
}

Try / catch

match load_instruments(&mut con, trader_key, encoding).await {
    Ok(instruments) => instruments,
    Err(e) if e.to_string().contains("Failed to parse instrument ID") => {
        tracing::error!("malformed instrument key: {e}; fix or delete the key");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: load_instruments / load_instrument_closes (via load_all) encountering keys like 'instruments:EURUSD' (missing venue), 'instruments:.IDEAL' (empty symbol), or keys containing unexpected extra segments the InstrumentId parser rejects.

Common situations: Instruments written with non-standard symbol strings; keys hand-crafted by import scripts missing the venue suffix; symbol changes introducing characters unsupported by InstrumentId; colons inside symbols interacting with the delimiter scheme.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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