{"record":{"id":"d1a4298cd9d92ae9","repo":"nautechsystems/nautilus_trader","slug":"portfolio-message-has-empty-currency-code","errorCode":null,"errorMessage":"Portfolio message has empty currency code","messagePattern":"Portfolio message has empty currency code","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/deribit/src/common/parse.rs","lineNumber":697,"sourceCode":"/// subscription channel into Nautilus account state events.\n///\n/// # Returns\n///\n/// An `AccountState` containing balances and margin information.\n///\n/// # Errors\n///\n/// Returns an error if Money conversion fails for any balance field.\npub fn parse_portfolio_to_account_state(\n    portfolio: &DeribitPortfolioMsg,\n    account_id: AccountId,\n    ts_init: UnixNanos,\n) -> anyhow::Result<AccountState> {\n    let ccy_str = portfolio.currency.trim();\n\n    // Skip empty currency codes\n    if ccy_str.is_empty() {\n        anyhow::bail!(\"Portfolio message has empty currency code\");\n    }\n\n    let currency = Currency::get_or_create_crypto_with_context(\n        ccy_str,\n        Some(\"DERIBIT - Parsing portfolio update\"),\n    );\n\n    // See `parse_account_state` for the rationale: cross-margin uses equity and\n    // `available_withdrawal_funds` (per-currency consistency, conservative free balance);\n    // segregated uses `margin_balance` and `available_funds` (per-currency scoped).\n    let is_cross_margin = portfolio.cross_collateral_enabled.unwrap_or(false);\n    let (total, free) = if is_cross_margin {\n        (\n            portfolio.equity,\n            portfolio\n                .available_withdrawal_funds\n                .unwrap_or(Decimal::ZERO),\n        )","sourceCodeStart":679,"sourceCodeEnd":715,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/deribit/src/common/parse.rs#L679-L715","documentation":"parse_portfolio_to_account_state converts a Deribit portfolio update into an AccountState keyed by currency. A portfolio message with a blank currency string cannot identify the asset, so it is rejected.","triggerScenarios":"Deribit sends a portfolio message whose currency field is empty or whitespace-only after trimming.","commonSituations":"Malformed/empty Deribit websocket payloads, API schema changes, upstream bugs in portfolio snapshot construction.","solutions":["Skip or log the message instead of hard-failing the stream","Validate the Deribit payload schema; update the client model if Deribit changed it","Check for upstream Deribit issues affecting portfolio channel"],"exampleFix":"// before\nif ccy_str.is_empty() {\n    anyhow::bail!(\"Portfolio message has empty currency code\");\n}\n// after\nif ccy_str.is_empty() {\n    log::debug!(\"Skipping portfolio message with empty currency\");\n    return Ok(None); // or continue\n}","handlingStrategy":"try-catch","validationCode":"if portfolio.currency.trim().is_empty() {\n    // skip message before parsing\n    return Ok(());\n}","typeGuard":"fn has_currency(p: &DeribitPortfolio) -> bool { !p.currency.trim().is_empty() }","tryCatchPattern":"match parse_portfolio_to_account_state(&portfolio, ts_init) {\n    Err(e) if e.to_string().contains(\"empty currency code\") => {\n        debug!(\"ignoring empty portfolio update\");\n    }\n    other => other?,\n}","preventionTips":["Sanity-check websocket payloads before parsing","Watch for Deribit API schema changes in portfolio messages","Skip-and-log non-conforming messages to keep streams alive"],"tags":["deribit","account","currency","parsing"],"backgroundTag":"empty-required-field","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"}