nautilus_interactive_brokers/
error.rs1use thiserror::Error;
19
20#[derive(Error, Debug)]
22pub enum InteractiveBrokersError {
23 #[error("Connection error: {0}")]
25 Connection(String),
26
27 #[error("Authentication error: {0}")]
29 Authentication(String),
30
31 #[error("Invalid configuration: {0}")]
33 Configuration(String),
34
35 #[error("API request error: {0}")]
37 Request(String),
38
39 #[error("Response parsing error: {0}")]
41 Parse(String),
42
43 #[error("Instrument error: {0}")]
45 Instrument(String),
46
47 #[error("Order error: {0}")]
49 Order(String),
50
51 #[error("Market data error: {0}")]
53 MarketData(String),
54
55 #[error("IB API error: {0}")]
57 IbApi(String),
58
59 #[error("Internal error: {0}")]
61 Internal(String),
62}
63
64impl InteractiveBrokersError {
65 #[must_use]
67 pub const fn kind(&self) -> InteractiveBrokersErrorKind {
68 match self {
69 Self::Connection(_) => InteractiveBrokersErrorKind::Connection,
70 Self::Authentication(_) => InteractiveBrokersErrorKind::Authentication,
71 Self::Configuration(_) => InteractiveBrokersErrorKind::Configuration,
72 Self::Request(_) => InteractiveBrokersErrorKind::Request,
73 Self::Parse(_) => InteractiveBrokersErrorKind::Parse,
74 Self::Instrument(_) => InteractiveBrokersErrorKind::Instrument,
75 Self::Order(_) => InteractiveBrokersErrorKind::Order,
76 Self::MarketData(_) => InteractiveBrokersErrorKind::MarketData,
77 Self::IbApi(_) => InteractiveBrokersErrorKind::IbApi,
78 Self::Internal(_) => InteractiveBrokersErrorKind::Internal,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[cfg_attr(
86 feature = "python",
87 pyo3::pyclass(
88 module = "nautilus_trader.adapters.interactive_brokers",
89 from_py_object,
90 rename_all = "SCREAMING_SNAKE_CASE"
91 )
92)]
93#[cfg_attr(
94 feature = "python",
95 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
96 module = "nautilus_trader.adapters.interactive_brokers"
97 )
98)]
99pub enum InteractiveBrokersErrorKind {
100 Connection,
102 Authentication,
104 Configuration,
106 Request,
108 Parse,
110 Instrument,
112 Order,
114 MarketData,
116 IbApi,
118 Internal,
120}
121
122pub type InteractiveBrokersResult<T> = Result<T, InteractiveBrokersError>;
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127#[cfg_attr(
128 feature = "python",
129 pyo3::pyclass(
130 module = "nautilus_trader.adapters.interactive_brokers",
131 from_py_object,
132 rename_all = "SCREAMING_SNAKE_CASE"
133 )
134)]
135#[cfg_attr(
136 feature = "python",
137 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
138 module = "nautilus_trader.adapters.interactive_brokers"
139 )
140)]
141pub enum ErrorCategory {
142 ClientError,
144 ConnectivityError,
146 SubscriptionError,
148 OrderError,
150 MarketDataError,
152 Unknown,
154}
155
156pub fn classify_error_code(error_code: i32) -> ErrorCategory {
158 match error_code {
159 200..=299 => ErrorCategory::ClientError,
161
162 326 | 502 | 503 | 504 | 1100 | 1101 | 1102 | 1300 | 1301 | 1302 => {
164 ErrorCategory::ConnectivityError
165 }
166
167 10189 | 366 | 102 | 10182 => ErrorCategory::SubscriptionError,
169
170 100..=199 if error_code != 10182 => ErrorCategory::MarketDataError,
172
173 _ => ErrorCategory::Unknown,
178 }
179}
180
181pub fn is_recoverable_error(error_code: i32) -> bool {
183 matches!(
184 classify_error_code(error_code),
185 ErrorCategory::ConnectivityError | ErrorCategory::SubscriptionError
186 )
187}
188
189pub fn requires_resubscription(error_code: i32) -> bool {
191 matches!(error_code, 10189 | 366 | 102 | 10182)
192}
193
194pub fn format_error_message(error_code: i32, error_string: &str) -> String {
196 let category = classify_error_code(error_code);
197 let category_str = match category {
198 ErrorCategory::ClientError => "Client Error",
199 ErrorCategory::ConnectivityError => "Connectivity Error",
200 ErrorCategory::SubscriptionError => "Subscription Error",
201 ErrorCategory::OrderError => "Order Error",
202 ErrorCategory::MarketDataError => "Market Data Error",
203 ErrorCategory::Unknown => "Unknown Error",
204 };
205
206 format!(
207 "[{}] {} (Code: {}): {}",
208 category_str, error_string, error_code, error_string
209 )
210}
211
212#[cfg(test)]
213mod tests {
214 use rstest::rstest;
215
216 use super::{ErrorCategory, classify_error_code, is_recoverable_error};
217
218 #[rstest]
219 #[case(326, ErrorCategory::ConnectivityError)]
220 #[case(502, ErrorCategory::ConnectivityError)]
221 #[case(10182, ErrorCategory::SubscriptionError)]
222 #[case(200, ErrorCategory::ClientError)]
223 fn test_classify_error_code(#[case] error_code: i32, #[case] expected: ErrorCategory) {
224 assert_eq!(classify_error_code(error_code), expected);
225 }
226
227 #[rstest]
228 #[case(326, true)]
229 #[case(10182, true)]
230 #[case(200, false)]
231 fn test_is_recoverable_error(#[case] error_code: i32, #[case] expected: bool) {
232 assert_eq!(is_recoverable_error(error_code), expected);
233 }
234}