nautechsystems/nautilus_trader · error · anyhow::Error
Failed to parse sz='{}' as quote quantity: {}
Error message
Failed to parse sz='{}' as quote quantity: {} What it means
For quote-quantity orders (tgt_ccy=QuoteCcy, or the SPOT/BUY market-order heuristic), the parser treats msg.sz as a quote-currency amount and must parse it as a Decimal before dividing by a conversion price. If the sz string is not a valid decimal number, this error is raised. It indicates a malformed or non-numeric sz field on the OKX order message.
Source
Thrown at crates/adapters/okx/src/websocket/parse.rs:1788
// OKX always returns acc_fill_sz in base currency, but sz depends on tgt_ccy
// Determine if this is a quote-quantity order
// Method 1: Explicit tgt_ccy field set to QuoteCcy
let is_quote_qty_explicit = msg.tgt_ccy == Some(OKXTargetCurrency::QuoteCcy);
// Method 2: Use OKX defaults when tgt_ccy is None (old orders or missing field)
// OKX API defaults for SPOT market orders: BUY orders use quote_ccy, SELL orders use base_ccy
// Note: tgtCcy only applies to SPOT market orders (not limit orders)
// For limit orders, sz is always in base currency regardless of side
let is_quote_qty_heuristic = msg.tgt_ccy.is_none()
&& (msg.inst_type == OKXInstrumentType::Spot || msg.inst_type == OKXInstrumentType::Margin)
&& msg.side == OKXSide::Buy
&& order_type == OrderType::Market;
let (quantity, filled_qty) = if is_quote_qty_explicit || is_quote_qty_heuristic {
// Quote-quantity order: sz is in quote currency, need to convert to base
let sz_quote_dec = Decimal::from_str(&msg.sz).map_err(|e| {
anyhow::anyhow!("Failed to parse sz='{}' as quote quantity: {}", msg.sz, e)
})?;
// Determine the price to use for conversion
// Priority: 1) limit price (px) for limit orders, 2) avg_px for market orders
let conversion_price_dec =
if !is_market_price(&msg.px) {
// Limit order: use the limit price (msg.px)
Some(
Decimal::from_str(&msg.px)
.map_err(|e| anyhow::anyhow!("Failed to parse px='{}': {}", msg.px, e))?,
)
} else if !msg.avg_px.is_empty() && msg.avg_px != "0" {
// Market order with fills: use average fill price
Some(Decimal::from_str(&msg.avg_px).map_err(|e| {
anyhow::anyhow!("Failed to parse avg_px='{}': {}", msg.avg_px, e)
})?)
} else {
NoneView on GitHub (pinned to 18893faf8b)
Solutions
- Log the raw msg.sz and surrounding message JSON to see the actual value received.
- Guard before parsing: skip conversion (treat as base quantity via parse_quantity) when sz is empty or non-numeric.
- Normalize the string (trim whitespace, strip separators) before Decimal::from_str.
- Check for an OKX API format change and update the deserialization/model accordingly.
Example fix
// before (parse.rs:1787)
let sz_quote_dec = Decimal::from_str(&msg.sz).map_err(|e| {
anyhow::anyhow!("Failed to parse sz='{}' as quote quantity: {}", msg.sz, e)
})?;
// after
let sz_quote_dec = if msg.sz.is_empty() {
return Ok(/* fall back to base-quantity parsing or zero quantity */);
} else {
Decimal::from_str(msg.sz.trim()).map_err(|e| {
anyhow::anyhow!("Failed to parse sz='{}' as quote quantity: {}", msg.sz, e)
})?
}; Defensive patterns
Strategy: validation
Validate before calling
// Rust: validate sz is a non-empty decimal string before processing
fn valid_decimal_str(s: &str) -> bool {
!s.trim().is_empty() && Decimal::from_str(s.trim()).is_ok()
}
// gate: if quote-quantity path chosen, require valid_decimal_str(&msg.sz) Type guard
fn parseable_decimal(s: &str) -> Option<Decimal> {
Decimal::from_str(s.trim()).ok()
} Try / catch
match parse_order_status_report(&msg, &instrument, account_id, ts_init) {
Ok(report) => handle(report),
Err(e) if e.to_string().contains("Failed to parse sz") => {
tracing::warn!(ord_id = %msg.ord_id, sz = %msg.sz, "malformed sz on quote-quantity order");
}
Err(e) => return Err(e),
} Prevention
- Never hand-edit recorded WebSocket fixtures; regenerate them from live captures.
- Validate Decimal-parseability of sz/px/avg_px when constructing OKXOrderMsg in tests.
- Trim and normalize numeric strings at the deserialization layer.
- Monitor for OKX API changes to the sz field format for spot market orders.
When it happens
Trigger: A quote-quantity order status update (OKX SPOT market BUY with tgtCcy=quote_ccy, or tgtCcy absent on a SPOT/margin BUY market order) where msg.sz is not parseable by Decimal::from_str — e.g. empty string, scientific notation with unexpected format, thousands separators, or placeholder values from a malformed message.
Common situations: Replaying malformed/hand-edited test fixtures or recorded WebSocket logs with corrupt sz; an OKX API change emitting sz in an unexpected numeric format; deserialization defaulting sz to "" when the field is missing on older messages.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to parse px='{}': {}
- Failed to parse avg_px='{}': {}
- instrument update lock poisoned
- option_summary_family_subs mutex poisoned
- Conditional order types must use OKXAlgoOrderType
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/c624be20ebe82a86.
Report an issue: GitHub.