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}