nautechsystems/nautilus_trader · error · anyhow::Error

Failed to advance balances group: {e:?}

Error message

Failed to advance balances group: {e:?}

What it means

Decoding an SBE `ACCOUNT_POSITION` group failed while iterating the balances repeating group. `balances_dec.advance()` returned an error (malformed buffer, truncated group, or wrong template id) which is wrapped with debug formatting `{e:?}`.

Source

Thrown at crates/adapters/binance/src/spot/websocket/trading/decode_sbe.rs:295

        anyhow::bail!(
            "Buffer too short for fixed block: expected {min_len}, was {}",
            data.len()
        );
    }

    let dec = outbound_account_position_event_codec::OutboundAccountPositionEventDecoder::default()
        .wrap(buf, HEADER_LEN, block_length, version);

    let event_time_us = dec.event_time();
    let update_time_us = dec.update_time();

    let mut balances_dec = dec.balances_decoder();
    let count = balances_dec.count() as usize;
    let mut balances = Vec::with_capacity(count);

    while let Some(_idx) = balances_dec
        .advance()
        .map_err(|e| anyhow::anyhow!("Failed to advance balances group: {e:?}"))?
    {
        let exponent = balances_dec.exponent();
        let free_mantissa = balances_dec.free();
        let locked_mantissa = balances_dec.locked();

        let asset_coords = balances_dec.asset_decoder();
        let asset_bytes = balances_dec.asset_slice(asset_coords);
        let asset = Ustr::from(&String::from_utf8_lossy(asset_bytes));

        balances.push(BinanceSpotBalanceEntry {
            asset,
            free: mantissa_to_decimal(free_mantissa, exponent),
            locked: mantissa_to_decimal(locked_mantissa, exponent),
        });
    }

    Ok(BinanceSpotAccountPositionMsg {
        event_type: "outboundAccountPosition".to_string(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Update the SBE schema and generated decoders to the current Binance version — schema drift is the most common cause.
  2. Log and inspect the raw buffer around the balances group; compare its length against the group's `count` header.
  3. Verify the template id before decoding (reject wrong-template messages early).
  4. If capturing frames, ensure the full frame including the group payload is written.

Example fix

// before: decoding raw captured bytes with an old generated schema
let msg = MessageHeaderDecoder::default().decode(buf)?; decode_account_position(&mut dec)?;
// after: regenerate decoder bindings from the latest Binance SBE schema (sbe-tool) and rebuild
Defensive patterns

Strategy: try-catch

Validate before calling

if header.template_id() != ACCOUNT_POSITION_TEMPLATE_ID {
    return Err(anyhow::anyhow!("unexpected template id {}", header.template_id()));
}

Try / catch

match decode_account_position(&mut dec) {
    Ok(msg) => Ok(msg),
    Err(e) => {
        log::error!("SBE account position decode failed: {e:?}; resyncing schema/buffer");
        Err(e)
    }
}

Prevention

When it happens

Trigger: Calling `decode_account_position` on a buffer where the balances group header/entries are truncated, the message is not actually the account-position template, or the SBE block length doesn't match the schema; each `advance()` step that hits invalid data returns `Err`.

Common situations: Capturing frames with a mismatched SBE schema version (Binance updated the schema); cutting a frame short when recording traffic; a decoder pointed at a non-account-position message.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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