use thiserror::Error;
#[derive(Error, Debug)]
pub enum InteractiveBrokersError {
#[error("Connection error: {0}")]
Connection(String),
#[error("Authentication error: {0}")]
Authentication(String),
#[error("Invalid configuration: {0}")]
Configuration(String),
#[error("API request error: {0}")]
Request(String),
#[error("Response parsing error: {0}")]
Parse(String),
#[error("Instrument error: {0}")]
Instrument(String),
#[error("Order error: {0}")]
Order(String),
#[error("Market data error: {0}")]
MarketData(String),
#[error("IB API error: {0}")]
IbApi(String),
#[error("Internal error: {0}")]
Internal(String),
}
impl InteractiveBrokersError {
#[must_use]
pub const fn kind(&self) -> InteractiveBrokersErrorKind {
match self {
Self::Connection(_) => InteractiveBrokersErrorKind::Connection,
Self::Authentication(_) => InteractiveBrokersErrorKind::Authentication,
Self::Configuration(_) => InteractiveBrokersErrorKind::Configuration,
Self::Request(_) => InteractiveBrokersErrorKind::Request,
Self::Parse(_) => InteractiveBrokersErrorKind::Parse,
Self::Instrument(_) => InteractiveBrokersErrorKind::Instrument,
Self::Order(_) => InteractiveBrokersErrorKind::Order,
Self::MarketData(_) => InteractiveBrokersErrorKind::MarketData,
Self::IbApi(_) => InteractiveBrokersErrorKind::IbApi,
Self::Internal(_) => InteractiveBrokersErrorKind::Internal,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[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"
)
)]
pub enum InteractiveBrokersErrorKind {
Connection,
Authentication,
Configuration,
Request,
Parse,
Instrument,
Order,
MarketData,
IbApi,
Internal,
}
pub type InteractiveBrokersResult<T> = Result<T, InteractiveBrokersError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[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"
)
)]
pub enum ErrorCategory {
ClientError,
ConnectivityError,
SubscriptionError,
OrderError,
MarketDataError,
Unknown,
}
pub fn classify_error_code(error_code: i32) -> ErrorCategory {
match error_code {
200..=299 => ErrorCategory::ClientError,
326 | 502 | 503 | 504 | 1100 | 1101 | 1102 | 1300 | 1301 | 1302 => {
ErrorCategory::ConnectivityError
}
10189 | 366 | 102 | 10182 => ErrorCategory::SubscriptionError,
100..=199 if error_code != 10182 => ErrorCategory::MarketDataError,
_ => ErrorCategory::Unknown,
}
}
pub fn is_recoverable_error(error_code: i32) -> bool {
matches!(
classify_error_code(error_code),
ErrorCategory::ConnectivityError | ErrorCategory::SubscriptionError
)
}
pub fn requires_resubscription(error_code: i32) -> bool {
matches!(error_code, 10189 | 366 | 102 | 10182)
}
pub fn format_error_message(error_code: i32, error_string: &str) -> String {
let category = classify_error_code(error_code);
let category_str = match category {
ErrorCategory::ClientError => "Client Error",
ErrorCategory::ConnectivityError => "Connectivity Error",
ErrorCategory::SubscriptionError => "Subscription Error",
ErrorCategory::OrderError => "Order Error",
ErrorCategory::MarketDataError => "Market Data Error",
ErrorCategory::Unknown => "Unknown Error",
};
format!(
"[{}] {} (Code: {}): {}",
category_str, error_string, error_code, error_string
)
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::{ErrorCategory, classify_error_code, is_recoverable_error};
#[rstest]
#[case(326, ErrorCategory::ConnectivityError)]
#[case(502, ErrorCategory::ConnectivityError)]
#[case(10182, ErrorCategory::SubscriptionError)]
#[case(200, ErrorCategory::ClientError)]
fn test_classify_error_code(#[case] error_code: i32, #[case] expected: ErrorCategory) {
assert_eq!(classify_error_code(error_code), expected);
}
#[rstest]
#[case(326, true)]
#[case(10182, true)]
#[case(200, false)]
fn test_is_recoverable_error(#[case] error_code: i32, #[case] expected: bool) {
assert_eq!(is_recoverable_error(error_code), expected);
}
}