nautechsystems/nautilus_trader · error · anyhow::Error
Failed to parse fee '{fee_str}': {e}
Error message
Failed to parse fee '{fee_str}': {e} What it means
After confirming the fee string is present, the adapter converts it to a Decimal before computing the fee Money object. This error is thrown when Decimal::from_str fails on the fee string — the value OKX sent is not a valid decimal number.
Source
Thrown at crates/adapters/okx/src/websocket/parse.rs:2153
} else {
anyhow::bail!(
"Cannot determine fill quantity: fill_sz is empty/zero and acc_fill_sz is empty/zero"
);
}
} else {
anyhow::bail!(
"Cannot determine fill quantity: fill_sz='{}' and acc_fill_sz is None",
msg.fill_sz
);
};
let fee_str = msg
.fee
.as_deref()
.filter(|fee| !fee.trim().is_empty())
.ok_or_else(|| anyhow::anyhow!("missing fee for fill report inst_id={}", msg.inst_id))?;
let fee_dec = Decimal::from_str(fee_str)
.map_err(|e| anyhow::anyhow!("Failed to parse fee '{fee_str}': {e}"))?;
let fee_currency = parse_fee_currency(msg.fee_ccy.as_str(), fee_dec, || {
format!("fill report for inst_id={}", msg.inst_id)
});
// OKX sends fees as negative numbers (e.g., "-2.5" for a $2.5 charge), parse_fee negates to positive
let total_fee = parse_fee(Some(fee_str), fee_currency)
.map_err(|e| anyhow::anyhow!("Failed to parse fee={:?}: {}", msg.fee, e))?;
// OKX sends cumulative fees, so we subtract the previous total to get this fill's fee
let commission = if let Some(previous_fee) = previous_fee {
if total_fee.currency == previous_fee.currency {
let incremental = total_fee - previous_fee;
if incremental < Money::zero(fee_currency) {
log::debug!(
"Negative incremental fee detected - likely a maker rebate or fee refund: order_id={}, total_fee={}, previous_fee={}, incremental={}",
msg.ord_id.as_str(),View on GitHub (pinned to 18893faf8b)
Solutions
- Log msg.fee for the failing fill and check what non-decimal text was received.
- Verify no middleware or JSON layer is altering the raw WebSocket payload.
- If OKX changed the fee format (e.g. scientific notation), extend the adapter's parsing to normalize the string before Decimal::from_str.
- Resync fills via REST to confirm the expected format.
Example fix
// before
let fee_dec = Decimal::from_str(fee_str).map_err(...)?;
// after: normalize scientific notation first
let normalized = if fee_str.contains('e') || fee_str.contains('E') {
rust_decimal::Decimal::from_scientific(fee_str)?.to_string()
} else { fee_str.to_string() };
let fee_dec = Decimal::from_str(&normalized).map_err(...)?; Defensive patterns
Strategy: validation
Validate before calling
// Rust: check fee is a plain decimal before the adapter parses it
fn fee_is_decimal(fee: &str) -> bool {
!fee.is_empty() && !fee.contains(['e', 'E', ','])
&& rust_decimal::Decimal::from_str(fee).is_ok()
} Try / catch
if let Err(e) = parse_fill_report(&msg, ...) {
log::warn!("fee parse failed (fee={:?}): {e}", msg.fee);
fetch_fills_via_rest(&msg.inst_id); // fees available authoritatively there
} Prevention
- Sanity-check fee format in the first fills after connecting (regression detector).
- Disable any middleware that rewrites WebSocket JSON payloads.
- Test against recorded real payloads rather than synthetic fixtures.
When it happens
Trigger: parse_fill_report receives a fee string that Decimal::from_str cannot parse: non-numeric text, scientific notation if unsupported, thousands separators, or locale-formatted values.
Common situations: OKX API format changes; corrupted or hand-modified test payloads; proxy/middleware mangling numeric fields in WebSocket JSON.
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 fallback quantity for ord_id={}, sz='{}': {e
- Failed to parse filled quantity for ord_id={}, acc_fill_sz='
- Failed to parse base quantity for ord_id={}, sz='{}': {e}
- Failed to parse liab '{liab_str}': {e}
- Failed to parse spotInUseAmt '{spot_in_use_str}': {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/e0f9bb99f2831c14.
Report an issue: GitHub.