Skip to main content

nautilus_interactive_brokers/python/
providers.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 instrument provider.
17
18use nautilus_core::python::to_pyruntime_err;
19use nautilus_model::{identifiers::InstrumentId, python::instruments::instrument_any_to_pyobject};
20use pyo3::{prelude::*, types::PyList};
21
22use crate::{
23    providers::instruments::InteractiveBrokersInstrumentProvider,
24    python::conversion::{contract_details_to_pyobject, py_to_contract},
25};
26
27#[cfg(feature = "python")]
28#[pymethods]
29#[pyo3_stub_gen::derive::gen_stub_pymethods]
30impl InteractiveBrokersInstrumentProvider {
31    #[new]
32    fn py_new(config: crate::config::InteractiveBrokersInstrumentProviderConfig) -> Self {
33        Self::new(config)
34    }
35
36    fn __repr__(&self) -> String {
37        format!("{self:?}")
38    }
39
40    /// Find an instrument by its ID.
41    #[pyo3(name = "find")]
42    fn py_find(&self, py: Python, instrument_id: InstrumentId) -> PyResult<Option<Py<PyAny>>> {
43        match self.find(&instrument_id) {
44            Some(instrument) => Ok(Some(instrument_any_to_pyobject(py, instrument)?)),
45            None => Ok(None),
46        }
47    }
48
49    /// Find an instrument by IB contract ID.
50    #[pyo3(name = "find_by_contract_id")]
51    fn py_find_by_contract_id(&self, py: Python, contract_id: i32) -> PyResult<Option<Py<PyAny>>> {
52        match self.find_by_contract_id(contract_id) {
53            Some(instrument) => Ok(Some(instrument_any_to_pyobject(py, instrument)?)),
54            None => Ok(None),
55        }
56    }
57
58    /// Get all cached instruments.
59    #[pyo3(name = "get_all")]
60    fn py_get_all<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
61        let instruments = self.get_all();
62        let py_instruments: PyResult<Vec<_>> = instruments
63            .into_iter()
64            .map(|inst| instrument_any_to_pyobject(py, inst))
65            .collect();
66        PyList::new(py, py_instruments?)
67    }
68
69    /// Get the number of cached instruments.
70    #[pyo3(name = "count")]
71    fn py_count(&self) -> usize {
72        self.count()
73    }
74
75    /// Get price magnifier for an instrument ID.
76    #[pyo3(name = "get_price_magnifier")]
77    fn py_get_price_magnifier(&self, instrument_id: InstrumentId) -> i32 {
78        self.get_price_magnifier(&instrument_id)
79    }
80
81    /// Maintain compatibility with the legacy Python provider API.
82    ///
83    /// Contract details are fetched as part of the data/execution client load flow,
84    /// so the standalone provider has nothing to do here.
85    #[pyo3(name = "fetch_contract_details")]
86    fn py_fetch_contract_details(&self) {}
87
88    /// Determine venue from contract using provider configuration.
89    #[pyo3(name = "determine_venue")]
90    #[allow(clippy::needless_pass_by_value)]
91    fn py_determine_venue(&self, py: Python<'_>, contract: Py<PyAny>) -> PyResult<String> {
92        let rust_contract = py_to_contract(contract.bind(py))?;
93        Ok(self.determine_venue(&rust_contract, None).to_string())
94    }
95
96    /// Convert an instrument ID to cached IB contract details.
97    #[pyo3(name = "instrument_id_to_ib_contract_details")]
98    fn py_instrument_id_to_ib_contract_details(
99        &self,
100        py: Python<'_>,
101        instrument_id: InstrumentId,
102    ) -> PyResult<Option<Py<PyAny>>> {
103        self.instrument_id_to_ib_contract_details(&instrument_id)
104            .as_ref()
105            .map(|details| contract_details_to_pyobject(py, details))
106            .transpose()
107    }
108
109    /// Save the current instrument cache to disk.
110    ///
111    /// # Arguments
112    ///
113    /// * `cache_path` - Path to the cache file
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if serialization or file I/O fails.
118    #[pyo3(name = "save_cache")]
119    fn py_save_cache<'py>(
120        &self,
121        py: Python<'py>,
122        cache_path: String,
123    ) -> PyResult<Bound<'py, PyAny>> {
124        let provider = self.clone();
125        pyo3_async_runtimes::tokio::future_into_py(py, async move {
126            provider
127                .save_cache(&cache_path)
128                .await
129                .map_err(to_pyruntime_err)
130        })
131    }
132
133    /// Load instrument cache from disk if valid.
134    ///
135    /// # Arguments
136    ///
137    /// * `cache_path` - Path to the cache file
138    ///
139    /// # Returns
140    ///
141    /// Returns `true` if cache was loaded successfully and is valid, `false` otherwise.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if deserialization or file I/O fails (but treats missing file as non-error).
146    #[pyo3(name = "load_cache")]
147    fn py_load_cache<'py>(
148        &self,
149        py: Python<'py>,
150        cache_path: String,
151    ) -> PyResult<Bound<'py, PyAny>> {
152        let provider = self.clone();
153        pyo3_async_runtimes::tokio::future_into_py(py, async move {
154            provider
155                .load_cache(&cache_path)
156                .await
157                .map_err(to_pyruntime_err)
158        })
159    }
160}