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;
const VENUE: &str = "SIM";
const STARTING_BALANCE: &str = "1_000_000 USD";
const TRADE_SIZE: &str = "100000";
const EMA_FAST_PERIOD: usize = 10;
const EMA_SLOW_PERIOD: usize = 20;
const CHUNK_SIZE: usize = 100;
const RUN_ID: &str = "ema-cross-run";
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("es, None, None, None)?;
println!("Wrote {num_quotes} quotes to catalog: {catalog_path}");
let venue_config = BacktestVenueConfig::builder()
.name(Ustr::from(VENUE))
.oms_type(OmsType::Hedging)
.account_type(AccountType::Margin)
.book_type(BookType::L1_MBP)
.starting_balances(vec![STARTING_BALANCE.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(RUN_ID.to_string())
.venues(vec![venue_config])
.data(vec![data_config])
.chunk_size(CHUNK_SIZE)
.build()?;
let mut node = BacktestNode::new(vec![run_config])?;
node.build()?;
let engine = node.get_engine_mut(RUN_ID).unwrap();
engine.add_strategy(EmaCross::new(
instrument_id,
Quantity::from(TRADE_SIZE),
EMA_FAST_PERIOD,
EMA_SLOW_PERIOD,
))?;
node.run()?;
Ok(())
}