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.adapters.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
137#[cfg(feature = "python")]
138nautilus_core::impl_pyo3_config_getters!(TardisDataClientConfig {
139 tardis_ws_url: Option<String>,
140 normalize_symbols: bool,
141 extract_bbo_as_quotes: bool,
142 options: Vec<ReplayNormalizedRequestOptions>,
143 stream_options: Vec<StreamNormalizedRequestOptions>,
144});
145
146impl Default for TardisDataClientConfig {
147 fn default() -> Self {
148 Self::builder().build()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use rstest::rstest;
155
156 use super::*;
157
158 #[rstest]
159 fn test_default_config_values() {
160 let config = TardisDataClientConfig::default();
161 assert!(config.api_key.is_none());
162 assert!(config.tardis_ws_url.is_none());
163 assert!(config.proxy_url.is_none());
164 assert!(config.normalize_symbols);
165 assert!(matches!(
166 config.book_snapshot_output,
167 BookSnapshotOutput::Deltas
168 ));
169 assert!(!config.extract_bbo_as_quotes);
170 assert!(config.options.is_empty());
171 assert!(config.stream_options.is_empty());
172 }
173
174 #[rstest]
175 fn test_book_snapshot_output_default_is_deltas() {
176 assert!(matches!(
177 BookSnapshotOutput::default(),
178 BookSnapshotOutput::Deltas
179 ));
180 }
181
182 #[rstest]
183 fn test_book_snapshot_output_serde_roundtrip_deltas() {
184 let json = serde_json::to_string(&BookSnapshotOutput::Deltas).unwrap();
185 assert_eq!(json, "\"deltas\"");
186
187 let deserialized: BookSnapshotOutput = serde_json::from_str(&json).unwrap();
188 assert!(matches!(deserialized, BookSnapshotOutput::Deltas));
189 }
190
191 #[rstest]
192 fn test_book_snapshot_output_serde_roundtrip_depth10() {
193 let json = serde_json::to_string(&BookSnapshotOutput::Depth10).unwrap();
194 assert_eq!(json, "\"depth10\"");
195
196 let deserialized: BookSnapshotOutput = serde_json::from_str(&json).unwrap();
197 assert!(matches!(deserialized, BookSnapshotOutput::Depth10));
198 }
199
200 #[rstest]
201 fn test_parquet_compression_default_is_zstd() {
202 assert!(matches!(
203 ParquetCompression::default(),
204 ParquetCompression::Zstd
205 ));
206 assert!(matches!(
207 ParquetCompression::default().as_parquet_compression(),
208 Compression::ZSTD(_)
209 ));
210 }
211
212 #[rstest]
213 fn test_parquet_compression_serde_roundtrip() {
214 let cases = [
215 (ParquetCompression::Zstd, "\"zstd\""),
216 (ParquetCompression::Snappy, "\"snappy\""),
217 (ParquetCompression::Uncompressed, "\"uncompressed\""),
218 ];
219
220 for (compression, expected_json) in cases {
221 let json = serde_json::to_string(&compression).unwrap();
222 assert_eq!(json, expected_json);
223
224 let deserialized: ParquetCompression = serde_json::from_str(&json).unwrap();
225 assert_eq!(
226 compression.as_parquet_compression(),
227 deserialized.as_parquet_compression()
228 );
229 }
230 }
231
232 #[rstest]
233 fn test_data_client_config_toml_minimal() {
234 let config: TardisDataClientConfig = toml::from_str(
235 r#"
236normalize_symbols = false
237book_snapshot_output = "depth10"
238"#,
239 )
240 .unwrap();
241
242 assert!(!config.normalize_symbols);
243 assert!(matches!(
244 config.book_snapshot_output,
245 BookSnapshotOutput::Depth10
246 ));
247 assert!(!config.extract_bbo_as_quotes);
248 assert!(config.options.is_empty());
249 }
250
251 #[rstest]
252 fn test_replay_config_omits_options_uses_empty_default() {
253 let config: TardisReplayConfig = toml::from_str(
254 r#"
255tardis_ws_url = "wss://example.com"
256normalize_symbols = false
257"#,
258 )
259 .unwrap();
260
261 assert!(config.options.is_empty());
262 assert_eq!(config.tardis_ws_url.as_deref(), Some("wss://example.com"));
263 assert_eq!(config.normalize_symbols, Some(false));
264 assert_eq!(config.extract_bbo_as_quotes, None);
265 }
266
267 #[rstest]
268 fn test_replay_config_deserializes_compression() {
269 let json = r#"{
270 "tardis_ws_url": null,
271 "normalize_symbols": true,
272 "output_path": null,
273 "options": [],
274 "proxy_url": null,
275 "book_snapshot_output": "depth10",
276 "extract_bbo_as_quotes": true,
277 "compression": "zstd"
278 }"#;
279
280 let config: TardisReplayConfig = serde_json::from_str(json).unwrap();
281
282 assert!(matches!(
283 config.book_snapshot_output,
284 Some(BookSnapshotOutput::Depth10)
285 ));
286 assert_eq!(config.extract_bbo_as_quotes, Some(true));
287 assert!(matches!(config.compression, Some(ParquetCompression::Zstd)));
288 }
289}