nautilus_interactive_brokers/python/
historical.rs1use ibapi::contracts::Contract;
19use jiff::Timestamp;
20use nautilus_common::live::block_on_nautilus_with;
21use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
22use nautilus_model::{
23 data::{Bar, Data},
24 identifiers::InstrumentId,
25 instruments::any::InstrumentAny,
26 python::{data::data_to_pyobject, instruments::instrument_any_to_pyobject},
27};
28use pyo3::{prelude::*, types::PyList};
29
30use crate::{
31 common::enums::IbHistoricalTickType, config::InteractiveBrokersDataClientConfig,
32 historical::HistoricalInteractiveBrokersClient,
33 providers::instruments::InteractiveBrokersInstrumentProvider,
34 python::conversion::py_list_to_contracts,
35};
36
37#[pymethods]
38#[pyo3_stub_gen::derive::gen_stub_pymethods]
39impl HistoricalInteractiveBrokersClient {
40 #[new]
41 #[allow(clippy::needless_pass_by_value)]
42 fn py_new(
43 instrument_provider: InteractiveBrokersInstrumentProvider,
44 config: InteractiveBrokersDataClientConfig,
45 ) -> PyResult<Self> {
46 block_on_nautilus_with(move || Self::connect_with_provider(instrument_provider, config))
47 .map_err(to_pyruntime_err)
48 }
49
50 fn __repr__(&self) -> String {
51 format!("{self:?}")
52 }
53
54 #[pyo3(signature = (bar_specifications, end_date_time, start_date_time=None, duration=None, contracts=None, instrument_ids=None, use_rth=true, timeout=60))]
65 #[pyo3(name = "request_bars")]
66 #[allow(clippy::too_many_arguments)]
67 #[allow(clippy::needless_pass_by_value)]
68 fn py_request_bars<'py>(
69 &self,
70 py: Python<'py>,
71 bar_specifications: Vec<String>,
72 end_date_time: Timestamp,
73 start_date_time: Option<Timestamp>,
74 duration: Option<String>,
75 contracts: Option<Py<PyList>>,
76 instrument_ids: Option<Vec<InstrumentId>>,
77 use_rth: bool,
78 timeout: u64,
79 ) -> PyResult<Bound<'py, PyAny>> {
80 let client = self.clone();
81 let bar_specs = bar_specifications;
82 let duration_str = duration;
83
84 let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
86 let py_contracts_bound = py_contracts.bind(py);
87 match py_list_to_contracts(py_contracts_bound) {
88 Ok(contracts) => Some(contracts),
89 Err(e) => {
90 return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
91 }
92 }
93 } else {
94 None
95 };
96
97 pyo3_async_runtimes::tokio::future_into_py(py, async move {
98 let bar_specs_refs: Vec<&str> = bar_specs.iter().map(|s| s.as_str()).collect();
100 let bars: Vec<Bar> = client
101 .request_bars(
102 bar_specs_refs,
103 end_date_time,
104 start_date_time,
105 duration_str.as_deref(),
106 contracts_vec,
107 instrument_ids,
108 use_rth,
109 timeout,
110 )
111 .await
112 .map_err(to_pyruntime_err)?;
113 Ok(bars)
115 })
116 }
117
118 #[pyo3(signature = (tick_type, start_date_time, end_date_time, contracts=None, instrument_ids=None, use_rth=true, timeout=60, limit=0))]
120 #[pyo3(name = "request_ticks")]
121 #[allow(clippy::too_many_arguments)]
122 #[allow(clippy::needless_pass_by_value)]
123 fn py_request_ticks<'py>(
124 &self,
125 py: Python<'py>,
126 tick_type: IbHistoricalTickType,
127 start_date_time: Timestamp,
128 end_date_time: Timestamp,
129 contracts: Option<Py<PyList>>,
130 instrument_ids: Option<Vec<InstrumentId>>,
131 use_rth: bool,
132 timeout: u64,
133 limit: usize,
134 ) -> PyResult<Bound<'py, PyAny>> {
135 let client = self.clone();
136
137 let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
139 let py_contracts_bound = py_contracts.bind(py);
140 match py_list_to_contracts(py_contracts_bound) {
141 Ok(contracts) => Some(contracts),
142 Err(e) => {
143 return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
144 }
145 }
146 } else {
147 None
148 };
149
150 pyo3_async_runtimes::tokio::future_into_py(py, async move {
151 let data_vec: Vec<Data> = client
152 .request_ticks(
153 tick_type,
154 start_date_time,
155 end_date_time,
156 contracts_vec,
157 instrument_ids,
158 use_rth,
159 timeout,
160 limit,
161 )
162 .await
163 .map_err(to_pyruntime_err)?;
164 Python::attach(|py| -> PyResult<Py<PyList>> {
166 let py_list = PyList::empty(py);
167 for data in data_vec {
168 let py_obj = data_to_pyobject(py, data)?;
169 py_list.append(py_obj)?;
170 }
171
172 Ok(py_list.into())
173 })
174 })
175 }
176
177 #[pyo3(signature = (instrument_ids=None, contracts=None))]
179 #[pyo3(name = "request_instruments")]
180 #[allow(clippy::needless_pass_by_value)]
181 fn py_request_instruments<'py>(
182 &self,
183 py: Python<'py>,
184 instrument_ids: Option<Vec<InstrumentId>>,
185 contracts: Option<Py<PyList>>,
186 ) -> PyResult<Bound<'py, PyAny>> {
187 let client = self.clone();
188
189 let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
191 let py_contracts_bound = py_contracts.bind(py);
192 match py_list_to_contracts(py_contracts_bound) {
193 Ok(contracts) => Some(contracts),
194 Err(e) => {
195 return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
196 }
197 }
198 } else {
199 None
200 };
201
202 pyo3_async_runtimes::tokio::future_into_py(py, async move {
203 let instruments: Vec<InstrumentAny> = client
204 .request_instruments(instrument_ids, contracts_vec)
205 .await
206 .map_err(to_pyruntime_err)?;
207 Python::attach(|py| -> PyResult<Py<PyList>> {
209 let py_list = PyList::empty(py);
210
211 for instrument in instruments {
212 let py_obj =
213 instrument_any_to_pyobject(py, instrument).map_err(to_pyruntime_err)?;
214 py_list.append(py_obj)?;
215 }
216
217 Ok(py_list.into())
218 })
219 })
220 }
221}