nautilus_interactive_brokers/error.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Error types and classification for the Interactive Brokers adapter.
17
18use thiserror::Error;
19
20/// Errors that can occur in the Interactive Brokers adapter.
21#[derive(Error, Debug)]
22pub enum InteractiveBrokersError {
23 /// Connection error.
24 #[error("Connection error: {0}")]
25 Connection(String),
26
27 /// Authentication error.
28 #[error("Authentication error: {0}")]
29 Authentication(String),
30
31 /// Invalid configuration.
32 #[error("Invalid configuration: {0}")]
33 Configuration(String),
34
35 /// API request error.
36 #[error("API request error: {0}")]
37 Request(String),
38
39 /// Response parsing error.
40 #[error("Response parsing error: {0}")]
41 Parse(String),
42
43 /// Instrument error.
44 #[error("Instrument error: {0}")]
45 Instrument(String),
46
47 /// Order error.
48 #[error("Order error: {0}")]
49 Order(String),
50
51 /// Market data error.
52 #[error("Market data error: {0}")]
53 MarketData(String),
54
55 /// Generic error from rust-ibapi.
56 #[error("IB API error: {0}")]
57 IbApi(String),
58
59 /// Internal error.
60 #[error("Internal error: {0}")]
61 Internal(String),
62}
63
64/// Result type for Interactive Brokers operations.
65pub type InteractiveBrokersResult<T> = Result<T, InteractiveBrokersError>;
66
67/// IB API error code classification.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum ErrorCategory {
70 /// Client/application error (should not retry).
71 ClientError,
72 /// Connectivity error (should retry with backoff).
73 ConnectivityError,
74 /// Subscription error (may need resubscription).
75 SubscriptionError,
76 /// Order error (may need special handling).
77 OrderError,
78 /// Market data error (may need resubscription).
79 MarketDataError,
80 /// Unknown/unclassified error.
81 Unknown,
82}
83
84/// Classify an IB error code into a category.
85///
86/// # Arguments
87///
88/// * `error_code` - The IB API error code
89///
90/// # Returns
91///
92/// Returns the error category for the given code.
93pub fn classify_error_code(error_code: i32) -> ErrorCategory {
94 match error_code {
95 // Client errors - should not retry
96 200..=299 => ErrorCategory::ClientError,
97
98 // Connectivity errors - should retry
99 326 | 502 | 503 | 504 | 1100 | 1101 | 1102 | 1300 | 1301 | 1302 => {
100 ErrorCategory::ConnectivityError
101 }
102
103 // Subscription errors - may need resubscription
104 10189 | 366 | 102 | 10182 => ErrorCategory::SubscriptionError,
105
106 // Market data errors
107 100..=199 if error_code != 10182 => ErrorCategory::MarketDataError,
108
109 // Note: Order errors overlap with client errors range
110 // We handle order errors separately in the match
111
112 // Unknown
113 _ => ErrorCategory::Unknown,
114 }
115}
116
117/// Determine if an error is recoverable.
118///
119/// # Arguments
120///
121/// * `error_code` - The IB API error code
122///
123/// # Returns
124///
125/// Returns `true` if the error is recoverable (should retry).
126pub fn is_recoverable_error(error_code: i32) -> bool {
127 matches!(
128 classify_error_code(error_code),
129 ErrorCategory::ConnectivityError | ErrorCategory::SubscriptionError
130 )
131}
132
133/// Determine if an error requires subscription resubscription.
134///
135/// # Arguments
136///
137/// * `error_code` - The IB API error code
138///
139/// # Returns
140///
141/// Returns `true` if subscriptions should be resubscribed.
142pub fn requires_resubscription(error_code: i32) -> bool {
143 matches!(error_code, 10189 | 366 | 102 | 10182)
144}
145
146/// Get a human-readable error description.
147///
148/// # Arguments
149///
150/// * `error_code` - The IB API error code
151/// * `error_string` - The error message from IB
152///
153/// # Returns
154///
155/// Returns a formatted error description.
156pub fn format_error_message(error_code: i32, error_string: &str) -> String {
157 let category = classify_error_code(error_code);
158 let category_str = match category {
159 ErrorCategory::ClientError => "Client Error",
160 ErrorCategory::ConnectivityError => "Connectivity Error",
161 ErrorCategory::SubscriptionError => "Subscription Error",
162 ErrorCategory::OrderError => "Order Error",
163 ErrorCategory::MarketDataError => "Market Data Error",
164 ErrorCategory::Unknown => "Unknown Error",
165 };
166
167 format!(
168 "[{}] {} (Code: {}): {}",
169 category_str, error_string, error_code, error_string
170 )
171}
172
173#[cfg(test)]
174mod tests {
175 use rstest::rstest;
176
177 use super::{ErrorCategory, classify_error_code, is_recoverable_error};
178
179 #[rstest]
180 #[case(326, ErrorCategory::ConnectivityError)]
181 #[case(502, ErrorCategory::ConnectivityError)]
182 #[case(10182, ErrorCategory::SubscriptionError)]
183 #[case(200, ErrorCategory::ClientError)]
184 fn test_classify_error_code(#[case] error_code: i32, #[case] expected: ErrorCategory) {
185 assert_eq!(classify_error_code(error_code), expected);
186 }
187
188 #[rstest]
189 #[case(326, true)]
190 #[case(10182, true)]
191 #[case(200, false)]
192 fn test_is_recoverable_error(#[case] error_code: i32, #[case] expected: bool) {
193 assert_eq!(is_recoverable_error(error_code), expected);
194 }
195}