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 chrono::{DateTime, Utc};
19use ibapi::contracts::Contract;
20use nautilus_common::live::get_runtime;
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_pycapsule, instruments::instrument_any_to_pyobject},
27};
28use pyo3::{prelude::*, types::PyList};
29
30use crate::{
31    common::enums::IbHistoricalTickType, historical::HistoricalInteractiveBrokersClient,
32    python::conversion::py_list_to_contracts,
33};
34
35#[pymethods]
36impl HistoricalInteractiveBrokersClient {
37    #[new]
38    #[allow(clippy::needless_pass_by_value)]
39    fn py_new(
40        instrument_provider: crate::providers::instruments::InteractiveBrokersInstrumentProvider,
41        config: crate::config::InteractiveBrokersDataClientConfig,
42    ) -> PyResult<Self> {
43        get_runtime()
44            .block_on(Self::connect_with_provider(instrument_provider, config))
45            .map_err(to_pyruntime_err)
46    }
47
48    fn __repr__(&self) -> String {
49        format!("{self:?}")
50    }
51
52    /// Request historical bars.
53    ///
54    /// # Arguments
55    ///
56    /// * `bar_specifications` - List of bar specifications (e.g., ["1-HOUR-LAST"])
57    /// * `end_date_time` - End date for bars
58    /// * `start_date_time` - Optional start date
59    /// * `duration` - Optional duration string (e.g., "1 D")
60    /// * `contracts` - Optional list of IB contracts (dicts with symbol, sec_type, exchange, currency, etc.)
61    /// * `instrument_ids` - Optional list of instrument IDs
62    /// * `use_rth` - Use regular trading hours only
63    /// * `timeout` - Request timeout in seconds
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: DateTime<Utc>,
73        start_date_time: Option<DateTime<Utc>>,
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    ///
120    /// # Arguments
121    ///
122    /// * `tick_type` - Historical tick type.
123    /// * `start_date_time` - Start date for ticks
124    /// * `end_date_time` - End date for ticks
125    /// * `contracts` - Optional list of IB contracts (dicts with symbol, sec_type, exchange, currency, etc.)
126    /// * `instrument_ids` - Optional list of instrument IDs
127    /// * `use_rth` - Use regular trading hours only
128    /// * `timeout` - Request timeout in seconds
129    /// * `limit` - Maximum number of ticks to return, or 0 for no explicit limit
130    #[pyo3(signature = (tick_type, start_date_time, end_date_time, contracts=None, instrument_ids=None, use_rth=true, timeout=60, limit=0))]
131    #[pyo3(name = "request_ticks")]
132    #[allow(clippy::too_many_arguments)]
133    #[allow(clippy::needless_pass_by_value)]
134    fn py_request_ticks<'py>(
135        &self,
136        py: Python<'py>,
137        tick_type: IbHistoricalTickType,
138        start_date_time: DateTime<Utc>,
139        end_date_time: DateTime<Utc>,
140        contracts: Option<Py<PyList>>,
141        instrument_ids: Option<Vec<InstrumentId>>,
142        use_rth: bool,
143        timeout: u64,
144        limit: usize,
145    ) -> PyResult<Bound<'py, PyAny>> {
146        let client = self.clone();
147
148        // Convert Python contracts list to Rust Contracts
149        let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
150            let py_contracts_bound = py_contracts.bind(py);
151            match py_list_to_contracts(py_contracts_bound) {
152                Ok(contracts) => Some(contracts),
153                Err(e) => {
154                    return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
155                }
156            }
157        } else {
158            None
159        };
160
161        pyo3_async_runtimes::tokio::future_into_py(py, async move {
162            let data_vec: Vec<Data> = client
163                .request_ticks(
164                    tick_type,
165                    start_date_time,
166                    end_date_time,
167                    contracts_vec,
168                    instrument_ids,
169                    use_rth,
170                    timeout,
171                    limit,
172                )
173                .await
174                .map_err(to_pyruntime_err)?;
175            // Convert Data enum to Python objects using pycapsules
176            Python::attach(|py| -> PyResult<Py<PyList>> {
177                let py_list = PyList::empty(py);
178                for data in data_vec {
179                    let py_capsule = data_to_pycapsule(py, data);
180                    py_list.append(py_capsule)?;
181                }
182                Ok(py_list.into())
183            })
184        })
185    }
186
187    /// Request instruments.
188    ///
189    /// # Arguments
190    ///
191    /// * `instrument_ids` - Optional list of instrument IDs to load
192    /// * `contracts` - Optional list of IB contracts (dicts with symbol, sec_type, exchange, currency, etc.)
193    #[pyo3(signature = (instrument_ids=None, contracts=None))]
194    #[pyo3(name = "request_instruments")]
195    #[allow(clippy::needless_pass_by_value)]
196    fn py_request_instruments<'py>(
197        &self,
198        py: Python<'py>,
199        instrument_ids: Option<Vec<InstrumentId>>,
200        contracts: Option<Py<PyList>>,
201    ) -> PyResult<Bound<'py, PyAny>> {
202        let client = self.clone();
203
204        // Convert Python contracts list to Rust Contracts
205        let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
206            let py_contracts_bound = py_contracts.bind(py);
207            match py_list_to_contracts(py_contracts_bound) {
208                Ok(contracts) => Some(contracts),
209                Err(e) => {
210                    return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
211                }
212            }
213        } else {
214            None
215        };
216
217        pyo3_async_runtimes::tokio::future_into_py(py, async move {
218            let instruments: Vec<InstrumentAny> = client
219                .request_instruments(instrument_ids, contracts_vec)
220                .await
221                .map_err(to_pyruntime_err)?;
222            // Convert instruments to Python objects
223            Python::attach(|py| -> PyResult<Py<PyList>> {
224                let py_list = PyList::empty(py);
225
226                for instrument in instruments {
227                    let py_obj =
228                        instrument_any_to_pyobject(py, instrument).map_err(to_pyruntime_err)?;
229                    py_list.append(py_obj)?;
230                }
231                Ok(py_list.into())
232            })
233        })
234    }
235}