{"record":{"id":"e1998b068c45a063","repo":"nautechsystems/nautilus_trader","slug":"ax-instrument-product-must-be-non-empty-without-su","errorCode":null,"errorMessage":"AX instrument product must be non-empty without surrounding whitespace, was '{product}'","messagePattern":"AX instrument product must be non-empty without surrounding whitespace, was '(.+?)'","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/http/parse.rs","lineNumber":175,"sourceCode":"    definition: &AxInstrument,\n    maker_fee: Decimal,\n    taker_fee: Decimal,\n    ts_event: UnixNanos,\n    ts_init: UnixNanos,\n) -> anyhow::Result<InstrumentAny> {\n    let raw_symbol_str = definition.symbol.as_str();\n    let raw_symbol = Symbol::new(raw_symbol_str);\n    let instrument_id = InstrumentId::new(raw_symbol, *AX_VENUE);\n\n    let symbol_prefix = raw_symbol_str\n        .split('-')\n        .next()\n        .context(\"Failed to extract symbol prefix\")?;\n\n    let underlying = match definition.product {\n        Some(product) => {\n            let trimmed = product.as_str().trim();\n            anyhow::ensure!(\n                !trimmed.is_empty() && trimmed == product.as_str(),\n                \"AX instrument product must be non-empty without surrounding whitespace, was '{product}'\"\n            );\n            product\n        }\n        None => Ustr::from(symbol_prefix),\n    };\n\n    // Derive base code by stripping quote currency suffix if present\n    // e.g. JPYUSD-PERP → base=JPY, BTC-PERP → base=BTC\n    let quote_code = definition.quote_currency.as_str();\n    let base_code = if symbol_prefix.ends_with(quote_code) && symbol_prefix.len() > quote_code.len()\n    {\n        &symbol_prefix[..symbol_prefix.len() - quote_code.len()]\n    } else {\n        symbol_prefix\n    };\n","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/http/parse.rs#L157-L193","documentation":"When an AX instrument definition includes a product field, the parser requires it to be non-empty and identical to its own trim() — no leading/trailing whitespace. The product becomes the instrument's underlying, so padded or blank values would produce corrupted instruments and are rejected outright.","triggerScenarios":"GET /instruments or GET /instrument returns \"product\": \" \", \"product\": \"BTC \", or \"product\": \"\" for some symbol. Typically a newly listed market with sloppy venue metadata or a data-entry error on the AX side.","commonSituations":"A new listing appears with unclean metadata and every subsequent instruments load for the account fails on that symbol; environments fed by manual contract configuration (test venues) with whitespace copied from spreadsheets.","solutions":["Fetch the raw definition (curl /instrument?symbol=...) and confirm the product field value for the offending symbol","Fix the metadata at the source — correct the product string in AX venue configuration or report it to AX","If upstream cannot be fixed quickly, patch the adapter's deserialization to normalize (trim) product before parsing, via a PR with tests","Isolate the bad symbol: load instruments individually so one failure does not block the rest"],"exampleFix":"// before (AX contract metadata)\n{\"symbol\": \"BTC-PERP\", \"product\": \" BTC \"}\n// after\n{\"symbol\": \"BTC-PERP\", \"product\": \"BTC\"}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"for instrument_def in definitions {\n    match parse_instrument(instrument_def, ts_event, ts_init) {\n        Ok(instrument) => instruments.push(instrument),\n        Err(e) if e.to_string().contains(\"product must be non-empty\") => {\n            log::error!(\"skipping symbol {}: bad product metadata: {e}\", instrument_def.symbol);\n            quarantine.push(instrument_def.symbol.clone());\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Lint venue contract metadata for empty or whitespace-padded strings before publishing","Load instruments per-symbol in tooling so one bad definition does not block the whole set","Report metadata regressions to AX promptly — new listings are the usual source"],"tags":["architect-ax","instrument","validation","metadata","parsing"],"backgroundTag":"invalid-instrument-metadata","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}