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, CacheView},
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    /// Data clients receive a read-only cache view so adapters can query platform state during
51    /// construction without mutating the cache.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error if client creation fails.
56    fn create(
57        &self,
58        name: &str,
59        config: &dyn ClientConfig,
60        cache: CacheView,
61        clock: Rc<RefCell<dyn Clock>>,
62    ) -> anyhow::Result<Box<dyn DataClient>>;
63
64    /// Returns the name of this factory.
65    fn name(&self) -> &str;
66
67    /// Returns the supported configuration type name for this factory.
68    fn config_type(&self) -> &str;
69}
70
71/// Factory trait for creating execution client instances.
72///
73/// Implementations of this trait should create specific execution client types
74/// (e.g., Binance, Bybit, Interactive Brokers) based on the provided configuration.
75pub trait ExecutionClientFactory: Debug {
76    /// Create a new execution client instance.
77    ///
78    /// Execution clients receive a read-only cache view so venue adapters can query platform state
79    /// during construction without mutating the cache.
80    ///
81    /// # Errors
82    ///
83    /// Returns an error if client creation fails.
84    fn create(
85        &self,
86        name: &str,
87        config: &dyn ClientConfig,
88        cache: CacheView,
89    ) -> anyhow::Result<Box<dyn ExecutionClient>>;
90
91    /// Returns the name of this factory.
92    fn name(&self) -> &str;
93
94    /// Returns the supported configuration type name for this factory.
95    fn config_type(&self) -> &str;
96}
97
98/// Factory trait for simulated execution clients owned by the sync core.
99///
100/// Simulated execution clients may need the mutable cache handle because they host the matching
101/// engine and therefore own cache updates that a live venue adapter must not perform.
102pub trait SimulatedExecutionClientFactory: Debug {
103    /// Create a new simulated execution client instance.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if client creation fails.
108    fn create(
109        &self,
110        name: &str,
111        config: &dyn ClientConfig,
112        cache: Rc<RefCell<Cache>>,
113    ) -> anyhow::Result<Box<dyn ExecutionClient>>;
114
115    /// Returns the name of this factory.
116    fn name(&self) -> &str;
117
118    /// Returns the supported configuration type name for this factory.
119    fn config_type(&self) -> &str;
120}
121
122/// Registry for managing data client factories.
123///
124/// Allows dynamic registration and lookup of factories by name,
125/// enabling a plugin-like architecture for different data providers.
126#[derive(Debug, Default)]
127pub struct DataClientFactoryRegistry {
128    factories: AHashMap<String, Box<dyn DataClientFactory>>,
129}
130
131impl DataClientFactoryRegistry {
132    /// Creates a new empty registry.
133    #[must_use]
134    pub fn new() -> Self {
135        Self {
136            factories: AHashMap::new(),
137        }
138    }
139
140    /// Registers a data client factory.
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if a factory with the same name is already registered.
145    pub fn register(
146        &mut self,
147        name: String,
148        factory: Box<dyn DataClientFactory>,
149    ) -> anyhow::Result<()> {
150        if self.factories.contains_key(&name) {
151            anyhow::bail!("Data client factory '{name}' is already registered");
152        }
153
154        self.factories.insert(name, factory);
155        Ok(())
156    }
157
158    /// Gets a registered factory by name.
159    ///
160    /// # Returns
161    ///
162    /// The factory if found, None otherwise.
163    #[must_use]
164    pub fn get(&self, name: &str) -> Option<&dyn DataClientFactory> {
165        self.factories.get(name).map(std::convert::AsRef::as_ref)
166    }
167
168    /// Gets a list of all registered factory names.
169    #[must_use]
170    pub fn names(&self) -> Vec<&String> {
171        self.factories.keys().collect()
172    }
173
174    /// Checks if a factory is registered.
175    #[must_use]
176    pub fn contains(&self, name: &str) -> bool {
177        self.factories.contains_key(name)
178    }
179}
180
181/// Registry for managing execution client factories.
182///
183/// Allows dynamic registration and lookup of factories by name,
184/// enabling a plugin-like architecture for different execution providers.
185#[derive(Debug, Default)]
186pub struct ExecutionClientFactoryRegistry {
187    factories: AHashMap<String, Box<dyn ExecutionClientFactory>>,
188}
189
190impl ExecutionClientFactoryRegistry {
191    /// Creates a new empty registry.
192    #[must_use]
193    pub fn new() -> Self {
194        Self {
195            factories: AHashMap::new(),
196        }
197    }
198
199    /// Registers an execution client factory.
200    ///
201    /// # Errors
202    ///
203    /// Returns an error if a factory with the same name is already registered.
204    pub fn register(
205        &mut self,
206        name: String,
207        factory: Box<dyn ExecutionClientFactory>,
208    ) -> anyhow::Result<()> {
209        if self.factories.contains_key(&name) {
210            anyhow::bail!("Execution client factory '{name}' is already registered");
211        }
212
213        self.factories.insert(name, factory);
214        Ok(())
215    }
216
217    /// Gets a registered factory by name (if found).
218    #[must_use]
219    pub fn get(&self, name: &str) -> Option<&dyn ExecutionClientFactory> {
220        self.factories.get(name).map(std::convert::AsRef::as_ref)
221    }
222
223    /// Gets a list of all registered factory names.
224    #[must_use]
225    pub fn names(&self) -> Vec<&String> {
226        self.factories.keys().collect()
227    }
228
229    /// Checks if a factory is registered.
230    #[must_use]
231    pub fn contains(&self, name: &str) -> bool {
232        self.factories.contains_key(name)
233    }
234}
235
236#[allow(dead_code)]
237#[cfg(test)]
238mod tests {
239    use std::any::Any;
240
241    use rstest::*;
242
243    use super::*;
244
245    #[derive(Debug)]
246    struct MockConfig {
247        #[allow(dead_code)]
248        value: String,
249    }
250
251    impl ClientConfig for MockConfig {
252        fn as_any(&self) -> &dyn Any {
253            self
254        }
255    }
256
257    #[derive(Debug)]
258    struct MockDataClientFactory;
259
260    impl DataClientFactory for MockDataClientFactory {
261        fn create(
262            &self,
263            _name: &str,
264            _config: &dyn ClientConfig,
265            _cache: CacheView,
266            _clock: Rc<RefCell<dyn Clock>>,
267        ) -> anyhow::Result<Box<dyn DataClient>> {
268            Err(anyhow::anyhow!("Mock factory - not implemented"))
269        }
270
271        fn name(&self) -> &'static str {
272            "mock"
273        }
274
275        fn config_type(&self) -> &'static str {
276            "MockConfig"
277        }
278    }
279
280    #[rstest]
281    fn test_data_client_factory_registry() {
282        let mut registry = DataClientFactoryRegistry::new();
283
284        assert!(registry.names().is_empty());
285        assert!(!registry.contains("mock"));
286        assert!(registry.get("mock").is_none());
287
288        let factory = Box::new(MockDataClientFactory);
289        registry.register("mock".to_string(), factory).unwrap();
290
291        assert_eq!(registry.names().len(), 1);
292        assert!(registry.contains("mock"));
293        assert!(registry.get("mock").is_some());
294
295        let factory2 = Box::new(MockDataClientFactory);
296        let result = registry.register("mock".to_string(), factory2);
297        assert!(result.is_err());
298    }
299
300    #[rstest]
301    fn test_empty_data_client_factory_registry() {
302        let registry = DataClientFactoryRegistry::new();
303
304        assert!(registry.names().is_empty());
305        assert!(!registry.contains("mock"));
306        assert!(registry.get("mock").is_none());
307    }
308
309    #[rstest]
310    fn test_empty_execution_client_factory_registry() {
311        let registry = ExecutionClientFactoryRegistry::new();
312
313        assert!(registry.names().is_empty());
314        assert!(!registry.contains("mock"));
315        assert!(registry.get("mock").is_none());
316    }
317}