nautilus_tardis/
config.rs1use parquet::basic::{Compression, ZstdLevel};
17use serde::{Deserialize, Serialize};
18
19use super::machine::types::{ReplayNormalizedRequestOptions, StreamNormalizedRequestOptions};
20
21#[derive(Debug, Clone, Default, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum BookSnapshotOutput {
25 #[default]
27 Deltas,
28 Depth10,
30}
31
32#[derive(Debug, Clone, Default, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum ParquetCompression {
36 #[default]
38 Zstd,
39 Snappy,
41 Uncompressed,
43}
44
45impl ParquetCompression {
46 #[must_use]
52 pub fn as_parquet_compression(&self) -> Compression {
53 match self {
54 Self::Zstd => {
55 let level = ZstdLevel::try_new(3).expect("zstd level 3 is valid");
56 Compression::ZSTD(level)
57 }
58 Self::Snappy => Compression::SNAPPY,
59 Self::Uncompressed => Compression::UNCOMPRESSED,
60 }
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
66#[serde(deny_unknown_fields)]
67pub struct TardisReplayConfig {
68 pub tardis_ws_url: Option<String>,
70 pub normalize_symbols: Option<bool>,
72 pub output_path: Option<String>,
74 #[builder(default)]
76 #[serde(default)]
77 pub options: Vec<ReplayNormalizedRequestOptions>,
78 pub proxy_url: Option<String>,
81 pub book_snapshot_output: Option<BookSnapshotOutput>,
86 pub extract_bbo_as_quotes: Option<bool>,
88 pub compression: Option<ParquetCompression>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
98#[serde(default, deny_unknown_fields)]
99#[cfg_attr(
100 feature = "python",
101 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.tardis", from_py_object)
102)]
103#[cfg_attr(
104 feature = "python",
105 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.tardis")
106)]
107pub struct TardisDataClientConfig {
108 pub api_key: Option<String>,
111 pub tardis_ws_url: Option<String>,
114 pub proxy_url: Option<String>,
117 #[builder(default = true)]
119 pub normalize_symbols: bool,
120 #[builder(default)]
122 pub book_snapshot_output: BookSnapshotOutput,
123 #[builder(default)]
125 pub extract_bbo_as_quotes: bool,
126 #[builder(default)]
129 pub options: Vec<ReplayNormalizedRequestOptions>,
130 #[builder(default)]
134 pub stream_options: Vec<StreamNormalizedRequestOptions>,
135}
136
137impl Default for TardisDataClientConfig {
138 fn default() -> Self {
139 Self::builder().build()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use rstest::rstest;
146
147 use super::*;
148
149 #[rstest]
150 fn test_default_config_values() {
151 let config = TardisDataClientConfig::default();
152 assert!(config.api_key.is_none());
153 assert!(config.tardis_ws_url.is_none());
154 assert!(config.proxy_url.is_none());
155 assert!(config.normalize_symbols);
156 assert!(matches!(
157 config.book_snapshot_output,
158 BookSnapshotOutput::Deltas
159 ));
160 assert!(!config.extract_bbo_as_quotes);
161 assert!(config.options.is_empty());
162 assert!(config.stream_options.is_empty());
163 }
164
165 #[rstest]
166 fn test_book_snapshot_output_default_is_deltas() {
167 assert!(matches!(
168 BookSnapshotOutput::default(),
169 BookSnapshotOutput::Deltas
170 ));
171 }
172
173 #[rstest]
174 fn test_book_snapshot_output_serde_roundtrip_deltas() {
175 let json = serde_json::to_string(&BookSnapshotOutput::Deltas).unwrap();
176 assert_eq!(json, "\"deltas\"");
177
178 let deserialized: BookSnapshotOutput = serde_json::from_str(&json).unwrap();
179 assert!(matches!(deserialized, BookSnapshotOutput::Deltas));
180 }
181
182 #[rstest]
183 fn test_book_snapshot_output_serde_roundtrip_depth10() {
184 let json = serde_json::to_string(&BookSnapshotOutput::Depth10).unwrap();
185 assert_eq!(json, "\"depth10\"");
186
187 let deserialized: BookSnapshotOutput = serde_json::from_str(&json).unwrap();
188 assert!(matches!(deserialized, BookSnapshotOutput::Depth10));
189 }
190
191 #[rstest]
192 fn test_parquet_compression_default_is_zstd() {
193 assert!(matches!(
194 ParquetCompression::default(),
195 ParquetCompression::Zstd
196 ));
197 assert!(matches!(
198 ParquetCompression::default().as_parquet_compression(),
199 Compression::ZSTD(_)
200 ));
201 }
202
203 #[rstest]
204 fn test_parquet_compression_serde_roundtrip() {
205 let cases = [
206 (ParquetCompression::Zstd, "\"zstd\""),
207 (ParquetCompression::Snappy, "\"snappy\""),
208 (ParquetCompression::Uncompressed, "\"uncompressed\""),
209 ];
210
211 for (compression, expected_json) in cases {
212 let json = serde_json::to_string(&compression).unwrap();
213 assert_eq!(json, expected_json);
214
215 let deserialized: ParquetCompression = serde_json::from_str(&json).unwrap();
216 assert_eq!(
217 compression.as_parquet_compression(),
218 deserialized.as_parquet_compression()
219 );
220 }
221 }
222
223 #[rstest]
224 fn test_data_client_config_toml_minimal() {
225 let config: TardisDataClientConfig = toml::from_str(
226 r#"
227normalize_symbols = false
228book_snapshot_output = "depth10"
229"#,
230 )
231 .unwrap();
232
233 assert!(!config.normalize_symbols);
234 assert!(matches!(
235 config.book_snapshot_output,
236 BookSnapshotOutput::Depth10
237 ));
238 assert!(!config.extract_bbo_as_quotes);
239 assert!(config.options.is_empty());
240 }
241
242 #[rstest]
243 fn test_replay_config_omits_options_uses_empty_default() {
244 let config: TardisReplayConfig = toml::from_str(
245 r#"
246tardis_ws_url = "wss://example.com"
247normalize_symbols = false
248"#,
249 )
250 .unwrap();
251
252 assert!(config.options.is_empty());
253 assert_eq!(config.tardis_ws_url.as_deref(), Some("wss://example.com"));
254 assert_eq!(config.normalize_symbols, Some(false));
255 assert_eq!(config.extract_bbo_as_quotes, None);
256 }
257
258 #[rstest]
259 fn test_replay_config_deserializes_compression() {
260 let json = r#"{
261 "tardis_ws_url": null,
262 "normalize_symbols": true,
263 "output_path": null,
264 "options": [],
265 "proxy_url": null,
266 "book_snapshot_output": "depth10",
267 "extract_bbo_as_quotes": true,
268 "compression": "zstd"
269 }"#;
270
271 let config: TardisReplayConfig = serde_json::from_str(json).unwrap();
272
273 assert!(matches!(
274 config.book_snapshot_output,
275 Some(BookSnapshotOutput::Depth10)
276 ));
277 assert_eq!(config.extract_bbo_as_quotes, Some(true));
278 assert!(matches!(config.compression, Some(ParquetCompression::Zstd)));
279 }
280}