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

  1. Fetch the raw definition (curl /instrument?symbol=...) and confirm the product field value for the offending symbol
  2. Fix the metadata at the source — correct the product string in AX venue configuration or report it to AX
  3. If upstream cannot be fixed quickly, patch the adapter's deserialization to normalize (trim) product before parsing, via a PR with tests
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/e1998b068c45a063. Report an issue: GitHub.