use nautilus_core::python::to_pyvalue_err;
use nautilus_model::identifiers::InstrumentId;
use pyo3::prelude::*;
use crate::config::{
DockerizedIBGatewayConfig, InteractiveBrokersDataClientConfig,
InteractiveBrokersExecClientConfig, InteractiveBrokersInstrumentProviderConfig, MarketDataType,
TradingMode,
};
fn validate_order_id_client_slot(client_id: i32) -> PyResult<()> {
if client_id.unsigned_abs().is_multiple_of(1000) {
return Err(to_pyvalue_err(format!(
"`client_id` must not be a multiple of 1000 for the Rust/PyO3 IB execution client because order ID partitioning uses client_id % 1000; got {client_id}"
)));
}
Ok(())
}
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl InteractiveBrokersDataClientConfig {
#[new]
#[pyo3(signature = (host=None, port=None, client_id=None, use_regular_trading_hours=None, market_data_type=None, ignore_quote_tick_size_updates=None, connection_timeout=None, request_timeout=None, handle_revised_bars=None, batch_quotes=None, instrument_provider=None, dockerized_gateway=None))]
#[allow(clippy::too_many_arguments)]
fn py_new(
host: Option<String>,
port: Option<u16>,
client_id: Option<i32>,
use_regular_trading_hours: Option<bool>,
market_data_type: Option<MarketDataType>,
ignore_quote_tick_size_updates: Option<bool>,
connection_timeout: Option<u64>,
request_timeout: Option<u64>,
handle_revised_bars: Option<bool>,
batch_quotes: Option<bool>,
instrument_provider: Option<InteractiveBrokersInstrumentProviderConfig>,
dockerized_gateway: Option<&DockerizedIBGatewayConfig>,
) -> PyResult<Self> {
if dockerized_gateway.is_some() {
return Err(to_pyvalue_err(
"`dockerized_gateway` is not wired into the Rust/PyO3 IB data client; start `DockerizedIBGateway` separately and pass `host`/`port`",
));
}
let host = host.unwrap_or_else(|| crate::common::consts::DEFAULT_HOST.to_string());
let port = port.unwrap_or(crate::common::consts::DEFAULT_PORT);
let client_id = client_id.unwrap_or(crate::common::consts::DEFAULT_CLIENT_ID);
let request_timeout = request_timeout.unwrap_or(60);
Ok(Self {
host,
port,
client_id,
use_regular_trading_hours: use_regular_trading_hours.unwrap_or(true),
market_data_type: market_data_type.unwrap_or_default(),
ignore_quote_tick_size_updates: ignore_quote_tick_size_updates.unwrap_or(false),
connection_timeout: connection_timeout.unwrap_or(300),
request_timeout,
handle_revised_bars: handle_revised_bars.unwrap_or(false),
batch_quotes: batch_quotes.unwrap_or(true),
instrument_provider: instrument_provider.unwrap_or_default(),
})
}
#[getter]
fn host(&self) -> &str {
&self.host
}
#[getter]
fn port(&self) -> u16 {
self.port
}
#[getter]
fn client_id(&self) -> i32 {
self.client_id
}
#[getter]
fn use_regular_trading_hours(&self) -> bool {
self.use_regular_trading_hours
}
#[getter]
fn market_data_type(&self) -> MarketDataType {
self.market_data_type
}
#[getter]
fn ignore_quote_tick_size_updates(&self) -> bool {
self.ignore_quote_tick_size_updates
}
#[getter]
fn connection_timeout(&self) -> u64 {
self.connection_timeout
}
#[getter]
fn request_timeout(&self) -> u64 {
self.request_timeout
}
#[getter]
fn handle_revised_bars(&self) -> bool {
self.handle_revised_bars
}
#[getter]
fn batch_quotes(&self) -> bool {
self.batch_quotes
}
#[getter]
fn instrument_provider(&self) -> InteractiveBrokersInstrumentProviderConfig {
self.instrument_provider.clone()
}
#[setter]
fn set_instrument_provider(
&mut self,
instrument_provider: InteractiveBrokersInstrumentProviderConfig,
) {
self.instrument_provider = instrument_provider;
}
}
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl InteractiveBrokersExecClientConfig {
#[new]
#[pyo3(signature = (host=None, port=None, client_id=None, account_id=None, connection_timeout=None, request_timeout=None, fetch_all_open_orders=None, track_option_exercise_from_position_update=None, instrument_provider=None, dockerized_gateway=None))]
#[allow(clippy::too_many_arguments)]
fn py_new(
host: Option<String>,
port: Option<u16>,
client_id: Option<i32>,
account_id: Option<String>,
connection_timeout: Option<u64>,
request_timeout: Option<u64>,
fetch_all_open_orders: Option<bool>,
track_option_exercise_from_position_update: Option<bool>,
instrument_provider: Option<InteractiveBrokersInstrumentProviderConfig>,
dockerized_gateway: Option<&DockerizedIBGatewayConfig>,
) -> PyResult<Self> {
if dockerized_gateway.is_some() {
return Err(to_pyvalue_err(
"`dockerized_gateway` is not wired into the Rust/PyO3 IB execution client; start `DockerizedIBGateway` separately and pass `host`/`port`",
));
}
let host = host.unwrap_or_else(|| crate::common::consts::DEFAULT_HOST.to_string());
let port = port.unwrap_or(crate::common::consts::DEFAULT_PORT);
let client_id = client_id.unwrap_or(crate::common::consts::DEFAULT_CLIENT_ID);
validate_order_id_client_slot(client_id)?;
let request_timeout = request_timeout.unwrap_or(60);
Ok(Self {
host,
port,
client_id,
account_id,
connection_timeout: connection_timeout.unwrap_or(300),
request_timeout,
fetch_all_open_orders: fetch_all_open_orders.unwrap_or(false),
track_option_exercise_from_position_update: track_option_exercise_from_position_update
.unwrap_or(false),
instrument_provider: instrument_provider.unwrap_or_default(),
})
}
#[getter]
fn host(&self) -> &str {
&self.host
}
#[getter]
fn port(&self) -> u16 {
self.port
}
#[getter]
fn client_id(&self) -> i32 {
self.client_id
}
#[getter]
fn account_id(&self) -> Option<String> {
self.account_id.clone()
}
#[getter]
fn connection_timeout(&self) -> u64 {
self.connection_timeout
}
#[getter]
fn request_timeout(&self) -> u64 {
self.request_timeout
}
#[getter]
fn fetch_all_open_orders(&self) -> bool {
self.fetch_all_open_orders
}
#[getter]
fn track_option_exercise_from_position_update(&self) -> bool {
self.track_option_exercise_from_position_update
}
#[getter]
fn instrument_provider(&self) -> InteractiveBrokersInstrumentProviderConfig {
self.instrument_provider.clone()
}
#[setter]
fn set_instrument_provider(
&mut self,
instrument_provider: InteractiveBrokersInstrumentProviderConfig,
) {
self.instrument_provider = instrument_provider;
}
}
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl InteractiveBrokersInstrumentProviderConfig {
#[new]
#[pyo3(signature = (symbology_method=None, load_ids=None, load_contracts=None, min_expiry_days=None, max_expiry_days=None, build_options_chain=None, build_futures_chain=None, cache_validity_days=None, convert_exchange_to_mic_venue=None, symbol_to_mic_venue=None, filter_sec_types=None, filter_callable=None, cache_path=None))]
#[allow(clippy::too_many_arguments)]
fn py_new(
py: Python<'_>,
symbology_method: Option<crate::config::SymbologyMethod>,
load_ids: Option<std::collections::HashSet<InstrumentId>>,
load_contracts: Option<Py<pyo3::types::PyList>>,
min_expiry_days: Option<u32>,
max_expiry_days: Option<u32>,
build_options_chain: Option<bool>,
build_futures_chain: Option<bool>,
cache_validity_days: Option<u32>,
convert_exchange_to_mic_venue: Option<bool>,
symbol_to_mic_venue: Option<std::collections::HashMap<String, String>>,
filter_sec_types: Option<std::collections::HashSet<String>>,
filter_callable: Option<String>,
cache_path: Option<String>,
) -> PyResult<Self> {
Ok(Self {
symbology_method: symbology_method.unwrap_or_default(),
load_ids: load_ids.unwrap_or_default(),
load_contracts: if let Some(c) = load_contracts {
crate::python::conversion::py_list_to_json_values(c.bind(py))?
} else {
Vec::new()
},
min_expiry_days,
max_expiry_days,
build_options_chain,
build_futures_chain,
cache_validity_days,
convert_exchange_to_mic_venue: convert_exchange_to_mic_venue.unwrap_or(false),
symbol_to_mic_venue: symbol_to_mic_venue.unwrap_or_default(),
filter_sec_types: filter_sec_types.unwrap_or_default(),
filter_callable,
cache_path,
})
}
#[getter]
fn symbology_method(&self) -> crate::config::SymbologyMethod {
self.symbology_method
}
#[getter]
fn load_ids(&self) -> std::collections::HashSet<InstrumentId> {
self.load_ids.clone()
}
#[getter]
fn load_contracts(&self, py: Python<'_>) -> PyResult<Py<pyo3::types::PyList>> {
let json_mod = py.import("json")?;
let list = pyo3::types::PyList::empty(py);
for value in &self.load_contracts {
let json_str = value.to_string();
let dict = json_mod.call_method1("loads", (json_str,))?;
list.append(dict)?;
}
Ok(list.unbind())
}
#[getter]
fn min_expiry_days(&self) -> Option<u32> {
self.min_expiry_days
}
#[getter]
fn max_expiry_days(&self) -> Option<u32> {
self.max_expiry_days
}
#[getter]
fn build_options_chain(&self) -> Option<bool> {
self.build_options_chain
}
#[getter]
fn build_futures_chain(&self) -> Option<bool> {
self.build_futures_chain
}
#[getter]
fn cache_validity_days(&self) -> Option<u32> {
self.cache_validity_days
}
#[getter]
fn convert_exchange_to_mic_venue(&self) -> bool {
self.convert_exchange_to_mic_venue
}
#[getter]
fn symbol_to_mic_venue(&self) -> std::collections::HashMap<String, String> {
self.symbol_to_mic_venue.clone()
}
#[getter]
fn filter_sec_types(&self) -> Vec<String> {
self.filter_sec_types.iter().cloned().collect()
}
#[getter]
fn filter_callable(&self) -> Option<String> {
self.filter_callable.clone()
}
#[getter]
fn cache_path(&self) -> Option<String> {
self.cache_path.clone()
}
#[setter]
fn set_cache_path(&mut self, cache_path: Option<String>) {
self.cache_path = cache_path;
}
}
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl DockerizedIBGatewayConfig {
#[new]
#[pyo3(signature = (username=None, password=None, trading_mode=None, read_only_api=None, timeout=None, container_image=None, vnc_port=None))]
fn py_new(
username: Option<String>,
password: Option<String>,
trading_mode: Option<TradingMode>,
read_only_api: Option<bool>,
timeout: Option<u64>,
container_image: Option<String>,
vnc_port: Option<u16>,
) -> Self {
Self {
username,
password,
trading_mode: trading_mode.unwrap_or_default(),
read_only_api: read_only_api.unwrap_or(true),
timeout: timeout.unwrap_or(300),
container_image: container_image
.unwrap_or_else(|| "ghcr.io/gnzsnz/ib-gateway:stable".to_string()),
vnc_port,
}
}
#[getter]
fn username(&self) -> Option<String> {
self.username.clone()
}
#[getter]
const fn has_password(&self) -> bool {
self.password.is_some()
}
#[getter]
fn trading_mode(&self) -> TradingMode {
self.trading_mode
}
#[getter]
fn read_only_api(&self) -> bool {
self.read_only_api
}
#[getter]
fn timeout(&self) -> u64 {
self.timeout
}
#[getter]
fn container_image(&self) -> &str {
&self.container_image
}
#[getter]
fn vnc_port(&self) -> Option<u16> {
self.vnc_port
}
}