nautechsystems/nautilus_trader · error · anyhow::Error

Invalid Hyperliquid outcome symbol '{symbol}': encoding must

Error message

Invalid Hyperliquid outcome symbol '{symbol}': encoding must not be empty

What it means

parse_outcome_symbol_encoding decodes the numeric encoding of an outcome symbol (#<encoding> or +<encoding>, where encoding = 10*outcome + side as u32). An empty encoding after the prefix is rejected with this error.

Source

Thrown at crates/adapters/hyperliquid/src/common/parse.rs:280

    HyperliquidAssetId::from_outcome_encoding(encoding).with_context(|| {
        format!(
            "Invalid Hyperliquid outcome symbol '{symbol}': encoding must fit u32 and end with side digit 0 or 1"
        )
    })
}

fn parse_outcome_symbol_encoding(symbol: &str) -> anyhow::Result<u32> {
    let encoding = symbol
        .strip_prefix('#')
        .or_else(|| symbol.strip_prefix('+'))
        .with_context(|| {
            format!(
                "Invalid Hyperliquid outcome symbol '{symbol}': expected #<encoding> or +<encoding>"
            )
        })?;

    if encoding.is_empty() {
        anyhow::bail!("Invalid Hyperliquid outcome symbol '{symbol}': encoding must not be empty");
    }

    if !encoding.bytes().all(|b| b.is_ascii_digit()) {
        anyhow::bail!("Invalid Hyperliquid outcome symbol '{symbol}': encoding must be numeric");
    }

    encoding
        .parse::<u32>()
        .with_context(|| format!("Invalid Hyperliquid outcome symbol '{symbol}'"))
}

/// Suffix shared by every Nautilus outcome symbol, mirroring `-PERP` / `-SPOT`.
pub const OUTCOME_SYMBOL_SUFFIX: &str = "-OUTCOME";
/// Yes-side label on Nautilus outcome symbols.
pub const OUTCOME_SIDE_YES: &str = "YES";
/// No-side label on Nautilus outcome symbols.
pub const OUTCOME_SIDE_NO: &str = "NO";

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Supply the full outcome symbol including the numeric encoding, e.g. "#42" or "+11".
  2. Check upstream data/config for truncation of the symbol string.
  3. Validate non-empty digits after the prefix before invoking the parser.

Example fix

// before
parse_outcome_symbol("#")?; // empty encoding
// after
parse_outcome_symbol("#42")?; // outcome 4, side 2
Defensive patterns

Strategy: validation

Validate before calling

fn valid_outcome_symbol(s: &str) -> bool {
    let digits = s.strip_prefix('#').or_else(|| s.strip_prefix('+'));
    matches!(digits, Some(d) if !d.is_empty() && d.bytes().all(|b| b.is_ascii_digit()))
}

Try / catch

match parse_outcome_symbol(sym) {
    Ok(parsed) => use_outcome(parsed),
    Err(e) => anyhow::bail!("malformed outcome symbol: {e}"),
}

Prevention

When it happens

Trigger: Calling parse_outcome_symbol with "#" or "+" (prefix present but no digits), which yields an empty encoding string.

Common situations: Truncated symbols from an upstream feed or config; string slicing that drops the digits; user-entered outcome IDs missing the number.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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