nautechsystems/nautilus_trader · error
bar_type.aggregation_source must be External, was
Error message
bar_type.aggregation_source must be External, was {:?} What it means
When registering added Bar data, the backtest engine requires each bar's BarType to have AggregationSource::External, meaning bars arrive pre-aggregated from outside. Internally aggregated bars would need the aggregator machinery the backtest engine does not wire for added data, so it rejects them with this error.
Solutions
- Rebuild the bar_type with AggregationSource::External before creating the Bar objects you add.
- If bar types come from a catalog/config, map them to External source when loading for backtest.
- Aggregate data upstream (e.g. with the data catalog's bar aggregation) and only feed externally aggregated bars.
Example fix
// before
let bar_type = BarType::new(instrument_id, BarAggregation::Minute, Price::from("1"), AggregationSource::Internal);
// after
let bar_type = BarType::new(instrument_id, BarAggregation::Minute, Price::from("1"), AggregationSource::External); Defensive patterns
Strategy: validation
Validate before calling
if let DataRef::Bar(bar) = first {
assert_eq!(bar.bar_type.aggregation_source(), AggregationSource::External,
"bars added to a backtest must use AggregationSource::External");
} Type guard
fn is_external_bar(b: &Bar) -> bool {
b.bar_type.aggregation_source() == AggregationSource::External
} Prevention
- Always construct bar types with AggregationSource::Explicit/External for backtesting.
- Normalize bar types loaded from catalogs/config to External source before adding.
- Aggregate bars upstream so the engine only receives finished bars.
When it happens
Trigger: engine.add_data/add_data_batch with DataRef::Bar items whose bar_type.aggregation_source() is Internal — typically bars constructed with BarType::new(..., AggregationSource::Internal) or built via a spec defaulting to Internal.
Common situations: Reusing BarTypes created for a live node's internal aggregator; constructing bar types without explicitly setting the source; loading historical bars from a catalog where the bar type was stored with Internal source.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Derive only supports 1 DAY interval bars
- Derive only supports hour intervals 1, 4, 8
- Derive only supports minute intervals 1, 5, 15, 30 (use…
- Invalid step in bar_type.spec.step
- Invalid step in bar_type.spec.step
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/25ef23cdb9362762.
Report an issue: GitHub.
Appendix: source
Thrown at crates/backtest/src/engine.rs:488
#[cfg(feature = "defi")]
let first_is_defi = matches!(first, DataRef::Defi(_));
#[cfg(not(feature = "defi"))]
let first_is_defi = false;
if !first_is_defi && !matches!(first, DataRef::Custom(_)) {
let first_instrument_id = first.instrument_id();
anyhow::ensure!(
self.kernel
.cache
.borrow()
.instrument(&first_instrument_id)
.is_some(),
"Instrument {first_instrument_id} for the given data not found in the cache. \
Add the instrument through `add_instrument()` prior to adding related data."
);
if let DataRef::Bar(bar) = first {
anyhow::ensure!(
bar.bar_type.aggregation_source() == AggregationSource::External,
"bar_type.aggregation_source must be External, was {:?}",
bar.bar_type.aggregation_source(),
);
}
}
}
// Track has_data / has_book_data unconditionally so the depth-vs-data
// run-time check still fires for callers that pass validate=false
// (e.g. node.rs run_oneshot loading from a catalog). Time bounds are
// also tracked here so start/end defaults are correct even when the
// batch was added with sort=false.
let mut count = 0;
let mut batch_min_ts: Option<UnixNanos> = None;
let mut batch_max_ts: Option<UnixNanos> = None;
#[cfg(feature = "defi")]View on GitHub (pinned to 18893faf8b)