nautilus_interactive_brokers/
factories.rs1use std::{any::Any, cell::RefCell, rc::Rc, sync::Arc};
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::{IB, IB_VENUE},
34 config::{InteractiveBrokersDataClientConfig, InteractiveBrokersExecutionClientConfig},
35 data::InteractiveBrokersDataClient,
36 execution::InteractiveBrokersExecutionClient,
37 providers::instruments::InteractiveBrokersInstrumentProvider,
38};
39
40impl ClientConfig for InteractiveBrokersDataClientConfig {
41 fn as_any(&self) -> &dyn Any {
42 self
43 }
44}
45
46impl ClientConfig for InteractiveBrokersExecutionClientConfig {
47 fn as_any(&self) -> &dyn Any {
48 self
49 }
50}
51
52#[derive(Debug, Clone)]
54#[cfg_attr(
55 feature = "python",
56 pyo3::pyclass(
57 module = "nautilus_trader.adapters.interactive_brokers",
58 from_py_object
59 )
60)]
61#[cfg_attr(
62 feature = "python",
63 pyo3_stub_gen::derive::gen_stub_pyclass(
64 module = "nautilus_trader.adapters.interactive_brokers"
65 )
66)]
67pub struct InteractiveBrokersDataClientFactory;
68
69impl InteractiveBrokersDataClientFactory {
70 #[must_use]
72 pub const fn new() -> Self {
73 Self
74 }
75}
76
77impl Default for InteractiveBrokersDataClientFactory {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl DataClientFactory for InteractiveBrokersDataClientFactory {
84 fn create(
85 &self,
86 name: &str,
87 config: &dyn ClientConfig,
88 cache: CacheView,
89 _clock: Rc<RefCell<dyn Clock>>,
90 ) -> anyhow::Result<Box<dyn DataClient>> {
91 let ib_config = config
92 .as_any()
93 .downcast_ref::<InteractiveBrokersDataClientConfig>()
94 .ok_or_else(|| {
95 anyhow::anyhow!(
96 "Invalid config type for InteractiveBrokersDataClientFactory. Expected InteractiveBrokersDataClientConfig, was {config:?}",
97 )
98 })?
99 .clone();
100
101 let instrument_provider = Arc::new(InteractiveBrokersInstrumentProvider::new(
102 ib_config.instrument_provider.clone(),
103 ));
104 instrument_provider.seed_from_cache(&cache.borrow());
105
106 let client = InteractiveBrokersDataClient::new(
107 ClientId::from(name),
108 ib_config,
109 instrument_provider,
110 )?;
111 Ok(Box::new(client))
112 }
113
114 fn name(&self) -> &'static str {
115 IB
116 }
117
118 fn config_type(&self) -> &'static str {
119 stringify!(InteractiveBrokersDataClientConfig)
120 }
121}
122
123#[derive(Debug, Default, Clone)]
125#[cfg_attr(
126 feature = "python",
127 pyo3::pyclass(
128 module = "nautilus_trader.adapters.interactive_brokers",
129 from_py_object
130 )
131)]
132#[cfg_attr(
133 feature = "python",
134 pyo3_stub_gen::derive::gen_stub_pyclass(
135 module = "nautilus_trader.adapters.interactive_brokers"
136 )
137)]
138pub struct InteractiveBrokersExecutionClientFactory;
139
140impl InteractiveBrokersExecutionClientFactory {
141 #[must_use]
143 pub const fn new() -> Self {
144 Self
145 }
146}
147
148impl ExecutionClientFactory for InteractiveBrokersExecutionClientFactory {
149 fn create(
150 &self,
151 trader_id: TraderId,
152 name: &str,
153 config: &dyn ClientConfig,
154 cache: CacheView,
155 _clock: Rc<RefCell<dyn Clock>>,
156 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
157 let ib_config = config
158 .as_any()
159 .downcast_ref::<InteractiveBrokersExecutionClientConfig>()
160 .ok_or_else(|| {
161 anyhow::anyhow!(
162 "Invalid config type for InteractiveBrokersExecutionClientFactory. Expected InteractiveBrokersExecutionClientConfig, was {config:?}",
163 )
164 })?
165 .clone();
166
167 let account_id = if let Some(account_id) = ib_config.account_id.as_deref() {
168 resolve_account_id(name, account_id)?
169 } else {
170 AccountId::from("IB-001")
171 };
172
173 let instrument_provider = Arc::new(InteractiveBrokersInstrumentProvider::new(
174 ib_config.instrument_provider.clone(),
175 ));
176 instrument_provider.seed_from_cache(&cache.borrow());
177
178 let core = ExecutionClientCore::new(
179 trader_id,
180 ClientId::from(name),
181 *IB_VENUE,
182 OmsType::Netting,
183 account_id,
184 AccountType::Margin,
185 None, cache,
187 );
188
189 let client = InteractiveBrokersExecutionClient::new(core, ib_config, instrument_provider)?;
190 Ok(Box::new(client))
191 }
192
193 fn name(&self) -> &'static str {
194 IB
195 }
196
197 fn config_type(&self) -> &'static str {
198 stringify!(InteractiveBrokersExecutionClientConfig)
199 }
200}
201
202fn resolve_account_id(name: &str, account_id: &str) -> anyhow::Result<AccountId> {
203 if account_id.contains('-') {
204 return AccountId::new_checked(account_id)
205 .map_err(|e| anyhow::anyhow!("Invalid Interactive Brokers account_id: {e}"));
206 }
207
208 let issuer = if name.is_empty() { IB } else { name };
209 AccountId::new_checked(format!("{issuer}-{account_id}"))
210 .map_err(|e| anyhow::anyhow!("Invalid Interactive Brokers account_id: {e}"))
211}
212
213#[cfg(test)]
214mod tests {
215 use std::{cell::RefCell, rc::Rc};
216
217 use nautilus_common::{
218 cache::Cache,
219 clock::VirtualClock,
220 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
221 live::runner::replace_data_event_sender,
222 };
223 use rstest::rstest;
224
225 use super::*;
226
227 #[rstest]
228 fn test_interactive_brokers_data_client_factory_creation() {
229 let factory = InteractiveBrokersDataClientFactory::new();
230 assert_eq!(factory.name(), IB);
231 assert_eq!(factory.config_type(), "InteractiveBrokersDataClientConfig");
232 }
233
234 #[rstest]
235 fn test_interactive_brokers_data_client_factory_default() {
236 let factory = InteractiveBrokersDataClientFactory;
237 assert_eq!(factory.name(), IB);
238 }
239
240 #[rstest]
241 fn test_interactive_brokers_exec_client_factory_creation() {
242 let factory = InteractiveBrokersExecutionClientFactory::new();
243 assert_eq!(factory.name(), IB);
244 assert_eq!(
245 factory.config_type(),
246 "InteractiveBrokersExecutionClientConfig"
247 );
248 }
249
250 #[rstest]
251 fn test_interactive_brokers_configs_implement_client_config() {
252 let data_config = InteractiveBrokersDataClientConfig::default();
253 let exec_config = InteractiveBrokersExecutionClientConfig::default();
254
255 let boxed_data_config: Box<dyn ClientConfig> = Box::new(data_config);
256 let boxed_exec_config: Box<dyn ClientConfig> = Box::new(exec_config);
257
258 assert!(
259 boxed_data_config
260 .as_any()
261 .downcast_ref::<InteractiveBrokersDataClientConfig>()
262 .is_some()
263 );
264 assert!(
265 boxed_exec_config
266 .as_any()
267 .downcast_ref::<InteractiveBrokersExecutionClientConfig>()
268 .is_some()
269 );
270 }
271
272 #[rstest]
273 fn test_interactive_brokers_data_client_factory_creates_client() {
274 let factory = InteractiveBrokersDataClientFactory::new();
275 let config = InteractiveBrokersDataClientConfig::default();
276 let cache = Rc::new(RefCell::new(Cache::default()));
277 let clock = Rc::new(RefCell::new(VirtualClock::new()));
278 let (data_tx, _data_rx) = tokio::sync::mpsc::unbounded_channel();
279 replace_data_event_sender(data_tx);
280
281 let result = factory.create("IB-TEST", &config, cache.into(), clock);
282
283 assert!(result.is_ok());
284 let client = result.unwrap();
285 assert_eq!(client.client_id(), ClientId::from("IB-TEST"));
286 }
287
288 #[rstest]
289 fn test_interactive_brokers_exec_client_factory_creates_client() {
290 let factory = InteractiveBrokersExecutionClientFactory::new();
291 let config = InteractiveBrokersExecutionClientConfig::default();
292 let cache = Rc::new(RefCell::new(Cache::default()));
293
294 let result = factory.create(
295 TraderId::from("TRADER-001"),
296 "IB-TEST",
297 &config,
298 cache.into(),
299 Rc::new(RefCell::new(VirtualClock::new())),
300 );
301
302 assert!(result.is_ok());
303 let client = result.unwrap();
304 assert_eq!(client.client_id(), ClientId::from("IB-TEST"));
305 assert_eq!(client.account_id(), AccountId::from("IB-001"));
306 }
307
308 #[rstest]
309 fn test_interactive_brokers_exec_client_factory_uses_config_account_id() {
310 let factory = InteractiveBrokersExecutionClientFactory::new();
311
312 let config = InteractiveBrokersExecutionClientConfig {
313 account_id: Some(String::from("U7654321")),
314 ..Default::default()
315 };
316
317 let cache = Rc::new(RefCell::new(Cache::default()));
318
319 let result = factory.create(
320 TraderId::from("TRADER-001"),
321 "IB-CUSTOM",
322 &config,
323 cache.into(),
324 Rc::new(RefCell::new(VirtualClock::new())),
325 );
326
327 assert!(result.is_ok());
328 let client = result.unwrap();
329 assert_eq!(client.account_id(), AccountId::from("IB-CUSTOM-U7654321"));
330 }
331}