nautechsystems/nautilus_trader · error

Unknown position currency '{pos_ccy}' for instrument {instru

Error message

Unknown position currency '{pos_ccy}' for instrument {instrument_id} (base={base_ccy}, quote={quote_ccy})

What it means

parse_position_status_report maps an OKX position report to a Nautilus PositionStatusReport, deriving the position side from the position currency. It only recognizes the instrument's base currency (or quote currency in some inverse/margin cases); any other currency string makes side determination impossible, so it bails with this message naming the position currency and the instrument's base/quote currencies.

Source

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

            // Use Decimal arithmetic to avoid floating-point precision errors
            let avg_px_str = if position.avg_px.is_empty() {
                // If no avg_px, use mark_px as fallback
                &position.mark_px
            } else {
                &position.avg_px
            };
            let avg_px_dec = Decimal::from_str(avg_px_str)?;

            if avg_px_dec.is_zero() {
                anyhow::bail!(
                    "Cannot convert SHORT position from quote to base: avg_px is zero for {instrument_id}"
                );
            }

            let quantity_dec = (pos_dec.abs() / avg_px_dec).round_dp(size_precision as u32);
            (PositionSide::Short, quantity_dec)
        } else {
            anyhow::bail!(
                "Unknown position currency '{pos_ccy}' for instrument {instrument_id} (base={base_ccy}, quote={quote_ccy})"
            );
        }
    } else {
        // For SWAP/FUTURES/OPTION: use existing logic
        // Determine position side based on OKX position mode:
        // - Net mode: posSide="net", uses signed quantities (positive=long, negative=short)
        // - Long/Short mode: posSide="long"/"short", quantities are always positive, side from field
        let side = match position.pos_side {
            OKXPositionSide::Net | OKXPositionSide::None => {
                // Net mode: derive side from signed quantity
                if pos_dec.is_sign_positive() && !pos_dec.is_zero() {
                    PositionSide::Long
                } else if pos_dec.is_sign_negative() {
                    PositionSide::Short
                } else {
                    PositionSide::Flat
                }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Log the instrument_id with its base/quote currencies and compare against the position's `ccy` field; verify they are consistent
  2. Confirm the instrument_id used to build the report matches the instrument the OKX position (`instId`) refers to, and re-fetch the instrument definition if not
  3. Check whether the account is in multi-currency/portfolio margin mode where `ccy` is a settlement currency; use the net-mode/single-currency mapping or extend the currency match logic for that settlement currency
  4. If a new legit currency case exists (e.g. quote-currency margined inverse positions), update the match arms in parse_position_status_report to accept it

Example fix

// before
anyhow::bail!("Unknown position currency '{pos_ccy}' for instrument {instrument_id} ...");
// after
// ensure pos_ccy matches an expected settlement currency, e.g. accept quote-currency margin
if pos_ccy == quote_ccy {
    return Ok((PositionSide::Short, quantity_dec));
}
anyhow::bail!("Unknown position currency '{pos_ccy}' for instrument {instrument_id} ...");
Defensive patterns

Strategy: validation

Validate before calling

// Rust: validate before calling the parser
fn valid_position_ccy(pos_ccy: &str, base: &str, quote: &str) -> bool {
    pos_ccy == base || pos_ccy == quote
}
if !valid_position_ccy(position.ccy, instrument.base_currency(), instrument.quote_currency()) {
    // skip or map the settlement currency before parsing
    return Ok(None);
}

Try / catch

match parse_position_status_report(&position, &instrument) {
    Ok(report) => reports.push(report),
    Err(e) if e.to_string().contains("Unknown position currency") => {
        log::warn!("skipping position with unsupported ccy: {e}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling parse_position_status_report with an OKX position payload whose `ccy` field is neither the instrument's base currency nor its quote currency — e.g. a settlement-currency value like 'USD' or 'USDT' for a SWAP where ccy reflects margin/settlement rather than base/quote, or a stale/mismatched instrument_id whose currencies don't match the position's ccy.

Common situations: Developers parsing OKX portfolio-margin or multi-currency-mode accounts where OKX reports positions with the margin currency (USDT/USD) instead of the base currency; using position data fetched for one instrument type with instrument definitions of another; OKX changing the ccy semantics for new instrument types.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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