nautechsystems/nautilus_trader · error · anyhow::Error

Binance historical bars require time aggregation

Error message

Binance historical bars require time aggregation

What it means

Thrown by BinanceFuturesDataClient::request_bars when the requested BarType's aggregation method is not time-based. Binance's klines endpoint can only serve time-aggregated (Millisecond/Second/Minute/Hour/Day/Week/Month/Year) LAST-price bars with AggregationSource::External, so tick, volume, or internally aggregated bars cannot be backfilled from the venue. This is the third of three anyhow::ensure! guards validating the bar spec before the HTTP request is spawned.

Source

Thrown at crates/adapters/binance/src/futures/data.rs:3010

        let bar_type = request.bar_type;
        let start = request.start;
        let end = request.end;
        let limit = request.limit.map(|n| n.get() as u32);
        let request_id = request.request_id;
        let client_id = request.client_id.unwrap_or(self.client_id);
        let params = request.params;
        let clock = self.clock;
        let start_nanos = datetime_to_unix_nanos(start);
        let end_nanos = datetime_to_unix_nanos(end);
        anyhow::ensure!(
            bar_type.aggregation_source() == AggregationSource::External,
            "Binance historical bars require EXTERNAL aggregation"
        );
        anyhow::ensure!(
            bar_type.spec().price_type == PriceType::Last,
            "Binance historical bars require LAST price type"
        );
        anyhow::ensure!(
            bar_type.spec().is_time_aggregated(),
            "Binance historical bars require time aggregation"
        );

        get_runtime().spawn(async move {
            let result = http.request_bars(bar_type, start, end, limit).await;

            match result.context("failed to request bars from Binance Futures") {
                Ok(bars) => {
                    let response = DataResponse::Bars(BarsResponse::new(
                        request_id,
                        client_id,
                        bar_type,
                        bars,
                        start_nanos,
                        end_nanos,
                        clock.get_time_ns(),
                        params,

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Use a time-based aggregation in the BarType, e.g. -1-MINUTE-LAST-, -1-HOUR-LAST-, -1-DAY-LAST-
  2. Set AggregationSource::External (e.g. BarType::standard) so the venue supplies the bars rather than the engine aggregating them
  3. If you need volume/tick bars, subscribe to trades/quotes and aggregate locally with an internal bar builder instead of calling request_bars
  4. Verify the two sibling guards on the same path also pass: price_type == Last and aggregation_source == External

Example fix

// before
let bar_type = BarType::from_str("BTCUSDT-PERP.BINANCE-1-VOLUME-LAST-EXTERNAL")?;
client.request_bars(request, bar_type, start, end, None).await?;

// after
let bar_type = BarType::from_str("BTCUSDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL")?;
client.request_bars(request, bar_type, start, end, None).await?;
Defensive patterns

Strategy: validation

Validate before calling

use nautilus_model::data::{AggregationSource, BarType};
use nautilus_model::enums::PriceType;

fn is_binance_requestable_bar(bar_type: &BarType) -> bool {
    bar_type.aggregation_source() == AggregationSource::External
        && bar_type.spec().price_type == PriceType::Last
        && bar_type.spec().is_time_aggregated()
}

// before request_bars:
assert!(is_binance_requestable_bar(&bar_type), "unsupported bar spec");

Type guard

fn is_binance_requestable_bar(bar_type: &BarType) -> bool {
    bar_type.aggregation_source() == AggregationSource::External
        && bar_type.spec().price_type == PriceType::Last
        && bar_type.spec().is_time_aggregated()
}

Prevention

When it happens

Trigger: Calling request_bars (from a strategy or the data engine) with a BarType whose aggregation is Tick/Volume/VolumeDollar/Index/Custom, e.g. BTCUSDT-PERP.BINANCE-1-VOLUME-LAST-EXTERNAL, or any spec where bar_type.spec().is_time_aggregated() (crates/model/src/data/bar.rs:531) returns false.

Common situations: Strategies written around internal aggregation (e.g. -1-MINUTE-LAST-INTERNAL) pointed at the Binance Futures adapter for historical backfill; hand-built BarType strings with wrong aggregation tokens; porting configs from venues that serve volume bars.

Related errors


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