docs/concepts/backtesting/apis-and-runs.md
NautilusTrader provides a low-level BacktestEngine API for direct control and a high-level
BacktestNode API for catalog-backed, configurable runs.
Use the low-level API when:
Use the high-level API when:
ParquetDataCatalog.The low-level API centers on BacktestEngine. Create the engine with a
BacktestEngineConfig, then add venues, instruments, components, and data before calling run():
from nautilus_trader.backtest import BacktestEngine
from nautilus_trader.backtest import BacktestEngineConfig
engine = BacktestEngine(BacktestEngineConfig())
engine.add_venue(...)
engine.add_instrument(instrument)
engine.add_strategy(strategy)
engine.add_data(data)
engine.run()
Each add_data() call copies its input into an independent stream. The engine orders each stream
by replay timestamp and merges all streams chronologically during the run. Adding one batch per
instrument does not repeatedly sort a cumulative list:
engine.add_data(instrument1_bars)
engine.add_data(instrument2_bars)
engine.add_data(instrument3_bars)
engine.run()
Keep the default sort=True unless a batching workflow deliberately manages run readiness. A call
with sort=False marks the engine as not ready to run. Call sort_data() before run() unless a
later add_data(..., sort=True) call has restored readiness:
engine.add_data(instrument1_bars, sort=False)
engine.add_data(instrument2_bars, sort=False)
engine.sort_data()
engine.run()
sort_data() marks the independently ordered streams as ready for replay. It is safe to call more
than once.
The engine copies each input sequence. Clearing or modifying the original Python list after
add_data() does not change the loaded stream.
Use streaming mode when the complete dataset does not fit in memory:
engine.add_strategy(strategy)
for batch in data_batches:
engine.add_data(batch)
engine.run(streaming=True)
engine.clear_data()
engine.end()
run(streaming=True) pauses when the current data is exhausted. It does not finalize the trader or
advance timers beyond that batch. Call end() after the final batch to flush timers to the last
run boundary, invoke stop handlers, and produce the final result.
The low-level API does not expose a generator-based add_data_iterator() method. BacktestNode
provides automatic catalog chunking; direct engine users stream with the loop above.
The high-level API centers on BacktestNode. Each BacktestRunConfig contains:
BacktestVenueConfig objects.BacktestDataConfig objects.BacktestEngineConfig.Build the node before adding strategies through its run-specific methods:
from nautilus_trader.backtest import BacktestDataConfig
from nautilus_trader.backtest import BacktestEngineConfig
from nautilus_trader.backtest import BacktestNode
from nautilus_trader.backtest import BacktestRunConfig
from nautilus_trader.backtest import BacktestVenueConfig
from nautilus_trader.model import AccountType
from nautilus_trader.model import BookType
from nautilus_trader.model import OmsType
venue = BacktestVenueConfig(
name="SIM",
oms_type=OmsType.HEDGING,
account_type=AccountType.MARGIN,
book_type=BookType.L1_MBP,
starting_balances=["1_000_000 USD"],
)
data = BacktestDataConfig(
data_type="QuoteTick",
catalog_path="/data/catalog",
instrument_id=instrument_id,
)
config = BacktestRunConfig(
venues=[venue],
data=[data],
engine=BacktestEngineConfig(),
chunk_size=100_000,
)
node = BacktestNode([config])
node.build()
node.add_strategy_from_config(config.id, strategy_config)
results = node.run()
BacktestNode also provides methods for adding actors and built-in strategies to a built run.
Set BacktestEngineConfig.shutdown_on_error=True to request a normal shutdown when the Rust logger
emits an error record:
from nautilus_trader.backtest import BacktestEngineConfig
config = BacktestEngineConfig(shutdown_on_error=True)
The backtest loop observes the request, stops the trader and engines, and returns results collected
up to that point. It does not abort the process. Error records suppressed by component filters or
bypass_logging=True still request shutdown. Python logging.error(...) calls do not.
The trigger resets when a new kernel run starts. For final on_stop and command-settling behavior,
see shutdown semantics.
BacktestEngine.reset() returns trading state and loaded component state to their initial values.
It keeps data, instruments, venues, actors, strategies, and execution algorithms registered.
The reset clears:
The reset retains:
add_data().Instruments remain loaded because the default backtest cache configuration sets
drop_instruments_on_reset=False.
BacktestNode accepts one BacktestRunConfig. To run independent configurations, create and
dispose one node at a time:
configs = [
BacktestRunConfig(...),
BacktestRunConfig(...),
BacktestRunConfig(...),
]
results = []
for config in configs:
node = BacktestNode([config])
try:
results.extend(node.run())
finally:
node.dispose()
Build each node and register its strategies before run() when they are not supplied by a
controller.
Use reset() when runs should share loaded data and venue setup:
engine = BacktestEngine(BacktestEngineConfig())
engine.add_venue(...)
engine.add_instrument(instrument)
engine.add_data(data)
engine.add_strategy(strategy1)
engine.run()
engine.reset()
engine.run()
engine.reset()
engine.clear_strategies()
engine.add_strategy(strategy2)
engine.run()
Call clear_strategies() before replacing a strategy instance. Use clear_data() only when the
next run should load a different dataset.