{"record":{"id":"0193754764e0a493","repo":"nautechsystems/nautilus_trader","slug":"cannot-derive-currency-from-instrument-id","errorCode":null,"errorMessage":"cannot derive currency from {instrument_id}","messagePattern":"cannot derive currency from (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/derive/src/data.rs","lineNumber":2408,"sourceCode":"        InstrumentAny::CryptoOption(_) => Ok(DeriveInstrumentType::Option),\n        InstrumentAny::CurrencyPair(_) => Ok(DeriveInstrumentType::Erc20),\n        other => anyhow::bail!(\"unsupported Derive instrument type for trades: {other:?}\"),\n    }\n}\n\nfn currency_from_instrument_id(instrument_id: &InstrumentId) -> anyhow::Result<&str> {\n    anyhow::ensure!(\n        instrument_id.venue == *DERIVE_VENUE,\n        \"instrument ID `{instrument_id}` is not for venue {}\",\n        DERIVE_VENUE.as_str(),\n    );\n\n    instrument_id\n        .symbol\n        .as_str()\n        .split_once('-')\n        .and_then(|(currency, _)| (!currency.is_empty()).then_some(currency))\n        .ok_or_else(|| anyhow::anyhow!(\"cannot derive currency from {instrument_id}\"))\n}\n\n// Caps the rendered JSON at ~512 bytes for log grep-ability and backs the\n// slice off to a UTF-8 char boundary so a multi-byte codepoint near the cap\n// can never produce a panicking slice.\nfn truncated_payload_snippet(raw: &str) -> String {\n    const MAX_LEN: usize = 512;\n    if raw.len() <= MAX_LEN {\n        return raw.to_string();\n    }\n    let mut end = MAX_LEN;\n    while end > 0 && !raw.is_char_boundary(end) {\n        end -= 1;\n    }\n    format!(\"{}...(truncated)\", &raw[..end])\n}\n\n#[cfg(test)]","sourceCodeStart":2390,"sourceCodeEnd":2426,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/derive/src/data.rs#L2390-L2426","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use Derive instrument IDs in the BASE-QUOTE.VENUE form (e.g. \"ETH-USDC.DERIVE\") so split_once('-') succeeds.","Load instruments via the Derive adapter's instrument provider so symbols follow its canonical format.","Validate the symbol contains a non-empty '-'-separated currency before calling the API.","Inspect the failing instrument ID in the message and correct its naming in config/code."],"exampleFix":"// before\nlet id = InstrumentId::from(\"ETHPERP.DERIVE\"); // no '-' -> cannot derive currency\n// after\nlet id = InstrumentId::from(\"ETH-USDC.DERIVE\");","handlingStrategy":"validation","validationCode":"fn symbol_has_base(id: &InstrumentId) -> bool {\n    id.symbol.as_str().split_once('-')\n        .map_or(false, |(c, _)| !c.is_empty())\n}","typeGuard":"fn parse_base_currency(id: &InstrumentId) -> Option<&str> {\n    id.symbol.as_str().split_once('-')\n        .and_then(|(c, _)| (!c.is_empty()).then_some(c))\n}","tryCatchPattern":"let currency = derive_client.currency_from_instrument_id(&id)\n    .map_err(|e| { log::error!(\"bad Derive symbol {}: {e}\", id); e })?;","preventionTips":["Use BASE-QUOTE.VENUE instrument naming for Derive instruments","Load instruments via the Derive instrument provider so symbols are canonical","Validate symbol format at config-load time rather than at call time"],"tags":["derive","instrument-id","parsing","rust"],"backgroundTag":"invalid-identifier-format","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}