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::{ClientId, TraderId},
30};
31
32use crate::{
33 common::consts::{DERIVE, DERIVE_VENUE},
34 config::{DeriveDataClientConfig, DeriveExecutionClientConfig},
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 DeriveExecutionClientConfig {
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.adapters.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(Debug, Clone)]
109#[cfg_attr(
110 feature = "python",
111 pyo3::pyclass(module = "nautilus_trader.adapters.derive", from_py_object)
112)]
113#[cfg_attr(
114 feature = "python",
115 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
116)]
117pub struct DeriveExecutionClientFactory;
118
119impl DeriveExecutionClientFactory {
120 #[must_use]
121 pub const fn new() -> Self {
122 Self
123 }
124}
125
126impl Default for DeriveExecutionClientFactory {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132impl ExecutionClientFactory for DeriveExecutionClientFactory {
133 fn create(
134 &self,
135 trader_id: TraderId,
136 name: &str,
137 config: &dyn ClientConfig,
138 cache: CacheView,
139 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
140 let derive_config = config
141 .as_any()
142 .downcast_ref::<DeriveExecutionClientConfig>()
143 .ok_or_else(|| {
144 anyhow::anyhow!(
145 "Invalid config type for DeriveExecutionClientFactory. Expected DeriveExecutionClientConfig, was {config:?}",
146 )
147 })?
148 .clone();
149
150 let oms_type = OmsType::Netting;
152 let account_type = AccountType::Margin;
153
154 let core = ExecutionClientCore::new(
155 trader_id,
156 ClientId::from(name),
157 *DERIVE_VENUE,
158 oms_type,
159 derive_config.account_id,
160 account_type,
161 None,
162 cache,
163 );
164
165 let client = DeriveExecutionClient::new(core, derive_config)?;
166 Ok(Box::new(client))
167 }
168
169 fn name(&self) -> &'static str {
170 DERIVE
171 }
172
173 fn config_type(&self) -> &'static str {
174 stringify!(DeriveExecutionClientConfig)
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use nautilus_common::{
181 cache::Cache, clock::TestClock, live::runner::replace_data_event_sender,
182 messages::DataEvent,
183 };
184 use rstest::rstest;
185
186 use super::*;
187
188 #[derive(Debug)]
189 struct WrongConfig;
190
191 impl ClientConfig for WrongConfig {
192 fn as_any(&self) -> &dyn Any {
193 self
194 }
195 }
196
197 #[rstest]
198 fn test_data_client_factory_metadata() {
199 let factory = DeriveDataClientFactory::new();
200
201 assert_eq!(factory.name(), DERIVE);
202 assert_eq!(factory.config_type(), "DeriveDataClientConfig");
203 }
204
205 #[rstest]
206 fn test_data_client_factory_creates_client() {
207 let factory = DeriveDataClientFactory::new();
208 let cache = Rc::new(RefCell::new(Cache::default()));
209 let clock = Rc::new(RefCell::new(TestClock::new()));
210 let config = DeriveDataClientConfig::default();
211 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<DataEvent>();
212 replace_data_event_sender(tx);
213
214 let client = factory
215 .create(DERIVE, &config, cache.into(), clock)
216 .expect("factory creates data client");
217
218 assert_eq!(client.client_id(), ClientId::from(DERIVE));
219 assert_eq!(client.venue(), Some(*DERIVE_VENUE));
220 }
221
222 #[rstest]
223 fn test_data_client_factory_rejects_wrong_config_type() {
224 let factory = DeriveDataClientFactory::new();
225 let cache = Rc::new(RefCell::new(Cache::default()));
226 let clock = Rc::new(RefCell::new(TestClock::new()));
227 let wrong_config = WrongConfig;
228
229 let result = factory.create(DERIVE, &wrong_config, cache.into(), clock);
230
231 assert!(result.is_err());
232 assert!(
233 result
234 .err()
235 .unwrap()
236 .to_string()
237 .contains("Invalid config type")
238 );
239 }
240
241 #[rstest]
242 fn test_exec_client_factory_metadata() {
243 let factory = DeriveExecutionClientFactory::new();
244
245 assert_eq!(factory.name(), DERIVE);
246 assert_eq!(factory.config_type(), "DeriveExecutionClientConfig");
247 }
248
249 #[rstest]
250 fn test_exec_client_factory_rejects_wrong_config_type() {
251 let factory = DeriveExecutionClientFactory::new();
252 let cache = Rc::new(RefCell::new(Cache::default()));
253 let wrong_config = DeriveDataClientConfig::default();
254
255 let result = factory.create(
256 TraderId::from("TRADER-001"),
257 DERIVE,
258 &wrong_config,
259 cache.into(),
260 );
261
262 assert!(result.is_err());
263 assert!(
264 result
265 .err()
266 .unwrap()
267 .to_string()
268 .contains("Invalid config type")
269 );
270 }
271
272 #[rstest]
273 fn test_exec_client_config_implements_client_config() {
274 let boxed: Box<dyn ClientConfig> = Box::new(DeriveExecutionClientConfig::default());
275 assert!(
276 boxed
277 .as_any()
278 .downcast_ref::<DeriveExecutionClientConfig>()
279 .is_some()
280 );
281 }
282}