nautechsystems/nautilus_trader · error · anyhow::Error
AX instrument product must be non-empty without surrounding
Error message
AX instrument product must be non-empty without surrounding whitespace, was '{product}' What it means
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.
Source
Thrown at crates/adapters/architect_ax/src/http/parse.rs:175
definition: &AxInstrument,
maker_fee: Decimal,
taker_fee: Decimal,
ts_event: UnixNanos,
ts_init: UnixNanos,
) -> anyhow::Result<InstrumentAny> {
let raw_symbol_str = definition.symbol.as_str();
let raw_symbol = Symbol::new(raw_symbol_str);
let instrument_id = InstrumentId::new(raw_symbol, *AX_VENUE);
let symbol_prefix = raw_symbol_str
.split('-')
.next()
.context("Failed to extract symbol prefix")?;
let underlying = match definition.product {
Some(product) => {
let trimmed = product.as_str().trim();
anyhow::ensure!(
!trimmed.is_empty() && trimmed == product.as_str(),
"AX instrument product must be non-empty without surrounding whitespace, was '{product}'"
);
product
}
None => Ustr::from(symbol_prefix),
};
// Derive base code by stripping quote currency suffix if present
// e.g. JPYUSD-PERP → base=JPY, BTC-PERP → base=BTC
let quote_code = definition.quote_currency.as_str();
let base_code = if symbol_prefix.ends_with(quote_code) && symbol_prefix.len() > quote_code.len()
{
&symbol_prefix[..symbol_prefix.len() - quote_code.len()]
} else {
symbol_prefix
};
View on GitHub (pinned to a4b06ed870)
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
Example fix
// before (AX contract metadata)
{"symbol": "BTC-PERP", "product": " BTC "}
// after
{"symbol": "BTC-PERP", "product": "BTC"} Defensive patterns
Strategy: try-catch
Try / catch
for instrument_def in definitions {
match parse_instrument(instrument_def, ts_event, ts_init) {
Ok(instrument) => instruments.push(instrument),
Err(e) if e.to_string().contains("product must be non-empty") => {
log::error!("skipping symbol {}: bad product metadata: {e}", instrument_def.symbol);
quarantine.push(instrument_def.symbol.clone());
}
Err(e) => return Err(e),
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- AX minimum_order_size must be a positive whole number, was {
- AX initial_margin_pct must be positive, was {initial_margin_
- AX maintenance_margin_pct must be positive, was {maintenance
- AX dated contract {} has different quote and settlement curr
- AX maintenance_margin_pct {maintenance_margin_pct} exceeds i
AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16).
Data as JSON: /api/errors/e1998b068c45a063.
Report an issue: GitHub.