1use std::collections::HashMap;
16
17use nautilus_common::{clients::ExecutionClient, live::get_runtime};
18use nautilus_core::python::to_pyruntime_err;
19use nautilus_model::{
20 enums::OrderSide,
21 identifiers::{
22 ClientId, ClientOrderId, ExecAlgorithmId, InstrumentId, PositionId, StrategyId, TraderId,
23 VenueOrderId,
24 },
25 python::orders::pyobject_to_order_any,
26 types::{Price, Quantity},
27};
28use pyo3::prelude::*;
29
30use crate::execution::InteractiveBrokersExecutionClient;
31
32#[cfg(feature = "python")]
33#[pymethods]
34impl InteractiveBrokersExecutionClient {
35 #[new]
36 #[pyo3(signature = (_msgbus, _cache, _clock, instrument_provider, config))]
37 fn py_new(
38 _msgbus: Py<PyAny>,
39 _cache: Py<PyAny>,
40 _clock: Py<PyAny>,
41 instrument_provider: crate::providers::instruments::InteractiveBrokersInstrumentProvider,
42 config: crate::config::InteractiveBrokersExecClientConfig,
43 ) -> PyResult<Self> {
44 Self::new_for_python(config, instrument_provider).map_err(to_pyruntime_err)
45 }
46
47 #[pyo3(name = "set_event_callback")]
48 fn py_set_event_callback(&self, callback: Py<PyAny>) {
49 self.register_python_event_callback(callback);
50 }
51
52 #[getter]
54 pub fn client_id(&self) -> ClientId {
55 ExecutionClient::client_id(self)
56 }
57
58 #[getter]
60 pub fn is_connected(&self) -> bool {
61 ExecutionClient::is_connected(self)
62 }
63
64 #[getter]
66 pub fn is_disconnected(&self) -> bool {
67 !ExecutionClient::is_connected(self)
68 }
69
70 #[pyo3(name = "connect")]
71 fn py_connect(&mut self) -> PyResult<()> {
72 get_runtime()
73 .block_on(ExecutionClient::connect(self))
74 .map_err(to_pyruntime_err)
75 }
76
77 #[pyo3(name = "disconnect")]
78 fn py_disconnect(&mut self) -> PyResult<()> {
79 get_runtime()
80 .block_on(ExecutionClient::disconnect(self))
81 .map_err(to_pyruntime_err)
82 }
83
84 #[pyo3(name = "submit_order")]
100 fn py_submit_order(
101 &self,
102 py: Python,
103 trader_id: TraderId,
104 order: Py<PyAny>,
105 instrument_id: InstrumentId,
106 strategy_id: StrategyId,
107 exec_algorithm_id: Option<ExecAlgorithmId>,
108 position_id: Option<PositionId>,
109 params: Option<HashMap<String, String>>,
110 ) -> PyResult<()> {
111 let order_any = pyobject_to_order_any(py, order)?;
112 self.submit_order_for_python(
113 trader_id,
114 order_any,
115 instrument_id,
116 strategy_id,
117 exec_algorithm_id,
118 position_id,
119 params,
120 )
121 .map_err(to_pyruntime_err)
122 }
123
124 #[pyo3(name = "submit_order_list")]
139 fn py_submit_order_list(
140 &self,
141 py: Python,
142 trader_id: TraderId,
143 strategy_id: StrategyId,
144 orders: Vec<Py<PyAny>>,
145 exec_algorithm_id: Option<ExecAlgorithmId>,
146 position_id: Option<PositionId>,
147 params: Option<HashMap<String, String>>,
148 ) -> PyResult<()> {
149 let mut order_anys = Vec::new();
150 for order_py in orders {
151 order_anys.push(pyobject_to_order_any(py, order_py)?);
152 }
153 self.submit_order_list_for_python(
154 trader_id,
155 strategy_id,
156 order_anys,
157 exec_algorithm_id,
158 position_id,
159 params,
160 )
161 .map_err(to_pyruntime_err)
162 }
163
164 #[pyo3(name = "modify_order")]
179 fn py_modify_order(
180 &self,
181 trader_id: TraderId,
182 strategy_id: StrategyId,
183 client_order_id: ClientOrderId,
184 venue_order_id: Option<VenueOrderId>,
185 instrument_id: InstrumentId,
186 quantity: Option<Quantity>,
187 price: Option<Price>,
188 trigger_price: Option<Price>,
189 params: Option<HashMap<String, String>>,
190 ) -> PyResult<()> {
191 self.modify_order_for_python(
192 trader_id,
193 strategy_id,
194 client_order_id,
195 venue_order_id,
196 instrument_id,
197 quantity,
198 price,
199 trigger_price,
200 params,
201 )
202 .map_err(to_pyruntime_err)
203 }
204
205 #[pyo3(name = "cancel_order")]
217 fn py_cancel_order(
218 &self,
219 trader_id: TraderId,
220 strategy_id: StrategyId,
221 client_order_id: ClientOrderId,
222 venue_order_id: Option<VenueOrderId>,
223 instrument_id: InstrumentId,
224 params: Option<HashMap<String, String>>,
225 ) -> PyResult<()> {
226 self.cancel_order_for_python(
227 trader_id,
228 strategy_id,
229 client_order_id,
230 venue_order_id,
231 instrument_id,
232 params,
233 )
234 .map_err(to_pyruntime_err)
235 }
236
237 #[pyo3(name = "cancel_all_orders")]
247 fn py_cancel_all_orders(
248 &self,
249 trader_id: TraderId,
250 strategy_id: StrategyId,
251 instrument_id: InstrumentId,
252 order_side: OrderSide,
253 params: Option<HashMap<String, String>>,
254 ) -> PyResult<()> {
255 self.cancel_all_orders_for_python(trader_id, strategy_id, instrument_id, order_side, params)
256 .map_err(to_pyruntime_err)
257 }
258
259 #[pyo3(name = "batch_cancel_orders")]
269 fn py_batch_cancel_orders(
270 &self,
271 trader_id: TraderId,
272 strategy_id: StrategyId,
273 instrument_id: InstrumentId,
274 client_order_ids: Vec<ClientOrderId>,
275 params: Option<HashMap<String, String>>,
276 ) -> PyResult<()> {
277 self.batch_cancel_orders_for_python(
278 trader_id,
279 strategy_id,
280 instrument_id,
281 client_order_ids,
282 params,
283 )
284 .map_err(to_pyruntime_err)
285 }
286
287 #[pyo3(name = "query_account")]
293 fn py_query_account(&self, trader_id: TraderId) -> PyResult<()> {
294 self.query_account_for_python(trader_id)
295 .map_err(to_pyruntime_err)
296 }
297
298 #[pyo3(name = "query_order")]
312 fn py_query_order(
313 &self,
314 trader_id: TraderId,
315 strategy_id: StrategyId,
316 instrument_id: InstrumentId,
317 client_order_id: ClientOrderId,
318 venue_order_id: Option<VenueOrderId>,
319 ) -> PyResult<()> {
320 self.query_order_for_python(
321 trader_id,
322 strategy_id,
323 instrument_id,
324 client_order_id,
325 venue_order_id,
326 )
327 .map_err(to_pyruntime_err)
328 }
329
330 #[pyo3(name = "generate_mass_status")]
344 fn py_generate_mass_status<'py>(
345 &self,
346 py: Python<'py>,
347 lookback_mins: Option<u64>,
348 ) -> PyResult<Bound<'py, PyAny>> {
349 let client = self;
350 let fut = async move { client.generate_mass_status(lookback_mins).await };
351 let rt = get_runtime();
352
353 match rt.block_on(fut) {
354 Ok(Some(mass_status)) => {
355 let py_mass_status = Py::new(py, mass_status).map_err(to_pyruntime_err)?;
356 Ok(py_mass_status.bind(py).as_any().to_owned())
357 }
358 Ok(None) => Ok(py.None().bind(py).as_any().to_owned()),
359 Err(e) => Err(to_pyruntime_err(format!(
360 "Failed to generate mass status: {e}"
361 ))),
362 }
363 }
364
365 #[pyo3(name = "generate_order_status_report")]
381 fn py_generate_order_status_report<'py>(
382 &self,
383 py: Python<'py>,
384 instrument_id: Option<InstrumentId>,
385 client_order_id: Option<ClientOrderId>,
386 venue_order_id: Option<VenueOrderId>,
387 ) -> PyResult<Bound<'py, PyAny>> {
388 let client = self;
389 let fut = async move {
390 client
391 .generate_order_status_report_for_python(
392 instrument_id,
393 client_order_id,
394 venue_order_id,
395 )
396 .await
397 };
398 let rt = get_runtime();
399
400 match rt.block_on(fut) {
401 Ok(Some(report)) => {
402 let py_report = Py::new(py, report).map_err(to_pyruntime_err)?;
403 Ok(py_report.bind(py).as_any().to_owned())
404 }
405 Ok(None) => Ok(py.None().bind(py).as_any().to_owned()),
406 Err(e) => Err(to_pyruntime_err(format!(
407 "Failed to generate order_status_report: {e}"
408 ))),
409 }
410 }
411
412 #[pyo3(name = "generate_order_status_reports")]
429 fn py_generate_order_status_reports<'py>(
430 &self,
431 py: Python<'py>,
432 open_only: bool,
433 instrument_id: Option<InstrumentId>,
434 start: Option<u64>,
435 end: Option<u64>,
436 ) -> PyResult<Bound<'py, PyAny>> {
437 let client = self;
438 let fut = async move {
439 client
440 .generate_order_status_reports_for_python(open_only, instrument_id, start, end)
441 .await
442 };
443 let rt = get_runtime();
444
445 match rt.block_on(fut) {
446 Ok(reports) => {
447 let py_reports: Result<Vec<_>, _> =
448 reports.into_iter().map(|r| Py::new(py, r)).collect();
449 let py_list = pyo3::types::PyList::new(py, py_reports.map_err(to_pyruntime_err)?)
450 .map_err(to_pyruntime_err)?;
451 Ok(py_list.as_any().to_owned())
452 }
453 Err(e) => Err(to_pyruntime_err(format!(
454 "Failed to generate order status reports: {e}"
455 ))),
456 }
457 }
458
459 #[pyo3(name = "generate_fill_reports")]
476 fn py_generate_fill_reports<'py>(
477 &self,
478 py: Python<'py>,
479 instrument_id: Option<InstrumentId>,
480 venue_order_id: Option<VenueOrderId>,
481 start: Option<u64>,
482 end: Option<u64>,
483 ) -> PyResult<Bound<'py, PyAny>> {
484 let client = self;
485 let fut = async move {
486 client
487 .generate_fill_reports_for_python(instrument_id, venue_order_id, start, end)
488 .await
489 };
490 let rt = get_runtime();
491
492 match rt.block_on(fut) {
493 Ok(reports) => {
494 let py_reports: Result<Vec<_>, _> =
496 reports.into_iter().map(|r| Py::new(py, r)).collect();
497 let py_list = pyo3::types::PyList::new(py, py_reports.map_err(to_pyruntime_err)?)
498 .map_err(to_pyruntime_err)?;
499 Ok(py_list.as_any().to_owned())
500 }
501 Err(e) => Err(to_pyruntime_err(format!(
502 "Failed to generate fill reports: {e}"
503 ))),
504 }
505 }
506
507 #[pyo3(name = "generate_position_status_reports")]
523 fn py_generate_position_status_reports<'py>(
524 &self,
525 py: Python<'py>,
526 instrument_id: Option<InstrumentId>,
527 start: Option<u64>,
528 end: Option<u64>,
529 ) -> PyResult<Bound<'py, PyAny>> {
530 let client = self;
531 let fut = async move {
532 client
533 .generate_position_status_reports_for_python(instrument_id, start, end)
534 .await
535 };
536 let rt = get_runtime();
537
538 match rt.block_on(fut) {
539 Ok(reports) => {
540 let py_reports: Result<Vec<_>, _> =
542 reports.into_iter().map(|r| Py::new(py, r)).collect();
543 let py_list = pyo3::types::PyList::new(py, py_reports.map_err(to_pyruntime_err)?)
544 .map_err(to_pyruntime_err)?;
545 Ok(py_list.as_any().to_owned())
546 }
547 Err(e) => Err(to_pyruntime_err(format!(
548 "Failed to generate position status reports: {e}"
549 ))),
550 }
551 }
552}