use nautilus_backtest::{
config::{BacktestDataConfig, BacktestRunConfig, BacktestVenueConfig, NautilusDataType},
node::BacktestNode,
};
use nautilus_model::{
data::QuoteTick,
enums::{AccountType, BookType, OmsType},
identifiers::InstrumentId,
instruments::{Instrument, InstrumentAny, stubs::audusd_sim},
types::{Price, Quantity},
};
use nautilus_persistence::backend::catalog::ParquetDataCatalog;
use nautilus_trading::examples::strategies::EmaCross;
use tempfile::TempDir;
use ustr::Ustr;
fn generate_quotes(instrument_id: InstrumentId) -> Vec<QuoteTick> {
let spread = 0.00020;
let base_ts: u64 = 1_735_689_600_000_000_000; let interval: u64 = 1_000_000_000;
let mut quotes = Vec::new();
let mut tick: u64 = 0;
let mut add = |mid: f64| {
let bid = format!("{mid:.5}");
let ask = format!("{:.5}", mid + spread);
quotes.push(QuoteTick::new(
instrument_id,
Price::from(bid.as_str()),
Price::from(ask.as_str()),
Quantity::from("100000"),
Quantity::from("100000"),
(base_ts + tick * interval).into(),
(base_ts + tick * interval).into(),
));
tick += 1;
};
for _ in 0..25 {
add(0.65000);
}
let cycles = 6;
for cycle in 0..cycles {
let base = 0.65000 + (cycle as f64 * 0.00100);
for i in 0..40 {
add(base + (i as f64 * 0.00050));
}
for i in 0..80 {
let peak = base + 39.0 * 0.00050;
add(peak - (i as f64 * 0.00050));
}
}
quotes
}
fn main() -> anyhow::Result<()> {
let instrument = InstrumentAny::CurrencyPair(audusd_sim());
let instrument_id = instrument.id();
let quotes = generate_quotes(instrument_id);
let num_quotes = quotes.len();
let temp_dir = TempDir::new()?;
let catalog_path = temp_dir.path().to_str().unwrap().to_string();
let catalog = ParquetDataCatalog::new(temp_dir.path(), None, None, None, None);
catalog.write_instruments(vec![instrument])?;
catalog.write_to_parquet(quotes, None, None, None)?;
println!("Wrote {num_quotes} quotes to catalog: {catalog_path}");
let venue_config = BacktestVenueConfig::builder()
.name(Ustr::from("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();
let mut node = BacktestNode::new(vec![run_config])?;
node.build()?;
let engine = node.get_engine_mut("ema-cross-run").unwrap();
engine.add_strategy(EmaCross::new(
instrument_id,
Quantity::from("100000"),
10,
20,
))?;
node.run()?;
Ok(())
}