{"record":{"id":"c80a499500ba527d","repo":"nautechsystems/nautilus_trader","slug":"unknown-position-currency-pos-ccy-for-instrume","errorCode":null,"errorMessage":"Unknown position currency '{pos_ccy}' for instrument {instrument_id} (base={base_ccy}, quote={quote_ccy})","messagePattern":"Unknown position currency '(.+?)' for instrument (.+?) \\(base=(.+?), quote=(.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/common/parse.rs","lineNumber":1087,"sourceCode":"            // Use Decimal arithmetic to avoid floating-point precision errors\n            let avg_px_str = if position.avg_px.is_empty() {\n                // If no avg_px, use mark_px as fallback\n                &position.mark_px\n            } else {\n                &position.avg_px\n            };\n            let avg_px_dec = Decimal::from_str(avg_px_str)?;\n\n            if avg_px_dec.is_zero() {\n                anyhow::bail!(\n                    \"Cannot convert SHORT position from quote to base: avg_px is zero for {instrument_id}\"\n                );\n            }\n\n            let quantity_dec = (pos_dec.abs() / avg_px_dec).round_dp(size_precision as u32);\n            (PositionSide::Short, quantity_dec)\n        } else {\n            anyhow::bail!(\n                \"Unknown position currency '{pos_ccy}' for instrument {instrument_id} (base={base_ccy}, quote={quote_ccy})\"\n            );\n        }\n    } else {\n        // For SWAP/FUTURES/OPTION: use existing logic\n        // Determine position side based on OKX position mode:\n        // - Net mode: posSide=\"net\", uses signed quantities (positive=long, negative=short)\n        // - Long/Short mode: posSide=\"long\"/\"short\", quantities are always positive, side from field\n        let side = match position.pos_side {\n            OKXPositionSide::Net | OKXPositionSide::None => {\n                // Net mode: derive side from signed quantity\n                if pos_dec.is_sign_positive() && !pos_dec.is_zero() {\n                    PositionSide::Long\n                } else if pos_dec.is_sign_negative() {\n                    PositionSide::Short\n                } else {\n                    PositionSide::Flat\n                }","sourceCodeStart":1069,"sourceCodeEnd":1105,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/common/parse.rs#L1069-L1105","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log the instrument_id with its base/quote currencies and compare against the position's `ccy` field; verify they are consistent","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","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","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"],"exampleFix":"// before\nanyhow::bail!(\"Unknown position currency '{pos_ccy}' for instrument {instrument_id} ...\");\n// after\n// ensure pos_ccy matches an expected settlement currency, e.g. accept quote-currency margin\nif pos_ccy == quote_ccy {\n    return Ok((PositionSide::Short, quantity_dec));\n}\nanyhow::bail!(\"Unknown position currency '{pos_ccy}' for instrument {instrument_id} ...\");","handlingStrategy":"validation","validationCode":"// Rust: validate before calling the parser\nfn valid_position_ccy(pos_ccy: &str, base: &str, quote: &str) -> bool {\n    pos_ccy == base || pos_ccy == quote\n}\nif !valid_position_ccy(position.ccy, instrument.base_currency(), instrument.quote_currency()) {\n    // skip or map the settlement currency before parsing\n    return Ok(None);\n}","typeGuard":null,"tryCatchPattern":"match parse_position_status_report(&position, &instrument) {\n    Ok(report) => reports.push(report),\n    Err(e) if e.to_string().contains(\"Unknown position currency\") => {\n        log::warn!(\"skipping position with unsupported ccy: {e}\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Confirm the OKX account mode (single vs multi-currency margin) matches what the parser expects","Keep instrument definitions in sync with positions (same fetch cycle)","Log ccy/base/quote on mismatch to diagnose settlement-currency cases"],"tags":["rust","okx","adapter","parsing","positions"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}