nautechsystems/nautilus_trader · error

Failed to parse outcomes '{}': {e}

Error message

Failed to parse outcomes '{}': {e}

What it means

parse_gamma_market parses the market's outcomes field, which Polymarket delivers as a stringified JSON array of strings. This error is thrown when that string cannot be deserialized into Vec<String> — malformed JSON, empty string, or a changed payload shape.

Source

Thrown at crates/adapters/polymarket/src/http/parse.rs:122

            .events
            .as_ref()?
            .iter()
            .find_map(|event| event.game_id.clone())
    });

    let token_ids: Vec<String> = serde_json::from_str(&market.clob_token_ids).map_err(|e| {
        anyhow::anyhow!(
            "Failed to parse clob_token_ids '{}': {e}",
            market.clob_token_ids
        )
    })?;

    if token_ids.len() != 2 {
        anyhow::bail!("Expected 2 token IDs, received {}", token_ids.len());
    }

    let outcomes: Vec<String> = serde_json::from_str(&market.outcomes)
        .map_err(|e| anyhow::anyhow!("Failed to parse outcomes '{}': {e}", market.outcomes))?;

    if outcomes.len() != 2 {
        anyhow::bail!("Expected 2 outcomes, received {}", outcomes.len());
    }

    let tick_size = market
        .order_price_min_tick_size
        .unwrap_or(DEFAULT_TICK_SIZE);
    let price_precision = POLYMARKET_PRICE_PRECISION;

    // Polymarket charges fees using `feeSchedule.rate` on the Gamma market.
    // Only takers pay; makers are always zero.
    // Reference: https://docs.polymarket.com/trading/fees
    let maker_fee: Option<Decimal> = market.fee_schedule.as_ref().map(|_| Decimal::ZERO);
    let taker_fee: Option<Decimal> = market.fee_schedule.as_ref().map(|fs| fs.rate);

    let min_size = market.order_min_size;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the raw market.outcomes string to confirm the payload shape.
  2. Adapt the GammaMarket deserialization to accept both stringified and native-array forms.
  3. Skip or quarantine markets with unparseable outcomes instead of failing the whole batch.

Example fix

// before
let outcomes: Vec<String> = serde_json::from_str(&market.outcomes)
    .map_err(|e| anyhow::anyhow!("Failed to parse outcomes '{}': {e}", market.outcomes))?;
// after
let outcomes: Vec<String> = serde_json::from_str(&market.outcomes)
    .or_else(|_| serde_json::from_value(market.outcomes_value.clone()))
    .map_err(|e| anyhow::anyhow!("Failed to parse outcomes '{}': {e}", market.outcomes))?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_stringified_outcomes(market: &GammaMarket) -> bool {
    !market.outcomes.is_empty()
        && market.outcomes.starts_with('[')
        && serde_json::from_str::<Vec<String>>(&market.outcomes).is_ok()
}

Type guard

fn parse_outcomes(raw: &str) -> Option<Vec<String>> {
    serde_json::from_str::<Vec<String>>(raw).ok()
}

Try / catch

let outcomes = match serde_json::from_str::<Vec<String>>(&market.outcomes) {
    Ok(v) => v,
    Err(e) => { warn!("skipping market {}: bad outcomes: {e}", market.id); return Ok(None); }
};

Prevention

When it happens

Trigger: Calling parse_gamma_market (directly or via parse_markets_with_transient / request_instruments) on a market whose outcomes field is empty, invalid JSON, or not a string-encoded array.

Common situations: Gamma API schema drift (outcomes arriving as a native array), mocked responses that put real arrays in the field, or partially written/cached market records.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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