nautechsystems/nautilus_trader · error · anyhow::Error

invalid negative kline trade count {}

Error message

invalid negative kline trade count {}

What it means

When decoding Binance Spot klines received over the SBE (binary) market-data channel, the per-kline trade count is carried as a signed i64 and converted to u64 for NautilusTrader's Bar. A negative value cannot occur in a well-formed feed, so this error signals malformed or desynchronized SBE decoding — most often a schema/version mismatch between the adapter's generated codecs and the venue's current SBE templates, or a misaligned buffer.

Source

Thrown at crates/adapters/binance/src/common/parse.rs:1381

        let volume_mantissa = i128::from_le_bytes(kline.volume);
        let volume_dec =
            Decimal::from_i128_with_scale(volume_mantissa, (-klines.qty_exponent as i32) as u32);
        let volume = Quantity::from_decimal_dp(volume_dec, size_precision)?;

        let quote_volume = Decimal::from_i128_with_scale(
            i128::from_le_bytes(kline.quote_volume),
            (-klines.price_exponent as i32) as u32,
        );
        let taker_buy_base_volume = Decimal::from_i128_with_scale(
            i128::from_le_bytes(kline.taker_buy_base_volume),
            (-klines.qty_exponent as i32) as u32,
        );
        let taker_buy_quote_volume = Decimal::from_i128_with_scale(
            i128::from_le_bytes(kline.taker_buy_quote_volume),
            (-klines.price_exponent as i32) as u32,
        );
        let count = u64::try_from(kline.num_trades).map_err(|_| {
            anyhow::anyhow!("invalid negative kline trade count {}", kline.num_trades)
        })?;
        let ts_event = parse_micros(kline.close_time, "Spot SBE kline close time")?;

        let bar = crate::common::bar::BinanceBar::new(
            bar_type,
            open,
            high,
            low,
            close,
            volume,
            quote_volume,
            count,
            taker_buy_base_volume,
            taker_buy_quote_volume,
            ts_event,
            ts_init,
        );
        bars.push(bar);

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Update NautilusTrader to the latest release so the bundled SBE codecs match the venue's current schema.
  2. Unsubscribe and resubscribe (or reconnect) the SBE stream to re-sync decoding from a message boundary.
  3. If it persists, capture the raw frame and symbol/interval and report it to the adapter maintainers.
  4. As a workaround, configure the data client to use the JSON market-data channel instead of SBE.
Defensive patterns

Strategy: try-catch

Validate before calling

fn kline_trade_count_is_valid(num_trades: i64) -> bool {
    num_trades >= 0
}

Type guard

fn has_valid_trade_count(num_trades: i64) -> bool {
    num_trades >= 0
}

Try / catch

// where decoded SBE klines are consumed
let count = match u64::try_from(kline.num_trades) {
    Ok(c) => c,
    Err(_) => {
        tracing::error!(num_trades = kline.num_trades, "malformed SBE kline, resubscribing");
        resubscribe_kline_stream();
        continue;
    }
};

Prevention

When it happens

Trigger: Subscribing to Spot SBE kline streams (or replaying captured SBE frames) where the decoded numTrades is negative — typically after Binance updates its SBE schema while the adapter build predates it, or when a recorded byte stream is fed at the wrong offset.

Common situations: Venue ships an SBE template revision; a cached/recorded binary stream is replayed against a different adapter version; stream resubscription after a mid-session protocol switch desynchronizes the decoder.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/b07b5443617b3ded. Report an issue: GitHub.