nautilus_live/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Configuration types for live Posei system nodes.
17
18use std::collections::HashMap;
19
20use nautilus_common::{
21    cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig,
22    msgbus::database::MessageBusConfig,
23};
24use nautilus_core::UUID4;
25use posei_trader::engine::config::DataEngineConfig;
26use nautilus_execution::engine::config::ExecutionEngineConfig;
27use nautilus_model::identifiers::TraderId;
28use nautilus_persistence::config::StreamingConfig;
29use nautilus_portfolio::config::PortfolioConfig;
30use nautilus_risk::engine::config::RiskEngineConfig;
31use nautilus_system::config::PoseiKernelConfig;
32use serde::{Deserialize, Serialize};
33
34/// Configuration for live data engines.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct LiveDataEngineConfig {
37    /// The queue size for the engine's internal queue buffers.
38    pub qsize: u32,
39}
40
41impl Default for LiveDataEngineConfig {
42    fn default() -> Self {
43        Self { qsize: 100_000 }
44    }
45}
46
47impl From<LiveDataEngineConfig> for DataEngineConfig {
48    fn from(_config: LiveDataEngineConfig) -> Self {
49        Self::default()
50    }
51}
52
53/// Configuration for live risk engines.
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct LiveRiskEngineConfig {
56    /// The queue size for the engine's internal queue buffers.
57    pub qsize: u32,
58}
59
60impl Default for LiveRiskEngineConfig {
61    fn default() -> Self {
62        Self { qsize: 100_000 }
63    }
64}
65
66impl From<LiveRiskEngineConfig> for RiskEngineConfig {
67    fn from(_config: LiveRiskEngineConfig) -> Self {
68        Self::default()
69    }
70}
71
72/// Configuration for live execution engines.
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct LiveExecEngineConfig {
75    /// If reconciliation is active at start-up.
76    pub reconciliation: bool,
77    /// The maximum lookback minutes to reconcile state for.
78    pub reconciliation_lookback_mins: Option<u32>,
79    /// If unclaimed order events with an EXTERNAL strategy ID should be filtered/dropped.
80    pub filter_unclaimed_external_orders: bool,
81    /// If position status reports are filtered from reconciliation.
82    pub filter_position_reports: bool,
83    /// If MARKET order events will be generated during reconciliation to align discrepancies.
84    pub generate_missing_orders: bool,
85    /// The interval (milliseconds) between checking whether in-flight orders have exceeded their threshold.
86    pub inflight_check_interval_ms: u32,
87    /// The threshold (milliseconds) beyond which an in-flight order's status is checked with the venue.
88    pub inflight_check_threshold_ms: u32,
89    /// The number of retry attempts for verifying in-flight order status.
90    pub inflight_check_retries: u32,
91    /// The interval (seconds) between auditing own books against public order books.
92    pub own_books_audit_interval_secs: Option<f64>,
93    /// The interval (seconds) between checks for open orders at the venue.
94    pub open_check_interval_secs: Option<f64>,
95    /// If the `check_open_orders` requests only currently open orders from the venue.
96    pub open_check_open_only: bool,
97    /// The interval (minutes) between purging closed orders from the in-memory cache.
98    pub purge_closed_orders_interval_mins: Option<u32>,
99    /// The time buffer (minutes) before closed orders can be purged.
100    pub purge_closed_orders_buffer_mins: Option<u32>,
101    /// The interval (minutes) between purging closed positions from the in-memory cache.
102    pub purge_closed_positions_interval_mins: Option<u32>,
103    /// The time buffer (minutes) before closed positions can be purged.
104    pub purge_closed_positions_buffer_mins: Option<u32>,
105    /// The interval (minutes) between purging account events from the in-memory cache.
106    pub purge_account_events_interval_mins: Option<u32>,
107    /// The time buffer (minutes) before account events can be purged.
108    pub purge_account_events_lookback_mins: Option<u32>,
109    /// The queue size for the engine's internal queue buffers.
110    pub qsize: u32,
111}
112
113impl Default for LiveExecEngineConfig {
114    fn default() -> Self {
115        Self {
116            reconciliation: true,
117            reconciliation_lookback_mins: None,
118            filter_unclaimed_external_orders: false,
119            filter_position_reports: false,
120            generate_missing_orders: true,
121            inflight_check_interval_ms: 2_000,
122            inflight_check_threshold_ms: 5_000,
123            inflight_check_retries: 5,
124            own_books_audit_interval_secs: None,
125            open_check_interval_secs: None,
126            open_check_open_only: true,
127            purge_closed_orders_interval_mins: None,
128            purge_closed_orders_buffer_mins: None,
129            purge_closed_positions_interval_mins: None,
130            purge_closed_positions_buffer_mins: None,
131            purge_account_events_interval_mins: None,
132            purge_account_events_lookback_mins: None,
133            qsize: 100_000,
134        }
135    }
136}
137
138impl From<LiveExecEngineConfig> for ExecutionEngineConfig {
139    fn from(_config: LiveExecEngineConfig) -> Self {
140        Self::default()
141    }
142}
143
144/// Configuration for live client message routing.
145#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
146pub struct RoutingConfig {
147    /// If the client should be registered as the default routing client.
148    pub default: bool,
149    /// The venues to register for routing.
150    pub venues: Option<Vec<String>>,
151}
152
153/// Configuration for instrument providers.
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct InstrumentProviderConfig {
156    /// Whether to load all instruments on startup.
157    pub load_all: bool,
158    /// Whether to load instrument IDs only.
159    pub load_ids: bool,
160    /// Filters for loading specific instruments.
161    pub filters: HashMap<String, String>,
162}
163
164impl Default for InstrumentProviderConfig {
165    fn default() -> Self {
166        Self {
167            load_all: false,
168            load_ids: true,
169            filters: HashMap::new(),
170        }
171    }
172}
173
174/// Configuration for live data clients.
175#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
176pub struct LiveDataClientConfig {
177    /// If `DataClient` will emit bar updates when a new bar opens.
178    pub handle_revised_bars: bool,
179    /// The client's instrument provider configuration.
180    pub instrument_provider: InstrumentProviderConfig,
181    /// The client's message routing configuration.
182    pub routing: RoutingConfig,
183}
184
185/// Configuration for live execution clients.
186#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
187pub struct LiveExecClientConfig {
188    /// The client's instrument provider configuration.
189    pub instrument_provider: InstrumentProviderConfig,
190    /// The client's message routing configuration.
191    pub routing: RoutingConfig,
192}
193
194/// Configuration for live Posei system nodes.
195#[derive(Debug, Clone)]
196pub struct LiveNodeConfig {
197    /// The trading environment.
198    pub environment: Environment,
199    /// The trader ID for the node.
200    pub trader_id: TraderId,
201    /// If trading strategy state should be loaded from the database on start.
202    pub load_state: bool,
203    /// If trading strategy state should be saved to the database on stop.
204    pub save_state: bool,
205    /// The logging configuration for the kernel.
206    pub logging: LoggerConfig,
207    /// The unique instance identifier for the kernel
208    pub instance_id: Option<UUID4>,
209    /// The timeout (seconds) for all clients to connect and initialize.
210    pub timeout_connection: u32,
211    /// The timeout (seconds) for execution state to reconcile.
212    pub timeout_reconciliation: u32,
213    /// The timeout (seconds) for portfolio to initialize margins and unrealized pnls.
214    pub timeout_portfolio: u32,
215    /// The timeout (seconds) for all engine clients to disconnect.
216    pub timeout_disconnection: u32,
217    /// The timeout (seconds) after stopping the node to await residual events before final shutdown.
218    pub timeout_post_stop: u32,
219    /// The timeout (seconds) to await pending tasks cancellation during shutdown.
220    pub timeout_shutdown: u32,
221    /// The cache configuration.
222    pub cache: Option<CacheConfig>,
223    /// The message bus configuration.
224    pub msgbus: Option<MessageBusConfig>,
225    /// The portfolio configuration.
226    pub portfolio: Option<PortfolioConfig>,
227    /// The configuration for streaming to feather files.
228    pub streaming: Option<StreamingConfig>,
229    /// The live data engine configuration.
230    pub data_engine: LiveDataEngineConfig,
231    /// The live risk engine configuration.
232    pub risk_engine: LiveRiskEngineConfig,
233    /// The live execution engine configuration.
234    pub exec_engine: LiveExecEngineConfig,
235    /// The data client configurations.
236    pub data_clients: HashMap<String, LiveDataClientConfig>,
237    /// The execution client configurations.
238    pub exec_clients: HashMap<String, LiveExecClientConfig>,
239}
240
241impl Default for LiveNodeConfig {
242    fn default() -> Self {
243        Self {
244            environment: Environment::Live,
245            trader_id: TraderId::from("TRADER-001"),
246            load_state: false,
247            save_state: false,
248            logging: LoggerConfig::default(),
249            instance_id: None,
250            timeout_connection: 60,
251            timeout_reconciliation: 30,
252            timeout_portfolio: 10,
253            timeout_disconnection: 10,
254            timeout_post_stop: 10,
255            timeout_shutdown: 5,
256            cache: None,
257            msgbus: None,
258            portfolio: None,
259            streaming: None,
260            data_engine: LiveDataEngineConfig::default(),
261            risk_engine: LiveRiskEngineConfig::default(),
262            exec_engine: LiveExecEngineConfig::default(),
263            data_clients: HashMap::new(),
264            exec_clients: HashMap::new(),
265        }
266    }
267}
268
269impl PoseiKernelConfig for LiveNodeConfig {
270    fn environment(&self) -> Environment {
271        self.environment
272    }
273
274    fn trader_id(&self) -> TraderId {
275        self.trader_id
276    }
277
278    fn load_state(&self) -> bool {
279        self.load_state
280    }
281
282    fn save_state(&self) -> bool {
283        self.save_state
284    }
285
286    fn logging(&self) -> LoggerConfig {
287        self.logging.clone()
288    }
289
290    fn instance_id(&self) -> Option<UUID4> {
291        self.instance_id
292    }
293
294    fn timeout_connection(&self) -> u32 {
295        self.timeout_connection
296    }
297
298    fn timeout_reconciliation(&self) -> u32 {
299        self.timeout_reconciliation
300    }
301
302    fn timeout_portfolio(&self) -> u32 {
303        self.timeout_portfolio
304    }
305
306    fn timeout_disconnection(&self) -> u32 {
307        self.timeout_disconnection
308    }
309
310    fn timeout_post_stop(&self) -> u32 {
311        self.timeout_post_stop
312    }
313
314    fn timeout_shutdown(&self) -> u32 {
315        self.timeout_shutdown
316    }
317
318    fn cache(&self) -> Option<CacheConfig> {
319        self.cache.clone()
320    }
321
322    fn msgbus(&self) -> Option<MessageBusConfig> {
323        self.msgbus.clone()
324    }
325
326    fn data_engine(&self) -> Option<DataEngineConfig> {
327        Some(self.data_engine.clone().into())
328    }
329
330    fn risk_engine(&self) -> Option<RiskEngineConfig> {
331        Some(self.risk_engine.clone().into())
332    }
333
334    fn exec_engine(&self) -> Option<ExecutionEngineConfig> {
335        Some(self.exec_engine.clone().into())
336    }
337
338    fn portfolio(&self) -> Option<PortfolioConfig> {
339        self.portfolio.clone()
340    }
341
342    fn streaming(&self) -> Option<nautilus_persistence::config::StreamingConfig> {
343        self.streaming.clone()
344    }
345}
346
347////////////////////////////////////////////////////////////////////////////////
348// Tests
349////////////////////////////////////////////////////////////////////////////////
350
351#[cfg(test)]
352mod tests {
353    use rstest::rstest;
354
355    use super::*;
356
357    #[rstest]
358    fn test_trading_node_config_default() {
359        let config = LiveNodeConfig::default();
360
361        assert_eq!(config.environment, Environment::Live);
362        assert_eq!(config.trader_id, TraderId::from("TRADER-001"));
363        assert_eq!(config.data_engine.qsize, 100_000);
364        assert_eq!(config.risk_engine.qsize, 100_000);
365        assert_eq!(config.exec_engine.qsize, 100_000);
366        assert!(config.exec_engine.reconciliation);
367        assert!(!config.exec_engine.filter_unclaimed_external_orders);
368        assert!(config.data_clients.is_empty());
369        assert!(config.exec_clients.is_empty());
370    }
371
372    #[rstest]
373    fn test_trading_node_config_as_kernel_config() {
374        let config = LiveNodeConfig::default();
375
376        assert_eq!(config.environment(), Environment::Live);
377        assert_eq!(config.trader_id(), TraderId::from("TRADER-001"));
378        assert!(config.data_engine().is_some());
379        assert!(config.risk_engine().is_some());
380        assert!(config.exec_engine().is_some());
381        assert!(!config.load_state());
382        assert!(!config.save_state());
383    }
384
385    #[rstest]
386    fn test_live_exec_engine_config_defaults() {
387        let config = LiveExecEngineConfig::default();
388
389        assert!(config.reconciliation);
390        assert_eq!(config.reconciliation_lookback_mins, None);
391        assert!(!config.filter_unclaimed_external_orders);
392        assert!(!config.filter_position_reports);
393        assert!(config.generate_missing_orders);
394        assert_eq!(config.inflight_check_interval_ms, 2_000);
395        assert_eq!(config.inflight_check_threshold_ms, 5_000);
396        assert_eq!(config.inflight_check_retries, 5);
397        assert!(config.open_check_open_only);
398        assert_eq!(config.qsize, 100_000);
399    }
400
401    #[rstest]
402    fn test_routing_config_default() {
403        let config = RoutingConfig::default();
404
405        assert!(!config.default);
406        assert_eq!(config.venues, None);
407    }
408
409    #[rstest]
410    fn test_live_data_client_config_default() {
411        let config = LiveDataClientConfig::default();
412
413        assert!(!config.handle_revised_bars);
414        assert!(!config.instrument_provider.load_all);
415        assert!(config.instrument_provider.load_ids);
416        assert!(!config.routing.default);
417    }
418}