nautechsystems/nautilus_trader · error · anyhow::Error

Binance historical bars require EXTERNAL aggregation

Error message

Binance historical bars require EXTERNAL aggregation

What it means

Historical Binance bars come only from Binance klines, which are built by the exchange itself. Nautilus distinguishes EXTERNAL aggregation (venue-built bars) from INTERNAL (locally aggregated); the adapter rejects BarsRequest bar types whose aggregation source is INTERNAL because the venue cannot serve history for a bar spec it never produced.

Source

Thrown at crates/adapters/binance/src/spot/data.rs:2356

        });

        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") {
                Ok(bars) => {
                    let response = DataResponse::Bars(BarsResponse::new(

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Request the exchange-native bar type: suffix -EXTERNAL (AggregationSource::External), e.g. 'ETHUSDT_BINANCE-5-MINUTE-LAST-EXTERNAL'
  2. For INTERNAL bars, subscribe to trades/quotes and let the local aggregator build them instead of requesting history
  3. Check bar_type.aggregation_source() == AggregationSource::External before calling request_bars

Example fix

// before
let bar_type = BarType::from_str("ETHUSDT_BINANCE-5-MINUTE-LAST-INTERNAL")?;

// after: exchange-native klines
let bar_type = BarType::from_str("ETHUSDT_BINANCE-5-MINUTE-LAST-EXTERNAL")?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_external(bar_type: &BarType) -> anyhow::Result<()> {
    anyhow::ensure!(
        bar_type.aggregation_source() == AggregationSource::External,
        "historical Binance bars need aggregation source EXTERNAL, got {bar_type}"
    );
    Ok(())
}

Type guard

fn is_external_bar(bar_type: &BarType) -> bool {
    bar_type.aggregation_source() == AggregationSource::External
}

Prevention

When it happens

Trigger: Calling request_bars with a bar type string ending in -INTERNAL, e.g. 'ETHUSDT_BINANCE-5-MINUTE-LAST-INTERNAL'. The ensure! at spot/data.rs:2356 fails before the request task is spawned.

Common situations: Defaulting to AggregationSource::Internal when constructing BarType programmatically; requesting history for a bar type the strategy built for local aggregation; porting configs between adapters without checking the source suffix.

Related errors


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