use ibapi::contracts::Contract;
use jiff::Timestamp;
use nautilus_common::live::get_runtime;
use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
use nautilus_model::{
data::{Bar, Data},
identifiers::InstrumentId,
instruments::any::InstrumentAny,
python::{data::data_to_pyobject, instruments::instrument_any_to_pyobject},
};
use pyo3::{prelude::*, types::PyList};
use crate::{
common::enums::IbHistoricalTickType, historical::HistoricalInteractiveBrokersClient,
python::conversion::py_list_to_contracts,
};
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl HistoricalInteractiveBrokersClient {
#[new]
#[allow(clippy::needless_pass_by_value)]
fn py_new(
instrument_provider: crate::providers::instruments::InteractiveBrokersInstrumentProvider,
config: crate::config::InteractiveBrokersDataClientConfig,
) -> PyResult<Self> {
get_runtime()
.block_on(Self::connect_with_provider(instrument_provider, config))
.map_err(to_pyruntime_err)
}
fn __repr__(&self) -> String {
format!("{self:?}")
}
#[pyo3(signature = (bar_specifications, end_date_time, start_date_time=None, duration=None, contracts=None, instrument_ids=None, use_rth=true, timeout=60))]
#[pyo3(name = "request_bars")]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::needless_pass_by_value)]
fn py_request_bars<'py>(
&self,
py: Python<'py>,
bar_specifications: Vec<String>,
end_date_time: Timestamp,
start_date_time: Option<Timestamp>,
duration: Option<String>,
contracts: Option<Py<PyList>>,
instrument_ids: Option<Vec<InstrumentId>>,
use_rth: bool,
timeout: u64,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let bar_specs = bar_specifications;
let duration_str = duration;
let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
let py_contracts_bound = py_contracts.bind(py);
match py_list_to_contracts(py_contracts_bound) {
Ok(contracts) => Some(contracts),
Err(e) => {
return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
}
}
} else {
None
};
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let bar_specs_refs: Vec<&str> = bar_specs.iter().map(|s| s.as_str()).collect();
let bars: Vec<Bar> = client
.request_bars(
bar_specs_refs,
end_date_time,
start_date_time,
duration_str.as_deref(),
contracts_vec,
instrument_ids,
use_rth,
timeout,
)
.await
.map_err(to_pyruntime_err)?;
Ok(bars)
})
}
#[pyo3(signature = (tick_type, start_date_time, end_date_time, contracts=None, instrument_ids=None, use_rth=true, timeout=60, limit=0))]
#[pyo3(name = "request_ticks")]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::needless_pass_by_value)]
fn py_request_ticks<'py>(
&self,
py: Python<'py>,
tick_type: IbHistoricalTickType,
start_date_time: Timestamp,
end_date_time: Timestamp,
contracts: Option<Py<PyList>>,
instrument_ids: Option<Vec<InstrumentId>>,
use_rth: bool,
timeout: u64,
limit: usize,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
let py_contracts_bound = py_contracts.bind(py);
match py_list_to_contracts(py_contracts_bound) {
Ok(contracts) => Some(contracts),
Err(e) => {
return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
}
}
} else {
None
};
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let data_vec: Vec<Data> = client
.request_ticks(
tick_type,
start_date_time,
end_date_time,
contracts_vec,
instrument_ids,
use_rth,
timeout,
limit,
)
.await
.map_err(to_pyruntime_err)?;
Python::attach(|py| -> PyResult<Py<PyList>> {
let py_list = PyList::empty(py);
for data in data_vec {
py_list.append(data_to_pyobject(py, data)?)?;
}
Ok(py_list.into())
})
})
}
#[pyo3(signature = (instrument_ids=None, contracts=None))]
#[pyo3(name = "request_instruments")]
#[allow(clippy::needless_pass_by_value)]
fn py_request_instruments<'py>(
&self,
py: Python<'py>,
instrument_ids: Option<Vec<InstrumentId>>,
contracts: Option<Py<PyList>>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let contracts_vec: Option<Vec<Contract>> = if let Some(py_contracts) = contracts.as_ref() {
let py_contracts_bound = py_contracts.bind(py);
match py_list_to_contracts(py_contracts_bound) {
Ok(contracts) => Some(contracts),
Err(e) => {
return Err(to_pyvalue_err(format!("Failed to convert contracts: {e}")));
}
}
} else {
None
};
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let instruments: Vec<InstrumentAny> = client
.request_instruments(instrument_ids, contracts_vec)
.await
.map_err(to_pyruntime_err)?;
Python::attach(|py| -> PyResult<Py<PyList>> {
let py_list = PyList::empty(py);
for instrument in instruments {
let py_obj =
instrument_any_to_pyobject(py, instrument).map_err(to_pyruntime_err)?;
py_list.append(py_obj)?;
}
Ok(py_list.into())
})
})
}
}