nautechsystems/nautilus_trader · error
Failed to parse liab '{liab_str}': {e}
Error message
Failed to parse liab '{liab_str}': {e} What it means
parse_spot_margin_position_from_balance parses the OKX balance liability field (liab, or "0" when absent) into a Decimal. This error is raised when the liab string is not a valid decimal number, so the spot margin position cannot be derived from the balance record.
Source
Thrown at crates/adapters/okx/src/common/parse.rs:974
account_id: AccountId,
instrument_id: InstrumentId,
size_precision: u8,
ts_init: UnixNanos,
) -> anyhow::Result<Option<PositionStatusReport>> {
// OKX returns empty strings for zero values, normalize to "0" before parsing
let liab_str = if balance.liab.trim().is_empty() {
"0"
} else {
balance.liab.trim()
};
let spot_in_use_str = if balance.spot_in_use_amt.trim().is_empty() {
"0"
} else {
balance.spot_in_use_amt.trim()
};
let liab_dec = Decimal::from_str(liab_str)
.map_err(|e| anyhow::anyhow!("Failed to parse liab '{liab_str}': {e}"))?;
let spot_in_use_dec = Decimal::from_str(spot_in_use_str)
.map_err(|e| anyhow::anyhow!("Failed to parse spotInUseAmt '{spot_in_use_str}': {e}"))?;
// Skip if no margin position (no liability and no spot in use)
if liab_dec.is_zero() && spot_in_use_dec.is_zero() {
return Ok(None);
}
// Check if spotInUseAmt is zero first
if spot_in_use_dec.is_zero() {
// No position if spotInUseAmt is zero (regardless of liability)
return Ok(None);
}
// Position side based on spotInUseAmt sign
let (position_side, quantity_dec) = if spot_in_use_dec.is_sign_negative() {
// Negative spotInUseAmt = sold (short position)
(PositionSide::Short, spot_in_use_dec.abs())View on GitHub (pinned to 18893faf8b)
Solutions
- Log the raw liab string from the balance payload for the failing currency
- Treat empty or missing liab as "0" before calling the parser (the code already defaults None to "0"; check empty strings too)
- Normalize/sanitize the balance record upstream (trim, strip separators) before parsing
- Upgrade the OKX adapter if a newer version handles the field format
Example fix
// before
let liab_dec = Decimal::from_str(liab_str).map_err(|e| anyhow!("Failed to parse liab '{liab_str}': {e}"))?;
// after
let liab_str = liab_str.trim();
let liab_dec = if liab_str.is_empty() { Decimal::ZERO } else { Decimal::from_str(liab_str).map_err(|e| anyhow!("Failed to parse liab '{liab_str}': {e}"))? }; Defensive patterns
Strategy: validation
Validate before calling
let liab = balance.liab.as_deref().unwrap_or("0").trim();
if liab.is_empty() || rust_decimal::Decimal::from_str(liab).is_err() { /* default to "0" or skip record */ } Type guard
fn parseable_decimal(s: Option<&str>) -> bool {
s.unwrap_or("0").trim().pipe(|t| !t.is_empty() && rust_decimal::Decimal::from_str(t).is_ok())
} Try / catch
match parse_spot_margin_position_from_balance(&balance, ...) {
Ok(Some(pos)) => handle(pos),
Ok(None) => {},
Err(e) if e.to_string().contains("Failed to parse liab") => log::warn!("bad liab '{}': {e}", balance.liab),
Err(e) => return Err(e),
} Prevention
- Normalize empty/missing numeric fields to "0" before parsing
- Validate all balance numeric strings after any OKX API version bump
- Sanitize recorded fixtures to realistic values
- Add unit tests for empty-string balance fields
When it happens
Trigger: balance.liab (or the liability currency field) contains an empty, non-numeric, or malformed string (e.g. "", "NaN", thousands separators) when parsing an OKX spot margin balance response.
Common situations: OKX returning empty liab for currencies with no position but unexpected formats; API schema changes; mocked/recorded fixtures with placeholder values; locale-formatted numbers from custom middleware.
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 spotInUseAmt '{spot_in_use_str}': {e}
- 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 position quantity '{}' for instrument {}: {e
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/1dbc2788d88512db.
Report an issue: GitHub.