nautechsystems/nautilus_trader · error

Failed to parse position quantity '{}' for instrument {}: {e

Error message

Failed to parse position quantity '{}' for instrument {}: {e:?}

What it means

parse_position_status_report parses the `pos` field of an OKX position response into a Decimal. If the string is empty or not a valid decimal number, the error is raised naming the position quantity and instrument.

Source

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

///
/// - **Long/Short mode** (`posSide="long"` or `"short"`): The `pos` field is always
///   positive regardless of side. Position side is determined from the `posSide` field.
///   Position IDs are suffixed with `-LONG` or `-SHORT` for uniqueness.
///
/// See: <https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-positions>
///
/// # Errors
///
/// Returns an error if any numeric fields cannot be parsed into their target types.
pub fn parse_position_status_report(
    position: &OKXPosition,
    account_id: AccountId,
    instrument_id: InstrumentId,
    size_precision: u8,
    ts_init: UnixNanos,
) -> anyhow::Result<PositionStatusReport> {
    let pos_dec = Decimal::from_str(&position.pos).map_err(|e| {
        anyhow::anyhow!(
            "Failed to parse position quantity '{}' for instrument {}: {e:?}",
            position.pos,
            instrument_id
        )
    })?;

    // For SPOT/MARGIN: determine position side and quantity based on pos_ccy
    // - If pos_ccy = base currency: LONG position, pos is in base currency
    // - If pos_ccy = quote currency: SHORT position, pos is in quote currency (needs conversion)
    // - If pos_ccy is empty: FLAT position (no position)
    let (position_side, quantity_dec) = if position.inst_type == OKXInstrumentType::Spot
        || position.inst_type == OKXInstrumentType::Margin
    {
        // Extract base and quote currencies from instrument symbol
        let (base_ccy, quote_ccy) = parse_base_quote_from_symbol(instrument_id.symbol.as_str())?;

        let pos_ccy = position.pos_ccy.as_str();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check position.pos is a non-empty valid decimal string before calling the parser
  2. Skip or zero-fill positions with empty pos values before parsing
  3. Re-fetch positions from the OKX positions endpoint to replace stale/corrupt records
  4. Add a test fixture matching the actual OKX payload shape

Example fix

// before
let report = parse_position_status_report(&position, account_id, instrument_id, precision, ts_init)?;
// after
if position.pos.is_empty() {
    return Ok(PositionStatusReport::closed(account_id, instrument_id, ts_init));
}
let report = parse_position_status_report(&position, account_id, instrument_id, precision, ts_init)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_position_qty(pos: &str) -> bool {
    !pos.is_empty() && pos.parse::<rust_decimal::Decimal>().is_ok()
}

Try / catch

match parse_position_status_report(&position, account_id, instrument_id, size_precision, ts_init) {
    Ok(report) => reports.push(report),
    Err(e) => tracing::warn!("skipping unparseable position for {instrument_id}: {e:#}"),
}

Prevention

When it happens

Trigger: Calling parse_position_status_report when position.pos is an empty string, contains formatting characters, or is otherwise not parseable by Decimal::from_str.

Common situations: Flat/closed positions where OKX returns an empty pos field; a change in the OKX positions API payload; feeding in a manually crafted or cached JSON record with missing fields.

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/45b9dccc35184ee6. Report an issue: GitHub.