{"record":{"id":"2e306df93091de24","repo":"nautechsystems/nautilus_trader","slug":"expected-2-outcomes-received","errorCode":null,"errorMessage":"Expected 2 outcomes, received {}","messagePattern":"Expected 2 outcomes, received (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/polymarket/src/http/parse.rs","lineNumber":125,"sourceCode":"            .find_map(|event| event.game_id.clone())\n    });\n\n    let token_ids: Vec<String> = serde_json::from_str(&market.clob_token_ids).map_err(|e| {\n        anyhow::anyhow!(\n            \"Failed to parse clob_token_ids '{}': {e}\",\n            market.clob_token_ids\n        )\n    })?;\n\n    if token_ids.len() != 2 {\n        anyhow::bail!(\"Expected 2 token IDs, received {}\", token_ids.len());\n    }\n\n    let outcomes: Vec<String> = serde_json::from_str(&market.outcomes)\n        .map_err(|e| anyhow::anyhow!(\"Failed to parse outcomes '{}': {e}\", market.outcomes))?;\n\n    if outcomes.len() != 2 {\n        anyhow::bail!(\"Expected 2 outcomes, received {}\", outcomes.len());\n    }\n\n    let tick_size = market\n        .order_price_min_tick_size\n        .unwrap_or(DEFAULT_TICK_SIZE);\n    let price_precision = POLYMARKET_PRICE_PRECISION;\n\n    // Polymarket charges fees using `feeSchedule.rate` on the Gamma market.\n    // Only takers pay; makers are always zero.\n    // Reference: https://docs.polymarket.com/trading/fees\n    let maker_fee: Option<Decimal> = market.fee_schedule.as_ref().map(|_| Decimal::ZERO);\n    let taker_fee: Option<Decimal> = market.fee_schedule.as_ref().map(|fs| fs.rate);\n\n    let min_size = market.order_min_size;\n\n    let active = market.active.unwrap_or(false)\n        && !market.closed.unwrap_or(false)\n        && market.accepting_orders.unwrap_or(false);","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/polymarket/src/http/parse.rs#L107-L143","documentation":"parse_gamma_market requires the market's outcomes JSON array to contain exactly two outcomes, matching the two CLOB token IDs of a binary market. Any other count aborts the parse because instrument construction assumes a YES/NO pair.","triggerScenarios":"A Gamma market whose outcomes field deserializes to fewer or more than 2 strings (multi-outcome markets, empty outcomes arrays, malformed outcomes JSON).","commonSituations":"Fetching sports or neg-risk multi-outcome events where a market can list several outcomes; API responses for very new markets with empty outcome arrays; Gamma API payload drift.","solutions":["Pre-filter markets to those with exactly two outcomes before parsing into instruments.","Skip and log the offending market.condition_id so one bad market does not fail the whole batch.","Verify the raw outcomes JSON from the Gamma API to detect schema changes.","Handle multi-outcome events with dedicated parsing logic if support is needed."],"exampleFix":"// before\nif outcomes.len() != 2 {\n    anyhow::bail!(\"Expected 2 outcomes, received {}\", outcomes.len());\n}\n// after (caller-side guard)\nlet outcomes: Vec<String> = serde_json::from_str(&market.outcomes)?;\nif outcomes.len() != 2 { log::warn!(\"skipping non-binary market {}\", market.condition_id); continue; }","handlingStrategy":"validation","validationCode":"fn has_two_outcomes(m: &GammaMarket) -> bool {\n    serde_json::from_str::<Vec<String>>(&m.outcomes)\n        .map(|o| o.len() == 2)\n        .unwrap_or(false)\n}\n// filter: markets.retain(has_two_outcomes);","typeGuard":"fn is_binary_outcomes(raw: &str) -> bool {\n    matches!(serde_json::from_str::<Vec<String>>(raw), Ok(v) if v.len() == 2)\n}","tryCatchPattern":"let outcomes: Vec<String> = serde_json::from_str(&market.outcomes)?;\nif outcomes.len() != 2 {\n    log::warn!(\"skipping market {} with {} outcomes\", market.condition_id, outcomes.len());\n    return Ok(None); // skip instead of failing batch\n}","preventionTips":["Pre-filter markets to binary (2-outcome) ones before parsing.","Combine outcome and token-id count checks into one guard.","Test parsing against real Gamma payloads in CI."],"tags":["parsing","polymarket","invariant","json"],"backgroundTag":"schema-validation-failed","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"}