{"record":{"id":"4cfff323b805104f","repo":"nautechsystems/nautilus_trader","slug":"invalid-underlying-for-expected-format-b","errorCode":null,"errorMessage":"Invalid underlying '{}' for {}: expected format 'BASE-QUOTE'","messagePattern":"Invalid underlying '(.+?)' for (.+?): expected format 'BASE-QUOTE'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/common/parse.rs","lineNumber":2148,"sourceCode":"///\n/// Returns an error if the instrument definition cannot be parsed.\n///\n/// # Panics\n///\n/// Panics if the constructed instrument fails validation.\npub fn parse_swap_instrument(\n    definition: &OKXInstrument,\n    margin_init: Option<Decimal>,\n    margin_maint: Option<Decimal>,\n    maker_fee: Option<Decimal>,\n    taker_fee: Option<Decimal>,\n    ts_init: UnixNanos,\n) -> anyhow::Result<InstrumentAny> {\n    validate_underlying(definition.inst_id, definition.uly)?;\n\n    let context = format!(\"SWAP instrument {}\", definition.inst_id);\n    let (base_currency, quote_currency) = definition.uly.split_once('-').ok_or_else(|| {\n        anyhow::anyhow!(\n            \"Invalid underlying '{}' for {}: expected format 'BASE-QUOTE'\",\n            definition.uly,\n            definition.inst_id\n        )\n    })?;\n\n    let instrument_id = parse_instrument_id(definition.inst_id);\n    let raw_symbol = Symbol::from_ustr_unchecked(definition.inst_id);\n    let base_currency = Currency::get_or_create_crypto_with_context(base_currency, Some(&context));\n    let quote_currency =\n        Currency::get_or_create_crypto_with_context(quote_currency, Some(&context));\n    let settlement_currency =\n        Currency::get_or_create_crypto_with_context(definition.settle_ccy, Some(&context));\n    let is_inverse = match definition.ct_type {\n        OKXContractType::Linear => false,\n        OKXContractType::Inverse => true,\n        OKXContractType::None => {\n            anyhow::bail!(","sourceCodeStart":2130,"sourceCodeEnd":2166,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/common/parse.rs#L2130-L2166","documentation":"parse_swap_instrument splits the OKX `uly` (underlying) string on '-' to derive base and quote currencies, expecting the 'BASE-QUOTE' format. If the split yields no dash-separated pair, the library throws this error. `validate_underlying` has already rejected an empty underlying, so this fires only when a non-empty uly lacks the expected structure.","triggerScenarios":"Calling parse_instrument_any -> parse_swap_instrument with a definition whose uly is non-empty but does not contain a '-' (e.g. \"BTCUSD\" instead of \"BTC-USD\", or a single-token underlying).","commonSituations":"Fixture data written without the exchange's BASE-QUOTE convention; a new OKX instrument family whose underlying uses a different separator; manually constructed definitions in tests or custom adapters feeding the parser.","solutions":["Print definition.uly for the failing inst_id and check it contains exactly a 'BASE-QUOTE' pair like \"BTC-USD\".","Fix the source data so uly matches OKX's 'BTC-USD-TIMING' underlying format (the parser splits on the first '-').","Confirm validate_underlying passed; if uly should be empty, fix the instrument definition instead.","If a new OKX format is genuinely in play, update the parser's split logic upstream."],"exampleFix":"// before\n\"uly\": \"BTCUSD\"\n// after\n\"uly\": \"BTC-USD\"","handlingStrategy":"validation","validationCode":"fn has_base_quote_uly(uly: &str) -> bool {\n    !uly.trim().is_empty() && uly.contains('-') && uly.split('-').next().map_or(false, |b| !b.is_empty())\n}\n// guard: assert!(has_base_quote_uly(&definition.uly), \"bad uly: {}\", definition.uly);","typeGuard":"fn parse_uly_pair(uly: &str) -> Option<(&str, &str)> {\n    uly.split_once('-')\n}","tryCatchPattern":"match parse_instrument_any(&definition, ts_init) {\n    Ok(inst) => inst,\n    Err(e) if e.to_string().contains(\"expected format 'BASE-QUOTE'\") => {\n        tracing::warn!(inst_id = %definition.inst_id, \"skipping SWAP with malformed uly '{}': {e}\", definition.uly);\n        continue;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always model OKX underlyings as 'BASE-QUOTE' strings (e.g. BTC-USD) exactly as the exchange reports them.","Run validate_underlying plus a contains('-') check before constructing definitions.","Derive uly from the live instruments response instead of composing it manually.","Add a unit test asserting every fixture uly splits on '-'."],"tags":["okx","swap","instrument-parsing","identifier-format"],"backgroundTag":"invalid-argument-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"}