nautechsystems/nautilus_trader · error

Failed to parse spotInUseAmt '{spot_in_use_str}': {e}

Error message

Failed to parse spotInUseAmt '{spot_in_use_str}': {e}

What it means

The sibling of the liab parse: parse_spot_margin_position_from_balance parses spot_in_use_amt (trimmed) into a Decimal to detect whether a spot margin position exists. This error is raised when spot_in_use_amt is not a valid decimal number.

Source

Thrown at crates/adapters/okx/src/common/parse.rs:976

    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())
    } else {
        // Positive spotInUseAmt = bought (long position)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Log the raw spot_in_use_amt value for the failing currency
  2. Default empty strings to "0" before parsing so the no-position skip path (returns Ok(None)) works
  3. Sanitize the balance payload upstream before calling the parser
  4. Upgrade the OKX adapter in case newer versions normalize this field

Example fix

// before
let spot_in_use_dec = Decimal::from_str(spot_in_use_str).map_err(|e| anyhow!("Failed to parse spotInUseAmt '{spot_in_use_str}': {e}"))?;
// after
let spot_in_use_str = spot_in_use_str.trim();
let spot_in_use_dec = if spot_in_use_str.is_empty() { Decimal::ZERO } else { Decimal::from_str(spot_in_use_str).map_err(|e| anyhow!("Failed to parse spotInUseAmt '{spot_in_use_str}': {e}"))? };
Defensive patterns

Strategy: validation

Validate before calling

let siu = balance.spot_in_use_amt.as_deref().unwrap_or("0").trim();
if siu.is_empty() || rust_decimal::Decimal::from_str(siu).is_err() { /* default to "0" or skip record */ }

Type guard

fn valid_spot_in_use(s: &str) -> bool {
    let t = s.trim(); !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("spotInUseAmt") => log::warn!("bad spot_in_use_amt '{}': {e}", balance.spot_in_use_amt),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: balance.spot_in_use_amt contains an empty, malformed, or non-numeric string in an OKX spot margin balance record, after trimming.

Common situations: Empty strings returned by OKX for currencies without margin usage; API response schema changes; fixtures with placeholder values; custom middleware altering number formatting.

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.

Related errors


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