nautechsystems/nautilus_trader · error

cannot derive currency from {instrument_id}

Error message

cannot derive currency from {instrument_id}

What it means

currency_from_instrument_id derives the base currency by splitting the symbol on '-' and taking the first segment. If there is no '-' separator, or the segment before the dash is empty, it cannot determine the currency and bails with this message. It protects downstream Derive API calls from receiving a blank/unknown currency.

Source

Thrown at crates/adapters/derive/src/data.rs:2408

        InstrumentAny::CryptoOption(_) => Ok(DeriveInstrumentType::Option),
        InstrumentAny::CurrencyPair(_) => Ok(DeriveInstrumentType::Erc20),
        other => anyhow::bail!("unsupported Derive instrument type for trades: {other:?}"),
    }
}

fn currency_from_instrument_id(instrument_id: &InstrumentId) -> anyhow::Result<&str> {
    anyhow::ensure!(
        instrument_id.venue == *DERIVE_VENUE,
        "instrument ID `{instrument_id}` is not for venue {}",
        DERIVE_VENUE.as_str(),
    );

    instrument_id
        .symbol
        .as_str()
        .split_once('-')
        .and_then(|(currency, _)| (!currency.is_empty()).then_some(currency))
        .ok_or_else(|| anyhow::anyhow!("cannot derive currency from {instrument_id}"))
}

// Caps the rendered JSON at ~512 bytes for log grep-ability and backs the
// slice off to a UTF-8 char boundary so a multi-byte codepoint near the cap
// can never produce a panicking slice.
fn truncated_payload_snippet(raw: &str) -> String {
    const MAX_LEN: usize = 512;
    if raw.len() <= MAX_LEN {
        return raw.to_string();
    }
    let mut end = MAX_LEN;
    while end > 0 && !raw.is_char_boundary(end) {
        end -= 1;
    }
    format!("{}...(truncated)", &raw[..end])
}

#[cfg(test)]

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Use Derive instrument IDs in the BASE-QUOTE.VENUE form (e.g. "ETH-USDC.DERIVE") so split_once('-') succeeds.
  2. Load instruments via the Derive adapter's instrument provider so symbols follow its canonical format.
  3. Validate the symbol contains a non-empty '-'-separated currency before calling the API.
  4. Inspect the failing instrument ID in the message and correct its naming in config/code.

Example fix

// before
let id = InstrumentId::from("ETHPERP.DERIVE"); // no '-' -> cannot derive currency
// after
let id = InstrumentId::from("ETH-USDC.DERIVE");
Defensive patterns

Strategy: validation

Validate before calling

fn symbol_has_base(id: &InstrumentId) -> bool {
    id.symbol.as_str().split_once('-')
        .map_or(false, |(c, _)| !c.is_empty())
}

Type guard

fn parse_base_currency(id: &InstrumentId) -> Option<&str> {
    id.symbol.as_str().split_once('-')
        .and_then(|(c, _)| (!c.is_empty()).then_some(c))
}

Try / catch

let currency = derive_client.currency_from_instrument_id(&id)
    .map_err(|e| { log::error!("bad Derive symbol {}: {e}", id); e })?;

Prevention

When it happens

Trigger: Passing an InstrumentId whose symbol has no '-' delimiter (e.g. "ETHPERP.DERIVE") or whose symbol starts with '-' (empty first segment) into any Derive code path that needs the base currency.

Common situations: Custom or synthetically constructed instrument symbols that don't follow Nautilus BASE-QUOTE convention; forward-fill or option symbols that use a different naming scheme; hand-typed instrument names in config.

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/0193754764e0a493. Report an issue: GitHub.