nautilus_derive/
factories.rs1use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21 cache::CacheView,
22 clients::{DataClient, ExecutionClient},
23 clock::Clock,
24 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28 enums::{AccountType, OmsType},
29 identifiers::{AccountId, ClientId, TraderId},
30};
31
32use crate::{
33 common::consts::{DERIVE, DERIVE_VENUE},
34 config::{DeriveDataClientConfig, DeriveExecClientConfig},
35 data::DeriveDataClient,
36 execution::DeriveExecutionClient,
37};
38
39impl ClientConfig for DeriveDataClientConfig {
40 fn as_any(&self) -> &dyn Any {
41 self
42 }
43}
44
45impl ClientConfig for DeriveExecClientConfig {
46 fn as_any(&self) -> &dyn Any {
47 self
48 }
49}
50
51#[derive(Debug, Clone)]
53#[cfg_attr(
54 feature = "python",
55 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.derive", from_py_object)
56)]
57#[cfg_attr(
58 feature = "python",
59 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
60)]
61pub struct DeriveDataClientFactory;
62
63impl DeriveDataClientFactory {
64 #[must_use]
65 pub const fn new() -> Self {
66 Self
67 }
68}
69
70impl Default for DeriveDataClientFactory {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76impl DataClientFactory for DeriveDataClientFactory {
77 fn create(
78 &self,
79 name: &str,
80 config: &dyn ClientConfig,
81 _cache: CacheView,
82 _clock: Rc<RefCell<dyn Clock>>,
83 ) -> anyhow::Result<Box<dyn DataClient>> {
84 let derive_config = config
85 .as_any()
86 .downcast_ref::<DeriveDataClientConfig>()
87 .ok_or_else(|| {
88 anyhow::anyhow!(
89 "Invalid config type for DeriveDataClientFactory. Expected DeriveDataClientConfig, was {config:?}",
90 )
91 })?
92 .clone();
93
94 let client = DeriveDataClient::new(ClientId::from(name), derive_config)?;
95 Ok(Box::new(client))
96 }
97
98 fn name(&self) -> &'static str {
99 DERIVE
100 }
101
102 fn config_type(&self) -> &'static str {
103 stringify!(DeriveDataClientConfig)
104 }
105}
106
107#[derive(Clone, Debug)]
112#[cfg_attr(
113 feature = "python",
114 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.derive", from_py_object)
115)]
116#[cfg_attr(
117 feature = "python",
118 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
119)]
120pub struct DeriveExecFactoryConfig {
121 pub trader_id: TraderId,
123 pub account_id: AccountId,
125 pub config: DeriveExecClientConfig,
127}
128
129impl ClientConfig for DeriveExecFactoryConfig {
130 fn as_any(&self) -> &dyn Any {
131 self
132 }
133}
134
135#[derive(Debug, Clone)]
137#[cfg_attr(
138 feature = "python",
139 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.derive", from_py_object)
140)]
141#[cfg_attr(
142 feature = "python",
143 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
144)]
145pub struct DeriveExecutionClientFactory;
146
147impl DeriveExecutionClientFactory {
148 #[must_use]
149 pub const fn new() -> Self {
150 Self
151 }
152}
153
154impl Default for DeriveExecutionClientFactory {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160impl ExecutionClientFactory for DeriveExecutionClientFactory {
161 fn create(
162 &self,
163 name: &str,
164 config: &dyn ClientConfig,
165 cache: CacheView,
166 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
167 let factory_config = config
168 .as_any()
169 .downcast_ref::<DeriveExecFactoryConfig>()
170 .ok_or_else(|| {
171 anyhow::anyhow!(
172 "Invalid config type for DeriveExecutionClientFactory. Expected DeriveExecFactoryConfig, was {config:?}",
173 )
174 })?
175 .clone();
176
177 let oms_type = OmsType::Netting;
179 let account_type = AccountType::Margin;
180
181 let core = ExecutionClientCore::new(
182 factory_config.trader_id,
183 ClientId::from(name),
184 *DERIVE_VENUE,
185 oms_type,
186 factory_config.account_id,
187 account_type,
188 None,
189 cache,
190 );
191
192 let client = DeriveExecutionClient::new(core, factory_config.config)?;
193 Ok(Box::new(client))
194 }
195
196 fn name(&self) -> &'static str {
197 DERIVE
198 }
199
200 fn config_type(&self) -> &'static str {
201 stringify!(DeriveExecFactoryConfig)
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use nautilus_common::{
208 cache::Cache, clock::TestClock, live::runner::replace_data_event_sender,
209 messages::DataEvent,
210 };
211 use rstest::rstest;
212
213 use super::*;
214
215 #[derive(Debug)]
216 struct WrongConfig;
217
218 impl ClientConfig for WrongConfig {
219 fn as_any(&self) -> &dyn Any {
220 self
221 }
222 }
223
224 #[rstest]
225 fn test_data_client_factory_metadata() {
226 let factory = DeriveDataClientFactory::new();
227
228 assert_eq!(factory.name(), DERIVE);
229 assert_eq!(factory.config_type(), "DeriveDataClientConfig");
230 }
231
232 #[rstest]
233 fn test_data_client_factory_creates_client() {
234 let factory = DeriveDataClientFactory::new();
235 let cache = Rc::new(RefCell::new(Cache::default()));
236 let clock = Rc::new(RefCell::new(TestClock::new()));
237 let config = DeriveDataClientConfig::default();
238 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<DataEvent>();
239 replace_data_event_sender(tx);
240
241 let client = factory
242 .create(DERIVE, &config, cache.into(), clock)
243 .expect("factory creates data client");
244
245 assert_eq!(client.client_id(), ClientId::from(DERIVE));
246 assert_eq!(client.venue(), Some(*DERIVE_VENUE));
247 }
248
249 #[rstest]
250 fn test_data_client_factory_rejects_wrong_config_type() {
251 let factory = DeriveDataClientFactory::new();
252 let cache = Rc::new(RefCell::new(Cache::default()));
253 let clock = Rc::new(RefCell::new(TestClock::new()));
254 let wrong_config = WrongConfig;
255
256 let result = factory.create(DERIVE, &wrong_config, cache.into(), clock);
257
258 assert!(result.is_err());
259 assert!(
260 result
261 .err()
262 .unwrap()
263 .to_string()
264 .contains("Invalid config type")
265 );
266 }
267
268 #[rstest]
269 fn test_exec_client_factory_metadata() {
270 let factory = DeriveExecutionClientFactory::new();
271
272 assert_eq!(factory.name(), DERIVE);
273 assert_eq!(factory.config_type(), "DeriveExecFactoryConfig");
274 }
275
276 #[rstest]
277 fn test_exec_client_factory_rejects_wrong_config_type() {
278 let factory = DeriveExecutionClientFactory::new();
279 let cache = Rc::new(RefCell::new(Cache::default()));
280 let wrong_config = DeriveDataClientConfig::default();
281
282 let result = factory.create(DERIVE, &wrong_config, cache.into());
283
284 assert!(result.is_err());
285 assert!(
286 result
287 .err()
288 .unwrap()
289 .to_string()
290 .contains("Invalid config type")
291 );
292 }
293
294 #[rstest]
295 fn test_exec_factory_config_implements_client_config() {
296 let factory_config = DeriveExecFactoryConfig {
297 trader_id: TraderId::from("TRADER-001"),
298 account_id: AccountId::from("DERIVE-001"),
299 config: DeriveExecClientConfig::default(),
300 };
301
302 let boxed: Box<dyn ClientConfig> = Box::new(factory_config);
303 assert!(
304 boxed
305 .as_any()
306 .downcast_ref::<DeriveExecFactoryConfig>()
307 .is_some()
308 );
309 }
310}