nautilus_interactive_brokers/python/
historical.rs1use std::sync::Arc;
19
20use chrono::{DateTime, Utc};
21use ibapi::contracts::Contract;
22use nautilus_common::live::get_runtime;
23use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
24use nautilus_model::{
25 data::{Bar, Data},
26 identifiers::InstrumentId,
27 instruments::any::InstrumentAny,
28 python::{data::data_to_pycapsule, instruments::instrument_any_to_pyobject},
29};
30use pyo3::{prelude::*, types::PyList};
31
32use crate::{
33 historical::HistoricalInteractiveBrokersClient, python::conversion::py_list_to_contracts,
34};
35
36#[pymethods]
37impl HistoricalInteractiveBrokersClient {
38 #[new]
39 #[allow(clippy::needless_pass_by_value)]
40 fn py_new(
41 instrument_provider: crate::providers::instruments::InteractiveBrokersInstrumentProvider,
42 config: crate::config::InteractiveBrokersDataClientConfig,
43 ) -> PyResult<Self> {
44 let shared_client = get_runtime()
45 .block_on(crate::common::shared_client::get_or_connect(
46 &config.host,
47 config.port,
48 config.client_id,
49 config.connection_timeout,
50 ))
51 .map_err(to_pyruntime_err)?;
52
53 Ok(Self::new(
54 Arc::clone(shared_client.as_arc()),
55 Arc::new(instrument_provider),
56 ))
57 }
58
59 fn __repr__(&self) -> String {
60 format!("{self:?}")
61 }
62
63 #[pyo3(signature = (bar_specifications, end_date_time, start_date_time=None, duration=None, contracts=None, instrument_ids=None, use_rth=true, timeout=60))]
76 #[pyo3(name = "request_bars")]
77 #[allow(clippy::too_many_arguments)]
78 #[allow(clippy::needless_pass_by_value)]
79 fn py_request_bars<'py>(
80 &self,
81 py: Python<'py>,
82 bar_specifications: Vec<String>,
83 end_date_time: DateTime<Utc>,
84 start_date_time: Option<DateTime<Utc>>,
85 duration: Option<String>,
86 contracts: Option<Py<PyList>>,
87 instrument_ids: Option<Vec<InstrumentId>>,
88 use_rth: bool,
89 timeout: u64,
90 ) -> PyResult<Bound<'py, PyAny>> {
91 let client = self.clone();
92 let bar_specs = bar_specifications;
93 let duration_str = duration;
94
95 let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
97 let py_contracts_bound = py_contracts.bind(py);
98 match py_list_to_contracts(py_contracts_bound) {
99 Ok(contracts) => Some(contracts),
100 Err(e) => {
101 return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
102 }
103 }
104 } else {
105 None
106 };
107
108 pyo3_async_runtimes::tokio::future_into_py(py, async move {
109 let bar_specs_refs: Vec<&str> = bar_specs.iter().map(|s| s.as_str()).collect();
111 let bars: Vec<Bar> = client
112 .request_bars(
113 bar_specs_refs,
114 end_date_time,
115 start_date_time,
116 duration_str.as_deref(),
117 contracts_vec,
118 instrument_ids,
119 use_rth,
120 timeout,
121 )
122 .await
123 .map_err(to_pyruntime_err)?;
124 Ok(bars)
126 })
127 }
128
129 #[pyo3(signature = (tick_type, start_date_time, end_date_time, contracts=None, instrument_ids=None, use_rth=true, timeout=60))]
141 #[pyo3(name = "request_ticks")]
142 #[allow(clippy::too_many_arguments)]
143 #[allow(clippy::needless_pass_by_value)]
144 fn py_request_ticks<'py>(
145 &self,
146 py: Python<'py>,
147 tick_type: String,
148 start_date_time: DateTime<Utc>,
149 end_date_time: DateTime<Utc>,
150 contracts: Option<Py<PyList>>,
151 instrument_ids: Option<Vec<InstrumentId>>,
152 use_rth: bool,
153 timeout: u64,
154 ) -> PyResult<Bound<'py, PyAny>> {
155 let client = self.clone();
156
157 let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
159 let py_contracts_bound = py_contracts.bind(py);
160 match py_list_to_contracts(py_contracts_bound) {
161 Ok(contracts) => Some(contracts),
162 Err(e) => {
163 return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
164 }
165 }
166 } else {
167 None
168 };
169
170 pyo3_async_runtimes::tokio::future_into_py(py, async move {
171 let data_vec: Vec<Data> = client
172 .request_ticks(
173 &tick_type,
174 start_date_time,
175 end_date_time,
176 contracts_vec,
177 instrument_ids,
178 use_rth,
179 timeout,
180 )
181 .await
182 .map_err(to_pyruntime_err)?;
183 Python::attach(|py| -> PyResult<Py<PyList>> {
185 let py_list = PyList::empty(py);
186 for data in data_vec {
187 let py_capsule = data_to_pycapsule(py, data);
188 py_list.append(py_capsule)?;
189 }
190 Ok(py_list.into())
191 })
192 })
193 }
194
195 #[pyo3(signature = (instrument_ids=None, contracts=None))]
202 #[pyo3(name = "request_instruments")]
203 #[allow(clippy::needless_pass_by_value)]
204 fn py_request_instruments<'py>(
205 &self,
206 py: Python<'py>,
207 instrument_ids: Option<Vec<InstrumentId>>,
208 contracts: Option<Py<PyList>>,
209 ) -> PyResult<Bound<'py, PyAny>> {
210 let client = self.clone();
211
212 let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
214 let py_contracts_bound = py_contracts.bind(py);
215 match py_list_to_contracts(py_contracts_bound) {
216 Ok(contracts) => Some(contracts),
217 Err(e) => {
218 return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
219 }
220 }
221 } else {
222 None
223 };
224
225 pyo3_async_runtimes::tokio::future_into_py(py, async move {
226 let instruments: Vec<InstrumentAny> = client
227 .request_instruments(instrument_ids, contracts_vec)
228 .await
229 .map_err(to_pyruntime_err)?;
230 Python::attach(|py| -> PyResult<Py<PyList>> {
232 let py_list = PyList::empty(py);
233
234 for instrument in instruments {
235 let py_obj =
236 instrument_any_to_pyobject(py, instrument).map_err(to_pyruntime_err)?;
237 py_list.append(py_obj)?;
238 }
239 Ok(py_list.into())
240 })
241 })
242 }
243}