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

The AX instrument definition's `product` field, when present, must be a non-empty string with no leading or trailing whitespace. The adapter trims the value and rejects it if trimming changes it or if it is empty, because the product underlies symbol parsing and instrument ID construction.

Solutions

  1. Inspect the raw /instrument payload for the offending instrument and confirm the product value.
  2. Trim the product value at the source (venue feed or fixture) before it reaches the adapter.
  3. If the API now returns padded values, report/work around by normalizing the payload upstream (e.g. in a proxy or by updating the adapter's deserialization).
  4. Update stale fixtures/mocks to carry clean product values.

Example fix

// before (fixture)
{ "symbol": "BTC-USDT", "product": " BTC " }
// after
{ "symbol": "BTC-USDT", "product": "BTC" }
Defensive patterns

Strategy: validation

Validate before calling

fn check_product(product: Option<&str>) -> bool {
    match product {
        None => true,
        Some(p) => !p.trim().is_empty() && p.trim() == p,
    }
}

Type guard

fn has_clean_product(d: &InstrumentDefinition) -> bool {
    d.product.as_ref().map_or(true, |p| !p.trim().is_empty() && p.trim() == p.as_str())
}

Prevention

When it happens

Trigger: Calling request_instruments or request_instrument, which invoke parse_instrument, on an AX instrument definition whose `product` is an empty string, whitespace-only, or contains surrounding whitespace (e.g. " BTC" or "BTC ").

Common situations: Upstream AX API schema change or data-entry error in the exchange's instrument metadata, hand-edited mock/fixture JSON with a malformed product field, or a stale adapter parsing a newer API payload format.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at crates/adapters/architect_ax/src/http/parse.rs:179

    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.trim();
            anyhow::ensure!(
                !trimmed.is_empty() && trimmed == product,
                "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 18893faf8b)