Skip to main content

nautilus_common/factories/
client.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.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//! Client factory traits and registries shared by live, sandbox, and backtest runtimes.
17//!
18//! The traits in this module describe how to construct adapter [`DataClient`] and
19//! [`ExecutionClient`] instances from a configuration object, without coupling the
20//! definition site to any particular runtime (tokio, native threads, simulated time).
21//! Adapters implement these traits in their own crates; the live system kernel and
22//! any future backtest registry consume them via the registries below.
23
24use std::{any::Any, cell::RefCell, fmt::Debug, rc::Rc};
25
26use ahash::AHashMap;
27use nautilus_model::identifiers::TraderId;
28
29use crate::{
30    cache::{Cache, CacheView},
31    clients::{DataClient, ExecutionClient},
32    clock::Clock,
33};
34
35/// Configuration for creating client instances.
36///
37/// This trait allows different client types to provide their configuration
38/// in a type-safe manner while still being usable in generic factory contexts.
39pub trait ClientConfig: Debug {
40    /// Return the configuration as a trait object.
41    fn as_any(&self) -> &dyn Any;
42}
43
44/// Factory trait for creating data client instances.
45///
46/// Implementations of this trait should create specific data client types
47/// (e.g., Binance, Bybit, Databento) based on the provided configuration.
48pub trait DataClientFactory: Debug {
49    /// Create a new data client instance.
50    ///
51    /// Data clients receive a read-only cache view so adapters can query platform state during
52    /// construction without mutating the cache.
53    ///
54    /// # Errors
55    ///
56    /// Returns an error if client creation fails.
57    fn create(
58        &self,
59        name: &str,
60        config: &dyn ClientConfig,
61        cache: CacheView,
62        clock: Rc<RefCell<dyn Clock>>,
63    ) -> anyhow::Result<Box<dyn DataClient>>;
64
65    /// Returns the name of this factory.
66    fn name(&self) -> &str;
67
68    /// Returns the supported configuration type name for this factory.
69    fn config_type(&self) -> &str;
70}
71
72/// Factory trait for creating execution client instances.
73///
74/// Implementations of this trait should create specific execution client types
75/// (e.g., Binance, Bybit, Interactive Brokers) based on the provided configuration.
76pub trait ExecutionClientFactory: Debug {
77    /// Create a new execution client instance.
78    ///
79    /// Execution clients receive a read-only cache view so venue adapters can query platform state
80    /// during construction without mutating the cache. The trader ID and clock come from the owning node.
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if client creation fails.
85    fn create(
86        &self,
87        trader_id: TraderId,
88        name: &str,
89        config: &dyn ClientConfig,
90        cache: CacheView,
91        clock: Rc<RefCell<dyn Clock>>,
92    ) -> anyhow::Result<Box<dyn ExecutionClient>>;
93
94    /// Returns the name of this factory.
95    fn name(&self) -> &str;
96
97    /// Returns the supported configuration type name for this factory.
98    fn config_type(&self) -> &str;
99}
100
101/// Factory trait for simulated execution clients owned by the sync core.
102///
103/// Simulated execution clients may need the mutable cache handle because they host the matching
104/// engine and therefore own cache updates that a live venue adapter must not perform.
105pub trait SimulatedExecutionClientFactory: Debug {
106    /// Create a new simulated execution client instance.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if client creation fails.
111    fn create(
112        &self,
113        trader_id: TraderId,
114        name: &str,
115        config: &dyn ClientConfig,
116        cache: Rc<RefCell<Cache>>,
117    ) -> anyhow::Result<Box<dyn ExecutionClient>>;
118
119    /// Returns the name of this factory.
120    fn name(&self) -> &str;
121
122    /// Returns the supported configuration type name for this factory.
123    fn config_type(&self) -> &str;
124}
125
126/// Registry for managing data client factories.
127///
128/// Allows dynamic registration and lookup of factories by name,
129/// enabling a plugin-like architecture for different data providers.
130#[derive(Debug, Default)]
131pub struct DataClientFactoryRegistry {
132    factories: AHashMap<String, Box<dyn DataClientFactory>>,
133}
134
135impl DataClientFactoryRegistry {
136    /// Creates a new empty registry.
137    #[must_use]
138    pub fn new() -> Self {
139        Self {
140            factories: AHashMap::new(),
141        }
142    }
143
144    /// Registers a data client factory.
145    ///
146    /// # Errors
147    ///
148    /// Returns an error if a factory with the same name is already registered.
149    pub fn register(
150        &mut self,
151        name: String,
152        factory: Box<dyn DataClientFactory>,
153    ) -> anyhow::Result<()> {
154        if self.factories.contains_key(&name) {
155            anyhow::bail!("Data client factory '{name}' is already registered");
156        }
157
158        self.factories.insert(name, factory);
159        Ok(())
160    }
161
162    /// Gets a registered factory by name.
163    ///
164    /// # Returns
165    ///
166    /// The factory if found, None otherwise.
167    #[must_use]
168    pub fn get(&self, name: &str) -> Option<&dyn DataClientFactory> {
169        self.factories.get(name).map(std::convert::AsRef::as_ref)
170    }
171
172    /// Gets a list of all registered factory names.
173    #[must_use]
174    pub fn names(&self) -> Vec<&String> {
175        self.factories.keys().collect()
176    }
177
178    /// Checks if a factory is registered.
179    #[must_use]
180    pub fn contains(&self, name: &str) -> bool {
181        self.factories.contains_key(name)
182    }
183}
184
185/// Registry for managing execution client factories.
186///
187/// Allows dynamic registration and lookup of factories by name,
188/// enabling a plugin-like architecture for different execution providers.
189#[derive(Debug, Default)]
190pub struct ExecutionClientFactoryRegistry {
191    factories: AHashMap<String, Box<dyn ExecutionClientFactory>>,
192}
193
194impl ExecutionClientFactoryRegistry {
195    /// Creates a new empty registry.
196    #[must_use]
197    pub fn new() -> Self {
198        Self {
199            factories: AHashMap::new(),
200        }
201    }
202
203    /// Registers an execution client factory.
204    ///
205    /// # Errors
206    ///
207    /// Returns an error if a factory with the same name is already registered.
208    pub fn register(
209        &mut self,
210        name: String,
211        factory: Box<dyn ExecutionClientFactory>,
212    ) -> anyhow::Result<()> {
213        if self.factories.contains_key(&name) {
214            anyhow::bail!("Execution client factory '{name}' is already registered");
215        }
216
217        self.factories.insert(name, factory);
218        Ok(())
219    }
220
221    /// Gets a registered factory by name (if found).
222    #[must_use]
223    pub fn get(&self, name: &str) -> Option<&dyn ExecutionClientFactory> {
224        self.factories.get(name).map(std::convert::AsRef::as_ref)
225    }
226
227    /// Gets a list of all registered factory names.
228    #[must_use]
229    pub fn names(&self) -> Vec<&String> {
230        self.factories.keys().collect()
231    }
232
233    /// Checks if a factory is registered.
234    #[must_use]
235    pub fn contains(&self, name: &str) -> bool {
236        self.factories.contains_key(name)
237    }
238}
239
240#[allow(dead_code)]
241#[cfg(test)]
242mod tests {
243    use std::any::Any;
244
245    use rstest::*;
246
247    use super::*;
248
249    #[derive(Debug)]
250    struct MockConfig {
251        #[allow(dead_code)]
252        value: String,
253    }
254
255    impl ClientConfig for MockConfig {
256        fn as_any(&self) -> &dyn Any {
257            self
258        }
259    }
260
261    #[derive(Debug)]
262    struct MockDataClientFactory;
263
264    impl DataClientFactory for MockDataClientFactory {
265        fn create(
266            &self,
267            _name: &str,
268            _config: &dyn ClientConfig,
269            _cache: CacheView,
270            _clock: Rc<RefCell<dyn Clock>>,
271        ) -> anyhow::Result<Box<dyn DataClient>> {
272            Err(anyhow::anyhow!("Mock factory - not implemented"))
273        }
274
275        fn name(&self) -> &'static str {
276            "mock"
277        }
278
279        fn config_type(&self) -> &'static str {
280            "MockConfig"
281        }
282    }
283
284    #[derive(Debug)]
285    struct MockExecutionClientFactory;
286
287    impl ExecutionClientFactory for MockExecutionClientFactory {
288        fn create(
289            &self,
290            _trader_id: TraderId,
291            _name: &str,
292            _config: &dyn ClientConfig,
293            _cache: CacheView,
294            _clock: Rc<RefCell<dyn Clock>>,
295        ) -> anyhow::Result<Box<dyn ExecutionClient>> {
296            Err(anyhow::anyhow!("Mock factory - not implemented"))
297        }
298
299        fn name(&self) -> &'static str {
300            "mock-exec"
301        }
302
303        fn config_type(&self) -> &'static str {
304            "MockConfig"
305        }
306    }
307
308    #[rstest]
309    fn test_data_client_factory_registry() {
310        let mut registry = DataClientFactoryRegistry::new();
311
312        assert!(registry.names().is_empty());
313        assert!(!registry.contains("mock"));
314        assert!(registry.get("mock").is_none());
315
316        let factory = Box::new(MockDataClientFactory);
317        registry.register("mock".to_string(), factory).unwrap();
318
319        assert_eq!(registry.names().len(), 1);
320        assert!(registry.contains("mock"));
321        assert!(registry.get("mock").is_some());
322
323        let factory2 = Box::new(MockDataClientFactory);
324        let error = registry.register("mock".to_string(), factory2).unwrap_err();
325        assert_eq!(
326            error.to_string(),
327            "Data client factory 'mock' is already registered"
328        );
329        assert_eq!(
330            registry.names().len(),
331            1,
332            "rejected registration must not be stored"
333        );
334    }
335
336    #[rstest]
337    fn test_execution_client_factory_registry() {
338        let mut registry = ExecutionClientFactoryRegistry::new();
339
340        assert!(registry.names().is_empty());
341        assert!(!registry.contains("mock-exec"));
342        assert!(registry.get("mock-exec").is_none());
343
344        registry
345            .register(
346                "mock-exec".to_string(),
347                Box::new(MockExecutionClientFactory),
348            )
349            .unwrap();
350
351        assert_eq!(registry.names(), vec![&"mock-exec".to_string()]);
352        assert!(registry.contains("mock-exec"));
353        assert_eq!(registry.get("mock-exec").unwrap().name(), "mock-exec");
354        assert_eq!(
355            registry.get("mock-exec").unwrap().config_type(),
356            "MockConfig"
357        );
358
359        let error = registry
360            .register(
361                "mock-exec".to_string(),
362                Box::new(MockExecutionClientFactory),
363            )
364            .unwrap_err();
365
366        assert_eq!(
367            error.to_string(),
368            "Execution client factory 'mock-exec' is already registered"
369        );
370        assert_eq!(
371            registry.names().len(),
372            1,
373            "rejected registration must not be stored"
374        );
375    }
376
377    #[rstest]
378    fn test_registries_do_not_match_unregistered_names() {
379        let mut data_registry = DataClientFactoryRegistry::new();
380        let mut execution_registry = ExecutionClientFactoryRegistry::new();
381
382        data_registry
383            .register("mock".to_string(), Box::new(MockDataClientFactory))
384            .unwrap();
385        execution_registry
386            .register(
387                "mock-exec".to_string(),
388                Box::new(MockExecutionClientFactory),
389            )
390            .unwrap();
391
392        assert!(!data_registry.contains("mock-exec"));
393        assert!(data_registry.get("mock-exec").is_none());
394        assert!(!execution_registry.contains("mock"));
395        assert!(execution_registry.get("mock").is_none());
396    }
397
398    #[rstest]
399    fn test_default_registries_are_empty() {
400        assert!(DataClientFactoryRegistry::default().names().is_empty());
401        assert!(ExecutionClientFactoryRegistry::default().names().is_empty());
402    }
403}