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