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, normalize_legacy_fixed_columns};
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]
53pub fn get_nautilus_test_data_file_path(filename: &str) -> String {
54 let precision_directory = format!("{}-bit", PRECISION_BYTES * 8);
55 let path = get_test_data_path()
56 .join("nautilus")
57 .join("legacy")
58 .join(precision_directory);
59
60 path.join(filename).to_str().unwrap().to_string()
61}
62
63#[must_use]
65pub fn get_test_data_large_checksums_filepath() -> PathBuf {
66 get_test_data_path().join("large").join("checksums.json")
67}
68
69#[must_use]
75pub fn ensure_test_data_exists(filename: &str) -> PathBuf {
76 let filepath = get_test_data_path().join("large").join(filename);
77 assert!(
78 filepath.is_file(),
79 "Missing test data file: {}. Run `cargo run --locked -p nautilus-testkit --bin prepare-test-data` before testing.",
80 filepath.display(),
81 );
82 filepath
83}
84
85#[must_use]
91pub fn ensure_itch_aapl_deltas_parquet() -> PathBuf {
92 ensure_test_data_exists("itch_AAPL.XNAS_2019-01-30_deltas.parquet")
93}
94
95#[must_use]
101pub fn ensure_tardis_deribit_deltas_parquet() -> PathBuf {
102 ensure_test_data_exists("tardis_BTC-PERPETUAL.DERIBIT_2020-04-01_deltas.parquet")
103}
104
105#[must_use]
111pub fn ensure_histdata_eurusd_quotes_parquet() -> PathBuf {
112 ensure_test_data_exists("histdata_EURUSD.SIM_2020-01_quotes.parquet")
113}
114
115#[must_use]
121pub fn ensure_histdata_eurusd_instrument_parquet() -> PathBuf {
122 ensure_test_data_exists("histdata_EURUSD.SIM_2020-01_instrument.parquet")
123}
124
125#[must_use]
127pub fn get_tardis_deribit_book_l2_path() -> PathBuf {
128 get_test_data_path()
129 .join("tardis")
130 .join("deribit_incremental_book_L2_BTC-PERPETUAL.csv")
131}
132
133#[must_use]
135pub fn get_tardis_binance_snapshot5_path() -> PathBuf {
136 get_test_data_path()
137 .join("tardis")
138 .join("binance-futures_book_snapshot_5_BTCUSDT.csv")
139}
140
141#[must_use]
143pub fn get_tardis_binance_snapshot25_path() -> PathBuf {
144 get_test_data_path()
145 .join("tardis")
146 .join("binance-futures_book_snapshot_25_BTCUSDT.csv")
147}
148
149#[must_use]
151pub fn get_tardis_huobi_quotes_path() -> PathBuf {
152 get_test_data_path()
153 .join("tardis")
154 .join("huobi-dm-swap_quotes_BTC-USD.csv")
155}
156
157#[must_use]
159pub fn get_tardis_bitmex_trades_path() -> PathBuf {
160 get_test_data_path()
161 .join("tardis")
162 .join("bitmex_trades_XBTUSD.csv")
163}
164
165#[must_use]
168pub fn itch_aapl_equity() -> InstrumentAny {
169 InstrumentAny::Equity(equity_aapl_itch())
170}
171
172#[must_use]
176pub fn load_itch_aapl_deltas(limit: Option<usize>) -> Vec<OrderBookDelta> {
177 static PATH: OnceLock<PathBuf> = OnceLock::new();
178 let filepath = PATH.get_or_init(ensure_itch_aapl_deltas_parquet);
179 load_deltas_from_parquet(filepath, limit)
180}
181
182#[must_use]
186pub fn load_tardis_deribit_deltas(limit: Option<usize>) -> Vec<OrderBookDelta> {
187 static PATH: OnceLock<PathBuf> = OnceLock::new();
188 let filepath = PATH.get_or_init(ensure_tardis_deribit_deltas_parquet);
189 load_deltas_from_parquet(filepath, limit)
190}
191
192fn load_deltas_from_parquet(filepath: &Path, limit: Option<usize>) -> Vec<OrderBookDelta> {
193 let file = File::open(filepath).unwrap();
194 let mut builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
195 let metadata = builder.schema().metadata().clone();
196
197 if let Some(limit) = limit {
198 builder = builder.with_limit(limit);
199 }
200 let reader = builder.build().unwrap();
201
202 let mut deltas = Vec::new();
203
204 for batch_result in reader {
205 let batch = normalize_legacy_fixed_columns(&batch_result.unwrap()).unwrap();
206 let batch_deltas = OrderBookDelta::decode_batch(&metadata, batch).unwrap();
207 deltas.extend(batch_deltas);
208 }
209 deltas
210}
211
212#[cfg(test)]
213mod tests {
214 use rstest::rstest;
215 use tempfile::TempDir;
216
217 use super::*;
218
219 #[rstest]
220 #[case::file("file")]
221 #[case::missing("missing")]
222 #[case::directory("directory")]
223 fn test_ensure_test_data_exists(#[case] state: &str) {
224 let directory = TempDir::new().unwrap();
225 let filepath = directory.path().join("fixture.parquet");
226 if state == "file" {
227 std::fs::write(&filepath, "local fixture").unwrap();
228 } else if state == "directory" {
229 std::fs::create_dir(&filepath).unwrap();
230 }
231
232 let result =
234 std::panic::catch_unwind(|| ensure_test_data_exists(filepath.to_str().unwrap()));
235
236 if state == "file" {
237 assert_eq!(result.unwrap(), filepath);
238 assert_eq!(std::fs::read_to_string(&filepath).unwrap(), "local fixture");
239 } else {
240 let panic = result.unwrap_err().downcast::<String>().unwrap();
241 assert_eq!(
242 *panic,
243 format!(
244 "Missing test data file: {}. Run `cargo run --locked -p nautilus-testkit --bin prepare-test-data` before testing.",
245 filepath.display(),
246 ),
247 );
248 assert_eq!(filepath.exists(), state == "directory");
249 }
250 }
251}