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
- Inspect the raw /instrument payload for the offending instrument and confirm the product value.
- Trim the product value at the source (venue feed or fixture) before it reaches the adapter.
- 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).
- 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
- Sanitize venue payloads (trim strings) in a normalization layer before parsing
- Validate fixtures against the same invariants as production data
- Watch for AX API schema changes affecting the product field
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
- AX dated contract has different quote and settlement…
- AX minimum_order_size must be a positive whole number, was
- Empty underlying for
- `lot_sz` is empty for
- `tick_sz` is empty for
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)