nautechsystems/nautilus_trader · error · anyhow::Error

Invalid `TcbboMsg`: expected trade action, was {action}

Error message

Invalid `TcbboMsg`: expected trade action, was {action}

What it means

decode_tcbbo_msg decodes a TCBB0/TcbboMsg into a quote and a trade tick, delegating to decode_cmbp1_msg. The CMBP-1 record may carry non-trade actions (e.g. book refreshes); since TCBB0 decoding requires a trade action, a record without a trade component is rejected with this error.

Source

Thrown at crates/adapters/databento/src/decode/market_data.rs:632

/// Decodes a Databento TCBBO (Consolidated Top of Book with Trade) message.
///
/// Returns `None` for the quote if either bid or ask price is undefined (`i64::MAX`).
/// The trade is always returned.
///
/// # Errors
///
/// Returns an error if decoding the TCBBO message fails.
pub fn decode_tcbbo_msg(
    msg: &dbn::TcbboMsg,
    instrument_id: InstrumentId,
    price_precision: u8,
    ts_init: Option<UnixNanos>,
) -> anyhow::Result<(Option<QuoteTick>, TradeTick)> {
    let (maybe_quote, maybe_trade) =
        decode_cmbp1_msg(msg, instrument_id, price_precision, ts_init, true)?;
    let trade = maybe_trade.ok_or_else(|| {
        anyhow::anyhow!(
            "Invalid `TcbboMsg`: expected trade action, was {}",
            msg.action as u8 as char
        )
    })?;

    Ok((maybe_quote, trade))
}

/// # Errors
///
/// Returns an error if `rtype` is not a supported bar aggregation.
pub fn decode_bar_type(
    msg: &dbn::OhlcvMsg,
    instrument_id: InstrumentId,
) -> anyhow::Result<BarType> {
    let bar_type = match msg.hd.rtype {
        32 => {
            // ohlcv-1s

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter records by msg.action before calling decode_tcbbo_msg, skipping non-trade actions
  2. Use the CMBP-1 decoder (decode_cmbp1_msg) directly to handle both quote and non-trade actions gracefully
  3. Upgrade the adapter in case newer versions tolerate non-trade TCBB0 records
  4. Inspect msg.action on offending records to confirm they are book events, not trades

Example fix

// before
let (quote, trade) = decode_tcbbo_msg(&msg, instrument_id, precision, None)?;
// after
if msg.action != b'T' { return Ok(None); } // skip non-trade records
let (quote, trade) = decode_tcbbo_msg(&msg, instrument_id, precision, None)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust
if msg.action != b'T' { return Ok(None); }

Try / catch

// Rust
Err(e) => log::debug("skip: {e:#}"),

Prevention

When it happens

Trigger: Calling decode_tcbbo_msg on a TcbboMsg whose msg.action is not a trade action (so decode_cmbp1_msg returns None for the trade), e.g. book-clearing/refresh records interleaved in a TCBB0 stream.

Common situations: Replaying TCBB0 historical data containing non-trade action records; decoding combined feed records where action='R' or similar book events appear; pipeline code that should filter actions before decode.

Related errors


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