nautilus_testkit/
common.rs1use std::{
17 fs::File,
18 path::{Path, PathBuf},
19 sync::OnceLock,
20};
21
22use nautilus_core::paths::get_test_data_path;
23use nautilus_model::{
24 data::OrderBookDelta,
25 instruments::{InstrumentAny, stubs::equity_aapl_itch},
26 types::fixed::PRECISION_BYTES,
27};
28use nautilus_serialization::arrow::DecodeFromRecordBatch;
29use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
30
31#[must_use]
37pub fn get_test_data_file_path(path: &str) -> String {
38 get_test_data_path()
39 .join(path)
40 .to_str()
41 .unwrap()
42 .to_string()
43}
44
45#[must_use]
51pub fn get_nautilus_test_data_file_path(filename: &str) -> String {
52 let precision_directory = format!("{}-bit", PRECISION_BYTES * 8);
53 let path = get_test_data_path()
54 .join("nautilus")
55 .join(precision_directory);
56
57 path.join(filename).to_str().unwrap().to_string()
58}
59
60#[must_use]
62pub fn get_test_data_large_checksums_filepath() -> PathBuf {
63 get_test_data_path().join("large").join("checksums.json")
64}
65
66#[must_use]
72pub fn ensure_test_data_exists(filename: &str) -> PathBuf {
73 let filepath = get_test_data_path().join("large").join(filename);
74 assert!(
75 filepath.is_file(),
76 "Missing test data file: {}. Run `cargo run --locked -p nautilus-testkit --bin prepare-test-data` before testing.",
77 filepath.display(),
78 );
79 filepath
80}
81
82#[must_use]
88pub fn ensure_itch_aapl_deltas_parquet() -> PathBuf {
89 ensure_test_data_exists("itch_AAPL.XNAS_2019-01-30_deltas.parquet")
90}
91
92#[must_use]
98pub fn ensure_tardis_deribit_deltas_parquet() -> PathBuf {
99 ensure_test_data_exists("tardis_BTC-PERPETUAL.DERIBIT_2020-04-01_deltas.parquet")
100}
101
102#[must_use]
108pub fn ensure_histdata_eurusd_quotes_parquet() -> PathBuf {
109 ensure_test_data_exists("histdata_EURUSD.SIM_2020-01_quotes.parquet")
110}
111
112#[must_use]
118pub fn ensure_histdata_eurusd_instrument_parquet() -> PathBuf {
119 ensure_test_data_exists("histdata_EURUSD.SIM_2020-01_instrument.parquet")
120}
121
122#[must_use]
124pub fn get_tardis_deribit_book_l2_path() -> PathBuf {
125 get_test_data_path()
126 .join("tardis")
127 .join("deribit_incremental_book_L2_BTC-PERPETUAL.csv")
128}
129
130#[must_use]
132pub fn get_tardis_binance_snapshot5_path() -> PathBuf {
133 get_test_data_path()
134 .join("tardis")
135 .join("binance-futures_book_snapshot_5_BTCUSDT.csv")
136}
137
138#[must_use]
140pub fn get_tardis_binance_snapshot25_path() -> PathBuf {
141 get_test_data_path()
142 .join("tardis")
143 .join("binance-futures_book_snapshot_25_BTCUSDT.csv")
144}
145
146#[must_use]
148pub fn get_tardis_huobi_quotes_path() -> PathBuf {
149 get_test_data_path()
150 .join("tardis")
151 .join("huobi-dm-swap_quotes_BTC-USD.csv")
152}
153
154#[must_use]
156pub fn get_tardis_bitmex_trades_path() -> PathBuf {
157 get_test_data_path()
158 .join("tardis")
159 .join("bitmex_trades_XBTUSD.csv")
160}
161
162#[must_use]
165pub fn itch_aapl_equity() -> InstrumentAny {
166 InstrumentAny::Equity(equity_aapl_itch())
167}
168
169#[must_use]
173pub fn load_itch_aapl_deltas(limit: Option<usize>) -> Vec<OrderBookDelta> {
174 static PATH: OnceLock<PathBuf> = OnceLock::new();
175 let filepath = PATH.get_or_init(ensure_itch_aapl_deltas_parquet);
176 load_deltas_from_parquet(filepath, limit)
177}
178
179#[must_use]
183pub fn load_tardis_deribit_deltas(limit: Option<usize>) -> Vec<OrderBookDelta> {
184 static PATH: OnceLock<PathBuf> = OnceLock::new();
185 let filepath = PATH.get_or_init(ensure_tardis_deribit_deltas_parquet);
186 load_deltas_from_parquet(filepath, limit)
187}
188
189fn load_deltas_from_parquet(filepath: &Path, limit: Option<usize>) -> Vec<OrderBookDelta> {
190 let file = File::open(filepath).unwrap();
191 let mut builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
192 let metadata = builder.schema().metadata().clone();
193
194 if let Some(limit) = limit {
195 builder = builder.with_limit(limit);
196 }
197 let reader = builder.build().unwrap();
198
199 let mut deltas = Vec::new();
200
201 for batch_result in reader {
202 let batch = batch_result.unwrap();
203 let batch_deltas = OrderBookDelta::decode_batch(&metadata, batch).unwrap();
204 deltas.extend(batch_deltas);
205 }
206 deltas
207}
208
209#[cfg(test)]
210mod tests {
211 use rstest::rstest;
212 use tempfile::TempDir;
213
214 use super::*;
215
216 #[rstest]
217 #[case::file("file")]
218 #[case::missing("missing")]
219 #[case::directory("directory")]
220 fn test_ensure_test_data_exists(#[case] state: &str) {
221 let directory = TempDir::new().unwrap();
222 let filepath = directory.path().join("fixture.parquet");
223 if state == "file" {
224 std::fs::write(&filepath, "local fixture").unwrap();
225 } else if state == "directory" {
226 std::fs::create_dir(&filepath).unwrap();
227 }
228
229 let result =
231 std::panic::catch_unwind(|| ensure_test_data_exists(filepath.to_str().unwrap()));
232
233 if state == "file" {
234 assert_eq!(result.unwrap(), filepath);
235 assert_eq!(std::fs::read_to_string(&filepath).unwrap(), "local fixture");
236 } else {
237 let panic = result.unwrap_err().downcast::<String>().unwrap();
238 assert_eq!(
239 *panic,
240 format!(
241 "Missing test data file: {}. Run `cargo run --locked -p nautilus-testkit --bin prepare-test-data` before testing.",
242 filepath.display(),
243 ),
244 );
245 assert_eq!(filepath.exists(), state == "directory");
246 }
247 }
248}