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;
27
28use crate::{
29    cache::Cache,
30    clients::{DataClient, ExecutionClient},
31    clock::Clock,
32};
33
34/// Configuration for creating client instances.
35///
36/// This trait allows different client types to provide their configuration
37/// in a type-safe manner while still being usable in generic factory contexts.
38pub trait ClientConfig: Debug {
39    /// Return the configuration as a trait object.
40    fn as_any(&self) -> &dyn Any;
41}
42
43/// Factory trait for creating data client instances.
44///
45/// Implementations of this trait should create specific data client types
46/// (e.g., Binance, Bybit, Databento) based on the provided configuration.
47pub trait DataClientFactory: Debug {
48    /// Create a new data client instance.
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if client creation fails.
53    fn create(
54        &self,
55        name: &str,
56        config: &dyn ClientConfig,
57        cache: Rc<RefCell<Cache>>,
58        clock: Rc<RefCell<dyn Clock>>,
59    ) -> anyhow::Result<Box<dyn DataClient>>;
60
61    /// Returns the name of this factory.
62    fn name(&self) -> &str;
63
64    /// Returns the supported configuration type name for this factory.
65    fn config_type(&self) -> &str;
66}
67
68/// Factory trait for creating execution client instances.
69///
70/// Implementations of this trait should create specific execution client types
71/// (e.g., Binance, Bybit, Interactive Brokers) based on the provided configuration.
72pub trait ExecutionClientFactory: Debug {
73    /// Create a new execution client instance.
74    ///
75    /// # Errors
76    ///
77    /// Returns an error if client creation fails.
78    fn create(
79        &self,
80        name: &str,
81        config: &dyn ClientConfig,
82        cache: Rc<RefCell<Cache>>,
83    ) -> anyhow::Result<Box<dyn ExecutionClient>>;
84
85    /// Returns the name of this factory.
86    fn name(&self) -> &str;
87
88    /// Returns the supported configuration type name for this factory.
89    fn config_type(&self) -> &str;
90}
91
92/// Registry for managing data client factories.
93///
94/// Allows dynamic registration and lookup of factories by name,
95/// enabling a plugin-like architecture for different data providers.
96#[derive(Debug, Default)]
97pub struct DataClientFactoryRegistry {
98    factories: AHashMap<String, Box<dyn DataClientFactory>>,
99}
100
101impl DataClientFactoryRegistry {
102    /// Creates a new empty registry.
103    #[must_use]
104    pub fn new() -> Self {
105        Self {
106            factories: AHashMap::new(),
107        }
108    }
109
110    /// Registers a data client factory.
111    ///
112    /// # Errors
113    ///
114    /// Returns an error if a factory with the same name is already registered.
115    pub fn register(
116        &mut self,
117        name: String,
118        factory: Box<dyn DataClientFactory>,
119    ) -> anyhow::Result<()> {
120        if self.factories.contains_key(&name) {
121            anyhow::bail!("Data client factory '{name}' is already registered");
122        }
123
124        self.factories.insert(name, factory);
125        Ok(())
126    }
127
128    /// Gets a registered factory by name.
129    ///
130    /// # Returns
131    ///
132    /// The factory if found, None otherwise.
133    #[must_use]
134    pub fn get(&self, name: &str) -> Option<&dyn DataClientFactory> {
135        self.factories.get(name).map(std::convert::AsRef::as_ref)
136    }
137
138    /// Gets a list of all registered factory names.
139    #[must_use]
140    pub fn names(&self) -> Vec<&String> {
141        self.factories.keys().collect()
142    }
143
144    /// Checks if a factory is registered.
145    #[must_use]
146    pub fn contains(&self, name: &str) -> bool {
147        self.factories.contains_key(name)
148    }
149}
150
151/// Registry for managing execution client factories.
152///
153/// Allows dynamic registration and lookup of factories by name,
154/// enabling a plugin-like architecture for different execution providers.
155#[derive(Debug, Default)]
156pub struct ExecutionClientFactoryRegistry {
157    factories: AHashMap<String, Box<dyn ExecutionClientFactory>>,
158}
159
160impl ExecutionClientFactoryRegistry {
161    /// Creates a new empty registry.
162    #[must_use]
163    pub fn new() -> Self {
164        Self {
165            factories: AHashMap::new(),
166        }
167    }
168
169    /// Registers an execution client factory.
170    ///
171    /// # Errors
172    ///
173    /// Returns an error if a factory with the same name is already registered.
174    pub fn register(
175        &mut self,
176        name: String,
177        factory: Box<dyn ExecutionClientFactory>,
178    ) -> anyhow::Result<()> {
179        if self.factories.contains_key(&name) {
180            anyhow::bail!("Execution client factory '{name}' is already registered");
181        }
182
183        self.factories.insert(name, factory);
184        Ok(())
185    }
186
187    /// Gets a registered factory by name (if found).
188    #[must_use]
189    pub fn get(&self, name: &str) -> Option<&dyn ExecutionClientFactory> {
190        self.factories.get(name).map(std::convert::AsRef::as_ref)
191    }
192
193    /// Gets a list of all registered factory names.
194    #[must_use]
195    pub fn names(&self) -> Vec<&String> {
196        self.factories.keys().collect()
197    }
198
199    /// Checks if a factory is registered.
200    #[must_use]
201    pub fn contains(&self, name: &str) -> bool {
202        self.factories.contains_key(name)
203    }
204}
205
206#[allow(dead_code)]
207#[cfg(test)]
208mod tests {
209    use std::any::Any;
210
211    use rstest::*;
212
213    use super::*;
214
215    #[derive(Debug)]
216    struct MockConfig {
217        #[allow(dead_code)]
218        value: String,
219    }
220
221    impl ClientConfig for MockConfig {
222        fn as_any(&self) -> &dyn Any {
223            self
224        }
225    }
226
227    #[derive(Debug)]
228    struct MockDataClientFactory;
229
230    impl DataClientFactory for MockDataClientFactory {
231        fn create(
232            &self,
233            _name: &str,
234            _config: &dyn ClientConfig,
235            _cache: Rc<RefCell<Cache>>,
236            _clock: Rc<RefCell<dyn Clock>>,
237        ) -> anyhow::Result<Box<dyn DataClient>> {
238            Err(anyhow::anyhow!("Mock factory - not implemented"))
239        }
240
241        fn name(&self) -> &'static str {
242            "mock"
243        }
244
245        fn config_type(&self) -> &'static str {
246            "MockConfig"
247        }
248    }
249
250    #[rstest]
251    fn test_data_client_factory_registry() {
252        let mut registry = DataClientFactoryRegistry::new();
253
254        assert!(registry.names().is_empty());
255        assert!(!registry.contains("mock"));
256        assert!(registry.get("mock").is_none());
257
258        let factory = Box::new(MockDataClientFactory);
259        registry.register("mock".to_string(), factory).unwrap();
260
261        assert_eq!(registry.names().len(), 1);
262        assert!(registry.contains("mock"));
263        assert!(registry.get("mock").is_some());
264
265        let factory2 = Box::new(MockDataClientFactory);
266        let result = registry.register("mock".to_string(), factory2);
267        assert!(result.is_err());
268    }
269
270    #[rstest]
271    fn test_empty_data_client_factory_registry() {
272        let registry = DataClientFactoryRegistry::new();
273
274        assert!(registry.names().is_empty());
275        assert!(!registry.contains("mock"));
276        assert!(registry.get("mock").is_none());
277    }
278
279    #[rstest]
280    fn test_empty_execution_client_factory_registry() {
281        let registry = ExecutionClientFactoryRegistry::new();
282
283        assert!(registry.names().is_empty());
284        assert!(!registry.contains("mock"));
285        assert!(registry.get("mock").is_none());
286    }
287}