Skip to main content

nautilus_interactive_brokers/python/
historical.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for the Interactive Brokers historical data client.
17
18use 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    /// Request historical bars.
55    ///
56    /// # Continuous futures
57    ///
58    /// Continuous futures (`CONTFUT`) reject an explicit end date/time with IB
59    /// error 10339. For these contracts the end date is dropped and only the
60    /// first duration segment is requested, anchored to the current time, so
61    /// the returned bars may fall outside `[start_date_time, end_date_time]`.
62    /// A warning is logged when the requested end date/time is in the past or
63    /// the range spans more than one duration segment.
64    #[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        // Convert Python contracts list to Rust Contracts
85        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            // Convert Vec<String> to Vec<&str> for the request
99            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            // Convert bars to Python objects
114            Ok(bars)
115        })
116    }
117
118    /// Request historical ticks (quotes or trades).
119    #[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        // Convert Python contracts list to Rust Contracts
138        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            // Convert Data values to native Python objects.
165            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    /// Request instruments.
178    #[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        // Convert Python contracts list to Rust Contracts
190        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            // Convert instruments to Python objects
208            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}