{"record":{"id":"d6b21d9977c7892a","repo":"nautechsystems/nautilus_trader","slug":"cannot-convert-short-position-from-quote-to-base","errorCode":null,"errorMessage":"Cannot convert SHORT position from quote to base: avg_px is zero for {instrument_id}","messagePattern":"Cannot convert SHORT position from quote to base: avg_px is zero for (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/common/parse.rs","lineNumber":1079,"sourceCode":"        if pos_ccy.is_empty() || pos_dec.is_zero() {\n            // Flat position: no position or zero quantity\n            (PositionSide::Flat, Decimal::ZERO)\n        } else if pos_ccy == base_ccy {\n            // Long position: pos_ccy is base currency, pos is already in base\n            (PositionSide::Long, pos_dec.abs())\n        } else if pos_ccy == quote_ccy {\n            // Short position: pos_ccy is quote currency, need to convert to base\n            // 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 => {","sourceCodeStart":1061,"sourceCodeEnd":1097,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/common/parse.rs#L1061-L1097","documentation":"In parse_position_status_report, a SHORT position reported in quote currency must be converted to base-currency quantity by dividing the absolute position size by the average price. If avg_px parses to zero the division is undefined, so the parser bails rather than emitting a bogus quantity.","triggerScenarios":"OKX position report for a SHORT position where the average price field is '0' or otherwise zero — typically a stale/just-closed position record or a venue field-population quirk in long/short (hedge) mode.","commonSituations":"Polling positions right after a close when OKX still returns the row with avgPx=0; hedge-mode accounts with empty side rows; netting mode quirks producing zero-priced SHORT rows.","solutions":["Skip/filter position rows with zero avg_px before parsing (treat as flat/closed)","Refresh position status after a short delay instead of parsing the stale zero-avg-px snapshot","If it persists, verify the OKX account mode (net vs long/short) matches the adapter configuration"],"exampleFix":"// before\nlet report = parse_position_status_report(&row)?;\n// after\nif row.avg_px == \"0\" { continue; } // skip zero-avg-px SHORT rows\nlet report = parse_position_status_report(&row)?;","handlingStrategy":"validation","validationCode":"// Rust\nif pos_side == \"short\" && avg_px_str.parse::<Decimal>().map(|d| d.is_zero()).unwrap_or(true) {\n    // skip: position effectively closed, cannot convert quote->base\n    return Ok(None);\n}","typeGuard":null,"tryCatchPattern":"// Rust\nmatch parse_position_status_report(&row) {\n    Err(e) if e.to_string().contains(\"avg_px is zero\") => {\n        debug!(\"ignoring stale SHORT row with zero avg_px\");\n        Ok(vec![])\n    }\n    other => other.map(|r| vec![r]),\n}","preventionTips":["Treat zero avg_px rows as closed positions and skip them","Re-poll positions shortly after close events","Confirm account mode (net vs long/short) matches adapter config"],"tags":["rust","okx","position-parsing","division-by-zero"],"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-14T05:17:10.506Z"}