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