use std::collections::{HashMap, HashSet};
use nautilus_model::identifiers::InstrumentId;
use serde::{Deserialize, Serialize};
use crate::common::consts::{DEFAULT_CLIENT_ID, DEFAULT_HOST, DEFAULT_PORT};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.adapters.interactive_brokers",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE"
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(
module = "nautilus_trader.adapters.interactive_brokers"
)
)]
#[derive(Default)]
pub enum MarketDataType {
#[default]
Realtime = 1,
Frozen = 2,
Delayed = 3,
DelayedFrozen = 4,
}
impl From<MarketDataType> for ibapi::market_data::MarketDataType {
fn from(data_type: MarketDataType) -> Self {
match data_type {
MarketDataType::Realtime => Self::Realtime,
MarketDataType::Frozen => Self::Frozen,
MarketDataType::Delayed => Self::Delayed,
MarketDataType::DelayedFrozen => Self::DelayedFrozen,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.adapters.interactive_brokers",
subclass,
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(
module = "nautilus_trader.adapters.interactive_brokers"
)
)]
pub struct InteractiveBrokersDataClientConfig {
#[builder(default = DEFAULT_HOST.to_string())]
pub host: String,
#[builder(default = DEFAULT_PORT)]
pub port: u16,
#[builder(default = DEFAULT_CLIENT_ID)]
pub client_id: i32,
#[builder(default = true)]
pub use_regular_trading_hours: bool,
#[builder(default)]
pub market_data_type: MarketDataType,
#[builder(default)]
pub ignore_quote_tick_size_updates: bool,
#[builder(default = 300)]
pub connection_timeout: u64,
#[builder(default = 60)]
pub request_timeout: u64,
#[builder(default)]
pub handle_revised_bars: bool,
#[builder(default = true)]
pub batch_quotes: bool,
#[builder(default)]
pub instrument_provider: InteractiveBrokersInstrumentProviderConfig,
}
impl Default for InteractiveBrokersDataClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.adapters.interactive_brokers",
subclass,
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(
module = "nautilus_trader.adapters.interactive_brokers"
)
)]
pub struct InteractiveBrokersExecClientConfig {
#[builder(default = DEFAULT_HOST.to_string())]
pub host: String,
#[builder(default = DEFAULT_PORT)]
pub port: u16,
#[builder(default = DEFAULT_CLIENT_ID)]
pub client_id: i32,
pub account_id: Option<String>,
#[builder(default = 300)]
pub connection_timeout: u64,
#[builder(default = 60)]
pub request_timeout: u64,
#[builder(default)]
pub fetch_all_open_orders: bool,
#[builder(default)]
pub track_option_exercise_from_position_update: bool,
#[builder(default)]
pub instrument_provider: InteractiveBrokersInstrumentProviderConfig,
}
impl Default for InteractiveBrokersExecClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.adapters.interactive_brokers",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE"
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(
module = "nautilus_trader.adapters.interactive_brokers"
)
)]
#[derive(Default)]
pub enum SymbologyMethod {
#[serde(rename = "simplified")]
#[default]
Simplified,
#[serde(rename = "raw")]
Raw,
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.adapters.interactive_brokers",
subclass,
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(
module = "nautilus_trader.adapters.interactive_brokers"
)
)]
pub struct InteractiveBrokersInstrumentProviderConfig {
#[builder(default)]
pub symbology_method: SymbologyMethod,
#[builder(default)]
pub load_ids: HashSet<InstrumentId>,
#[builder(default)]
pub load_contracts: Vec<serde_json::Value>,
pub min_expiry_days: Option<u32>,
pub max_expiry_days: Option<u32>,
pub build_options_chain: Option<bool>,
pub build_futures_chain: Option<bool>,
pub cache_validity_days: Option<u32>,
#[builder(default)]
pub convert_exchange_to_mic_venue: bool,
#[builder(default)]
pub symbol_to_mic_venue: HashMap<String, String>,
#[builder(default)]
pub filter_sec_types: HashSet<String>,
pub filter_callable: Option<String>,
pub cache_path: Option<String>,
}
impl Default for InteractiveBrokersInstrumentProviderConfig {
fn default() -> Self {
Self::builder().build()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.adapters.interactive_brokers",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE"
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(
module = "nautilus_trader.adapters.interactive_brokers"
)
)]
#[derive(Default)]
pub enum TradingMode {
#[serde(rename = "paper")]
#[default]
Paper,
#[serde(rename = "live")]
Live,
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.adapters.interactive_brokers",
subclass,
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(
module = "nautilus_trader.adapters.interactive_brokers"
)
)]
pub struct DockerizedIBGatewayConfig {
pub username: Option<String>,
pub password: Option<String>,
#[builder(default)]
pub trading_mode: TradingMode,
#[builder(default = true)]
pub read_only_api: bool,
#[builder(default = 300)]
pub timeout: u64,
#[builder(default = "ghcr.io/gnzsnz/ib-gateway:stable".to_string())]
pub container_image: String,
pub vnc_port: Option<u16>,
}
impl DockerizedIBGatewayConfig {
pub fn mask_sensitive_info(value: &str) -> String {
if value.len() <= 2 {
"*".repeat(value.len())
} else {
format!(
"{}{}{}",
&value[0..1],
"*".repeat(value.len() - 2),
&value[value.len() - 1..]
)
}
}
pub fn validate(&self) -> anyhow::Result<()> {
if self.timeout == 0 {
anyhow::bail!("Timeout must be greater than 0");
}
if self.timeout > 3600 {
anyhow::bail!("Timeout must be less than 3600 seconds");
}
if let Some(port) = self.vnc_port
&& (!(5900..=5999).contains(&port))
{
anyhow::bail!("VNC port must be between 5900 and 5999");
}
Ok(())
}
}
impl Default for DockerizedIBGatewayConfig {
fn default() -> Self {
Self::builder()
.maybe_username(std::env::var("TWS_USERNAME").ok())
.maybe_password(std::env::var("TWS_PASSWORD").ok())
.build()
}
}