docs/how_to/run_rust_backtest.md
Nautilus provides two Rust APIs for backtesting: BacktestEngine
(low-level) and BacktestNode (high-level with catalog streaming). This
guide covers both.
For background on backtesting concepts, fill models, and matching engine behavior, see the Backtesting concept guide. For project setup and feature flags, see the Rust concept guide.
Add the following to your Cargo.toml. The streaming and
nautilus-persistence entries are only needed for the high-level
BacktestNode API.
[dependencies]
nautilus-backtest = { version = "0.59", features = ["streaming"] }
nautilus-execution = "0.59"
nautilus-model = { version = "0.59", features = ["stubs"] }
nautilus-persistence = "0.59"
nautilus-trading = { version = "0.59", features = ["examples"] }
ahash = "0.8"
anyhow = "1"
tempfile = "3"
ustr = "1"
If you only need the low-level BacktestEngine, drop streaming,
nautilus-persistence, tempfile, and ustr.
The low-level API gives direct control: you build the engine, add venues and instruments, load data in memory, register strategies, and run.
use nautilus_backtest::{config::BacktestEngineConfig, engine::BacktestEngine};
let mut engine = BacktestEngine::new(BacktestEngineConfig::default())?;
SimulatedVenueConfig uses a bon::Builder: only required fields must be set,
every other setting falls back to a documented default. build() validates the
configuration and returns a ConfigResult, so propagate or unwrap it.
use nautilus_backtest::config::SimulatedVenueConfig;
use nautilus_model::{
enums::{AccountType, BookType, OmsType},
identifiers::Venue,
types::Money,
};
engine.add_venue(
SimulatedVenueConfig::builder()
.venue(Venue::from("SIM"))
.oms_type(OmsType::Hedging)
.account_type(AccountType::Margin)
.book_type(BookType::L1_MBP)
.starting_balances(vec![Money::from("1_000_000 USD")])
.build()?,
)?;
Override any default by chaining setters, e.g. .reject_stop_orders(false) or
.allow_cash_borrowing(true).
use nautilus_model::instruments::{
Instrument, InstrumentAny, stubs::audusd_sim,
};
let instrument = InstrumentAny::CurrencyPair(audusd_sim());
let instrument_id = instrument.id();
engine.add_instrument(&instrument)?;
let quotes = generate_quotes(instrument_id); // Your data loading function
engine.add_data(quotes, None, true, true)?;
use nautilus_model::types::Quantity;
use nautilus_trading::examples::strategies::EmaCross;
let strategy = EmaCross::new(
instrument_id,
Quantity::from("100000"),
10, // fast EMA period
20, // slow EMA period
);
engine.add_strategy(strategy)?;
engine.run(None, None, None, false)?;
cargo run -p nautilus-backtest --features examples --example engine-ema-cross
Source:
crates/backtest/examples/engine_ema_cross.rs
The high-level API loads data from a ParquetDataCatalog and streams in
configurable chunk sizes. Requires the streaming feature on
nautilus-backtest.
use nautilus_model::instruments::{
Instrument, InstrumentAny, stubs::audusd_sim,
};
use nautilus_persistence::backend::catalog::ParquetDataCatalog;
use tempfile::TempDir;
let instrument = InstrumentAny::CurrencyPair(audusd_sim());
let instrument_id = instrument.id();
let quotes = generate_quotes(instrument_id);
let temp_dir = TempDir::new()?;
let catalog_path = temp_dir.path().to_str()
.context("temp dir path is not valid UTF-8")?
.to_string();
let catalog = ParquetDataCatalog::new(
temp_dir.path(), None, None, None, None,
);
catalog.write_instruments(vec![instrument])?;
catalog.write_to_parquet("es, None, None, None)?;
use nautilus_backtest::config::{
BacktestDataConfig, BacktestRunConfig, BacktestVenueConfig, NautilusDataType,
};
use nautilus_model::enums::{AccountType, BookType, OmsType};
let venue_config = BacktestVenueConfig::builder()
.name("SIM")
.oms_type(OmsType::Hedging)
.account_type(AccountType::Margin)
.book_type(BookType::L1_MBP)
.starting_balances(vec!["1_000_000 USD".to_string()])
.build()?;
let data_config = BacktestDataConfig::builder()
.data_type(NautilusDataType::QuoteTick)
.catalog_path(catalog_path)
.instrument_id(instrument_id)
.build()?;
let run_config = BacktestRunConfig::builder()
.id("ema-cross-run".to_string())
.venues(vec![venue_config])
.data(vec![data_config])
.chunk_size(100)
.build()?;
use nautilus_backtest::node::BacktestNode;
use nautilus_model::types::Quantity;
use nautilus_trading::examples::strategies::EmaCross;
let mut node = BacktestNode::new(vec![run_config])?;
node.build()?;
let engine = node.get_engine_mut("ema-cross-run")
.context("engine not found for run config ID")?;
let strategy = EmaCross::new(
instrument_id,
Quantity::from("100000"),
10,
20,
);
engine.add_strategy(strategy)?;
node.run()?;
cargo run -p nautilus-backtest --features examples,streaming --example node-ema-cross