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 pub options: Vec<ReplayNormalizedRequestOptions>,
77 pub proxy_url: Option<String>,
80 pub book_snapshot_output: Option<BookSnapshotOutput>,
85 pub compression: Option<ParquetCompression>,
91}
92
93#[derive(Clone, Debug, bon::Builder)]
95#[cfg_attr(
96 feature = "python",
97 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.tardis", from_py_object)
98)]
99#[cfg_attr(
100 feature = "python",
101 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.tardis")
102)]
103pub struct TardisDataClientConfig {
104 pub api_key: Option<String>,
107 pub tardis_ws_url: Option<String>,
110 pub proxy_url: Option<String>,
113 #[builder(default = true)]
115 pub normalize_symbols: bool,
116 #[builder(default)]
118 pub book_snapshot_output: BookSnapshotOutput,
119 #[builder(default)]
122 pub options: Vec<ReplayNormalizedRequestOptions>,
123 #[builder(default)]
127 pub stream_options: Vec<StreamNormalizedRequestOptions>,
128}
129
130impl Default for TardisDataClientConfig {
131 fn default() -> Self {
132 Self::builder().build()
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use rstest::rstest;
139
140 use super::*;
141
142 #[rstest]
143 fn test_default_config_values() {
144 let config = TardisDataClientConfig::default();
145 assert!(config.api_key.is_none());
146 assert!(config.tardis_ws_url.is_none());
147 assert!(config.proxy_url.is_none());
148 assert!(config.normalize_symbols);
149 assert!(matches!(
150 config.book_snapshot_output,
151 BookSnapshotOutput::Deltas
152 ));
153 assert!(config.options.is_empty());
154 assert!(config.stream_options.is_empty());
155 }
156
157 #[rstest]
158 fn test_book_snapshot_output_default_is_deltas() {
159 assert!(matches!(
160 BookSnapshotOutput::default(),
161 BookSnapshotOutput::Deltas
162 ));
163 }
164
165 #[rstest]
166 fn test_book_snapshot_output_serde_roundtrip_deltas() {
167 let json = serde_json::to_string(&BookSnapshotOutput::Deltas).unwrap();
168 assert_eq!(json, "\"deltas\"");
169
170 let deserialized: BookSnapshotOutput = serde_json::from_str(&json).unwrap();
171 assert!(matches!(deserialized, BookSnapshotOutput::Deltas));
172 }
173
174 #[rstest]
175 fn test_book_snapshot_output_serde_roundtrip_depth10() {
176 let json = serde_json::to_string(&BookSnapshotOutput::Depth10).unwrap();
177 assert_eq!(json, "\"depth10\"");
178
179 let deserialized: BookSnapshotOutput = serde_json::from_str(&json).unwrap();
180 assert!(matches!(deserialized, BookSnapshotOutput::Depth10));
181 }
182
183 #[rstest]
184 fn test_parquet_compression_default_is_zstd() {
185 assert!(matches!(
186 ParquetCompression::default(),
187 ParquetCompression::Zstd
188 ));
189 assert!(matches!(
190 ParquetCompression::default().as_parquet_compression(),
191 Compression::ZSTD(_)
192 ));
193 }
194
195 #[rstest]
196 fn test_parquet_compression_serde_roundtrip() {
197 let cases = [
198 (ParquetCompression::Zstd, "\"zstd\""),
199 (ParquetCompression::Snappy, "\"snappy\""),
200 (ParquetCompression::Uncompressed, "\"uncompressed\""),
201 ];
202
203 for (compression, expected_json) in cases {
204 let json = serde_json::to_string(&compression).unwrap();
205 assert_eq!(json, expected_json);
206
207 let deserialized: ParquetCompression = serde_json::from_str(&json).unwrap();
208 assert_eq!(
209 compression.as_parquet_compression(),
210 deserialized.as_parquet_compression()
211 );
212 }
213 }
214
215 #[rstest]
216 fn test_replay_config_deserializes_compression() {
217 let json = r#"{
218 "tardis_ws_url": null,
219 "normalize_symbols": true,
220 "output_path": null,
221 "options": [],
222 "proxy_url": null,
223 "book_snapshot_output": "depth10",
224 "compression": "zstd"
225 }"#;
226
227 let config: TardisReplayConfig = serde_json::from_str(json).unwrap();
228
229 assert!(matches!(
230 config.book_snapshot_output,
231 Some(BookSnapshotOutput::Depth10)
232 ));
233 assert!(matches!(config.compression, Some(ParquetCompression::Zstd)));
234 }
235}