{"record":{"id":"f16d800fde8731c6","repo":"nautechsystems/nautilus_trader","slug":"expected-okx-option-symbol-with-expiry-received","errorCode":null,"errorMessage":"Expected OKX option symbol with expiry, received {symbol}","messagePattern":"Expected OKX option symbol with expiry, received (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/http/client.rs","lineNumber":2842,"sourceCode":"    ) -> anyhow::Result<OKXPriceLimit> {\n        let mut params = GetPriceLimitParamsBuilder::default();\n        params.inst_id(instrument_id.symbol.inner());\n        let params = params.build().map_err(|e| anyhow::anyhow!(e))?;\n\n        let resp = self\n            .inner\n            .get_price_limit(params)\n            .await\n            .map_err(|e| anyhow::anyhow!(e))?;\n\n        resp.first()\n            .cloned()\n            .ok_or_else(|| anyhow::anyhow!(\"No price limit returned from OKX\"))\n    }\n\n    fn option_summary_exp_time(symbol: &str) -> anyhow::Result<Option<String>> {\n        let parts: Vec<&str> = symbol.split('-').collect();\n        anyhow::ensure!(\n            parts.len() >= 5,\n            \"Expected OKX option symbol with expiry, received {symbol}\"\n        );\n        Ok(Some(parts[2].to_string()))\n    }\n\n    /// Requests the latest index price for the `instrument_id` from OKX.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the HTTP request fails or no index price is returned.\n    pub async fn request_index_price(\n        &self,\n        instrument_id: InstrumentId,\n    ) -> anyhow::Result<IndexPriceUpdate> {\n        // Index tickers endpoint requires base pair format (e.g., BTC-USDT)\n        let symbol = instrument_id.symbol.inner();\n        let (base, quote) = parse_base_quote_from_symbol(symbol.as_str())?;","sourceCodeStart":2824,"sourceCodeEnd":2860,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/http/client.rs#L2824-L2860","documentation":"This anyhow::ensure! check validates that an OKX option instrument symbol contains at least 5 dash-separated components (e.g. BTC-USD-250926-60000-C), because the function extracts the expiry date from the third component (parts[2]). If the symbol has fewer segments it cannot contain an expiry, so the library rejects it rather than returning a bogus expiry string.","triggerScenarios":"Calling option_summary_exp_time with a symbol like \"BTC-USDT\" or \"BTC-USD\" (spot/perp style or truncated option symbol) that has fewer than 5 '-' separated parts, e.g. when the instrument cache holds a mis-parsed option symbol.","commonSituations":"Requesting an OKX option instrument/price-limit whose cached symbol was built incorrectly, using a spot or perpetual symbol where an option symbol is expected, or OKX changing/abbreviating symbol formats in a response that gets fed back into this parser.","solutions":["Verify the symbol passed in is a full OKX option symbol with format UNDERLYING-ASSET-EXPIRY-STRIKE-CP (5 parts).","Check how the symbol was constructed/cached (instrument_from_cache / parse) to ensure the option expiry component wasn't dropped.","Log the offending symbol and compare against OKX /api/v5/public/instruments?instType=OPTION output for the correct format.","If you intentionally handle non-option instruments, route them away from option-specific parsing paths."],"exampleFix":"// before\nlet expiry = option_summary_exp_time(\"BTC-USDT\")?; // panics into ensure error\n// after\nlet expiry = match option_summary_exp_time(\"BTC-USD-250926-60000-C\") {\n    Ok(e) => e,\n    Err(err) => { eprintln!(\"skip non-option symbol: {err}\"); return Ok(None); }\n};","handlingStrategy":"validation","validationCode":"fn is_okx_option_symbol(symbol: &str) -> bool {\n    let parts: Vec<&str> = symbol.split('-').collect();\n    parts.len() >= 5\n}\nif !is_okx_option_symbol(&symbol) { return Ok(None); }","typeGuard":"fn as_option_symbol(symbol: &str) -> Option<(&str, &str, &str, &str, &str)> {\n    let p: Vec<&str> = symbol.split('-').collect();\n    if p.len() < 5 { return None; }\n    Some((p[0], p[1], p[2], p[3], p[4]))\n}","tryCatchPattern":"match option_summary_exp_time(&symbol) {\n    Ok(exp) => use(exp),\n    Err(e) => { log::warn!(\"non-option symbol skipped: {e}\"); continue; }\n}","preventionTips":["Only pass OKX OPTION instType instruments into option-specific parsing.","Store the raw exchange symbol verbatim in the instrument cache — never strip segments.","Add a format unit test pinning the 5-part option symbol shape."],"tags":["okx","options","symbol-parsing","validation"],"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-14T05:17:10.506Z"}