nautechsystems/nautilus_trader · error · anyhow::Error

Binance historical bars require EXTERNAL aggregation

Error message

Binance historical bars require EXTERNAL aggregation

What it means

The standard historical-bars request path (`request_bars`) requires the requested BarType to use `AggregationSource::External`, i.e. exchange-native bars. INTERNAL (client-aggregated) bar types are rejected before the fetch is spawned, because Binance kline endpoints can only return bars the exchange itself computed.

Source

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

        });

        Ok(())
    }

    fn request_bars(&self, request: RequestBars) -> anyhow::Result<()> {
        let http = self.http_client.clone();
        let sender = self.data_sender.clone();
        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(

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Request the matching EXTERNAL bar type, e.g. `BTCUSDT-PERP.BINANCE-5-MINUTE-LAST-EXTERNAL`
  2. Backfill INTERNAL bars by fetching trades/EXTERNAL bars and feeding them through the local aggregator instead of requesting history for the INTERNAL type

Example fix

# before
bar_type = BarType.from_str('BTCUSDT-PERP.BINANCE-5-MINUTE-LAST-INTERNAL')
actor.request_bars(bar_type, start=start_ts)

# after
bar_type = BarType.from_str('BTCUSDT-PERP.BINANCE-5-MINUTE-LAST-EXTERNAL')
actor.request_bars(bar_type, start=start_ts)
Defensive patterns

Strategy: validation

Validate before calling

from nautilus_trader.model.enums import AggregationSource

def is_requestable_binance_bar(bar_type) -> bool:
    return bar_type.aggregation_source == AggregationSource.EXTERNAL

if not is_requestable_binance_bar(request_bar_type):
    raise ValueError(f'{request_bar_type} is INTERNAL; request the matching -EXTERNAL bar type for Binance history')

Type guard

def is_external_bar(bar_type) -> bool:
    return bar_type.aggregation_source == AggregationSource.EXTERNAL

Try / catch

try:
    actor.request_bars(bar_type, start=start_ts, end=end_ts)
except Exception as e:
    if 'require EXTERNAL aggregation' in str(e):
        external = BarType.from_str(str(bar_type).rsplit('-', 1)[0] + '-EXTERNAL')
        actor.request_bars(external, start=start_ts, end=end_ts)
    else:
        raise

Prevention

When it happens

Trigger: `request_bars` with a BarType ending in `-INTERNAL`, e.g. `BTCUSDT-PERP.BINANCE-5-MINUTE-LAST-INTERNAL` — typically a bar type registered for client-side aggregation from ticks that is then also used for a history backfill request.

Common situations: Strategy registers INTERNAL bars (e.g. aggregated from trades) and then attempts to backfill the same bar type before live data arrives; config defaults producing INTERNAL bar types; porting workflows from venues that serve arbitrary aggregations.

Related errors


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