nautilus_system/
config.rs1use std::{fmt::Debug, time::Duration};
17
18use nautilus_common::{
19 cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig, msgbus::MessageBusConfig,
20};
21use nautilus_core::UUID4;
22use nautilus_data::engine::config::DataEngineConfig;
23use nautilus_execution::engine::config::ExecutionEngineConfig;
24use nautilus_model::identifiers::TraderId;
25#[cfg(feature = "streaming")]
26pub use nautilus_persistence::config::{DataCatalogConfig, RotationConfig, StreamingConfig};
27use nautilus_portfolio::config::PortfolioConfig;
28use nautilus_risk::engine::config::RiskEngineConfig;
29
30pub trait NautilusKernelConfig: Debug {
32 fn environment(&self) -> Environment;
34 fn trader_id(&self) -> TraderId;
36 fn load_state(&self) -> bool;
38 fn save_state(&self) -> bool;
40 fn shutdown_on_error(&self) -> bool;
44 fn logging(&self) -> LoggerConfig;
46 fn instance_id(&self) -> Option<UUID4>;
48 fn timeout_connection(&self) -> Duration;
50 fn timeout_reconciliation(&self) -> Duration;
52 fn timeout_portfolio(&self) -> Duration;
54 fn timeout_disconnection(&self) -> Duration;
56 fn delay_post_stop(&self) -> Duration;
58 fn timeout_shutdown(&self) -> Duration;
60 fn cache(&self) -> Option<CacheConfig>;
62 fn msgbus(&self) -> Option<MessageBusConfig>;
64 fn data_engine(&self) -> Option<DataEngineConfig>;
66 fn risk_engine(&self) -> Option<RiskEngineConfig>;
68 fn exec_engine(&self) -> Option<ExecutionEngineConfig>;
70 fn portfolio(&self) -> Option<PortfolioConfig>;
72 #[cfg(feature = "streaming")]
74 fn streaming(&self) -> Option<StreamingConfig> {
75 None
76 }
77 #[cfg(feature = "streaming")]
79 fn catalogs(&self) -> Vec<DataCatalogConfig> {
80 Vec::new()
81 }
82}
83
84#[derive(Debug, Clone, bon::Builder)]
86pub struct KernelConfig {
87 #[builder(default = Environment::Backtest)]
89 pub environment: Environment,
90 #[builder(default)]
92 pub trader_id: TraderId,
93 #[builder(default)]
95 pub load_state: bool,
96 #[builder(default)]
98 pub save_state: bool,
99 #[builder(default)]
103 pub shutdown_on_error: bool,
104 #[builder(default)]
106 pub logging: LoggerConfig,
107 pub instance_id: Option<UUID4>,
109 #[builder(default = Duration::from_mins(1))]
111 pub timeout_connection: Duration,
112 #[builder(default = Duration::from_secs(30))]
114 pub timeout_reconciliation: Duration,
115 #[builder(default = Duration::from_secs(10))]
117 pub timeout_portfolio: Duration,
118 #[builder(default = Duration::from_secs(10))]
120 pub timeout_disconnection: Duration,
121 #[builder(default = Duration::from_secs(10))]
123 pub delay_post_stop: Duration,
124 #[builder(default = Duration::from_secs(5))]
126 pub timeout_shutdown: Duration,
127 pub cache: Option<CacheConfig>,
129 pub msgbus: Option<MessageBusConfig>,
131 pub data_engine: Option<DataEngineConfig>,
133 pub risk_engine: Option<RiskEngineConfig>,
135 pub exec_engine: Option<ExecutionEngineConfig>,
137 pub portfolio: Option<PortfolioConfig>,
139 #[cfg(feature = "streaming")]
141 pub streaming: Option<StreamingConfig>,
142 #[cfg(feature = "streaming")]
144 #[builder(default)]
145 pub catalogs: Vec<DataCatalogConfig>,
146}
147
148impl NautilusKernelConfig for KernelConfig {
149 fn environment(&self) -> Environment {
150 self.environment
151 }
152
153 fn trader_id(&self) -> TraderId {
154 self.trader_id
155 }
156
157 fn load_state(&self) -> bool {
158 self.load_state
159 }
160
161 fn save_state(&self) -> bool {
162 self.save_state
163 }
164
165 fn shutdown_on_error(&self) -> bool {
166 self.shutdown_on_error
167 }
168
169 fn logging(&self) -> LoggerConfig {
170 self.logging.clone()
171 }
172
173 fn instance_id(&self) -> Option<UUID4> {
174 self.instance_id
175 }
176
177 fn timeout_connection(&self) -> Duration {
178 self.timeout_connection
179 }
180
181 fn timeout_reconciliation(&self) -> Duration {
182 self.timeout_reconciliation
183 }
184
185 fn timeout_portfolio(&self) -> Duration {
186 self.timeout_portfolio
187 }
188
189 fn timeout_disconnection(&self) -> Duration {
190 self.timeout_disconnection
191 }
192
193 fn delay_post_stop(&self) -> Duration {
194 self.delay_post_stop
195 }
196
197 fn timeout_shutdown(&self) -> Duration {
198 self.timeout_shutdown
199 }
200
201 fn cache(&self) -> Option<CacheConfig> {
202 self.cache.clone()
203 }
204
205 fn msgbus(&self) -> Option<MessageBusConfig> {
206 self.msgbus.clone()
207 }
208
209 fn data_engine(&self) -> Option<DataEngineConfig> {
210 self.data_engine.clone()
211 }
212
213 fn risk_engine(&self) -> Option<RiskEngineConfig> {
214 self.risk_engine.clone()
215 }
216
217 fn exec_engine(&self) -> Option<ExecutionEngineConfig> {
218 self.exec_engine.clone()
219 }
220
221 fn portfolio(&self) -> Option<PortfolioConfig> {
222 self.portfolio
223 }
224
225 #[cfg(feature = "streaming")]
226 fn streaming(&self) -> Option<StreamingConfig> {
227 self.streaming.clone()
228 }
229
230 #[cfg(feature = "streaming")]
231 fn catalogs(&self) -> Vec<DataCatalogConfig> {
232 self.catalogs.clone()
233 }
234}
235
236impl Default for KernelConfig {
237 fn default() -> Self {
238 Self::builder().build()
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use rstest::rstest;
245
246 use super::*;
247
248 #[rstest]
249 fn test_kernel_config_default_connection_timeout() {
250 let config = KernelConfig::default();
251
252 assert_eq!(config.timeout_connection, Duration::from_mins(1));
253 }
254}
255
256#[cfg(all(test, feature = "streaming"))]
257mod streaming_tests {
258 use nautilus_common::config::ConfigError;
259 use rstest::rstest;
260
261 use super::*;
262
263 #[rstest]
264 fn test_streaming_config_builder_valid() {
265 let config = StreamingConfig::builder()
266 .catalog_path("/data/catalog".to_string())
267 .fs_protocol("file".to_string())
268 .flush_interval_ms(1_000)
269 .replace_existing(false)
270 .rotation_config(RotationConfig::NoRotation)
271 .build();
272
273 assert!(config.is_ok());
274 }
275
276 #[rstest]
277 fn test_streaming_config_zero_flush_interval_rejected() {
278 let result = StreamingConfig::builder()
279 .catalog_path("/data/catalog".to_string())
280 .fs_protocol("file".to_string())
281 .flush_interval_ms(0)
282 .replace_existing(false)
283 .rotation_config(RotationConfig::NoRotation)
284 .build();
285
286 assert!(
287 matches!(result, Err(ConfigError::Range { field, .. }) if field == "flush_interval_ms")
288 );
289 }
290
291 #[rstest]
292 fn test_streaming_config_empty_catalog_path_rejected() {
293 let result = StreamingConfig::builder()
294 .catalog_path(String::new())
295 .fs_protocol("file".to_string())
296 .flush_interval_ms(1_000)
297 .replace_existing(false)
298 .rotation_config(RotationConfig::NoRotation)
299 .build();
300
301 assert!(
302 matches!(result, Err(ConfigError::EmptyField { field }) if field == "catalog_path")
303 );
304 }
305
306 #[rstest]
307 fn test_streaming_config_toml_round_trip() {
308 let config: StreamingConfig = toml::from_str(
309 r#"
310catalog_path = "/data/catalog"
311fs_protocol = "file"
312flush_interval_ms = 1000
313replace_existing = false
314
315[rotation_config.size]
316max_size = 1048576
317"#,
318 )
319 .unwrap();
320
321 assert_eq!(config.catalog_path, "/data/catalog");
322 assert_eq!(config.fs_protocol, "file");
323 assert_eq!(config.flush_interval_ms, 1000);
324 assert!(!config.replace_existing);
325 assert!(matches!(
326 config.rotation_config,
327 RotationConfig::Size {
328 max_size: 1_048_576
329 }
330 ));
331 }
332
333 #[rstest]
334 fn test_streaming_config_with_no_rotation_toml() {
335 let config: StreamingConfig = toml::from_str(
336 r#"
337catalog_path = "/data/catalog"
338fs_protocol = "file"
339flush_interval_ms = 500
340replace_existing = true
341rotation_config = "no_rotation"
342"#,
343 )
344 .unwrap();
345
346 assert!(matches!(config.rotation_config, RotationConfig::NoRotation));
347 assert!(config.replace_existing);
348 }
349}