nautechsystems/nautilus_trader · error

Continuous future transitions must not be empty

Error message

Continuous future transitions must not be empty

What it means

A continuous future request must contain at least one contract transition row (the chain of contract rolls). An empty transitions list would produce a continuous series with no segments, which is meaningless, so parse_continuous_future bails with this error.

Source

Thrown at crates/data/src/engine/requests.rs:399

        })?;
    let first_pre_instrument_id = parse_optional_chain_bound(
        params,
        FIRST_PRE_INSTRUMENT_ID,
        primary_bar_type.instrument_id(),
    )?;
    let last_post_instrument_id = parse_optional_chain_bound(
        params,
        LAST_POST_INSTRUMENT_ID,
        primary_bar_type.instrument_id(),
    )?;
    let transitions = parse_transitions(
        primary_bar_type,
        transitions_value,
        adjustment_mode.is_ratio(),
    )?;

    if transitions.is_empty() {
        anyhow::bail!("Continuous future transitions must not be empty");
    }

    if let Some(instrument_id) = first_pre_instrument_id
        && !transitions
            .iter()
            .any(|row| row.pre_instrument_id == instrument_id)
    {
        anyhow::bail!(
            "Continuous future first_pre_instrument_id {instrument_id} was not found in transitions"
        );
    }

    if let Some(instrument_id) = last_post_instrument_id
        && !transitions
            .iter()
            .any(|row| row.post_instrument_id == instrument_id)
    {
        anyhow::bail!(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Supply at least one transition row (pre/post instrument IDs with roll dates) in continuous_future_transitions.
  2. Check that the source of the contract chain actually returned contracts before constructing the request.
  3. Validate transitions.len() > 0 in the caller before invoking the subscription/request API.

Example fix

// before
let params = params_with(CONTINUOUS_FUTURE_TRANSITIONS, json!([]));
// after
let params = params_with(CONTINUOUS_FUTURE_TRANSITIONS, json!([
  {"pre_instrument_id": "ESZ5.XCME", "post_instrument_id": "ESH6.XCME", "roll_date": "2025-12-18"}
]));
Defensive patterns

Strategy: validation

Validate before calling

if transitions.is_empty() {
    anyhow::bail!("continuous_future_transitions must contain at least one row");
}

Type guard

fn has_transitions(v: &serde_json::Value) -> bool {
    v.as_array().map(|a| !a.is_empty()).unwrap_or(false)
}

Try / catch

match engine.subscribe_continuous_future_bars(&cmd) {
    Err(e) if e.to_string().contains("transitions must not be empty") => {
        eprintln!("contract chain was empty; check the source of continuous_future_transitions");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Passing continuous_future_transitions that parses to an empty list — e.g. an empty JSON array, a chain filtered down to nothing, or a missing/malformed transitions payload that deserializes as empty.

Common situations: Building the transitions array programmatically from a contract-chain query that returned no rows; hand-writing an empty [] in config; a data provider returning no contracts for the root/class.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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