Skip to main content

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
64impl InteractiveBrokersError {
65    /// Returns the payload-free error kind.
66    #[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/// Payload-free Interactive Brokers adapter error kind.
84#[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 error.
101    Connection,
102    /// Authentication error.
103    Authentication,
104    /// Invalid configuration.
105    Configuration,
106    /// API request error.
107    Request,
108    /// Response parsing error.
109    Parse,
110    /// Instrument error.
111    Instrument,
112    /// Order error.
113    Order,
114    /// Market data error.
115    MarketData,
116    /// Generic error from rust-ibapi.
117    IbApi,
118    /// Internal error.
119    Internal,
120}
121
122/// Result type for Interactive Brokers operations.
123pub type InteractiveBrokersResult<T> = Result<T, InteractiveBrokersError>;
124
125/// IB API error code classification.
126#[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    /// Client/application error (should not retry).
143    ClientError,
144    /// Connectivity error (should retry with backoff).
145    ConnectivityError,
146    /// Subscription error (may need resubscription).
147    SubscriptionError,
148    /// Order error (may need special handling).
149    OrderError,
150    /// Market data error (may need resubscription).
151    MarketDataError,
152    /// Unknown/unclassified error.
153    Unknown,
154}
155
156/// Classify an IB error code into a category.
157pub fn classify_error_code(error_code: i32) -> ErrorCategory {
158    match error_code {
159        // Client errors - should not retry
160        200..=299 => ErrorCategory::ClientError,
161
162        // Connectivity errors - should retry
163        326 | 502 | 503 | 504 | 1100 | 1101 | 1102 | 1300 | 1301 | 1302 => {
164            ErrorCategory::ConnectivityError
165        }
166
167        // Subscription errors - may need resubscription
168        10189 | 366 | 102 | 10182 => ErrorCategory::SubscriptionError,
169
170        // Market data errors
171        100..=199 if error_code != 10182 => ErrorCategory::MarketDataError,
172
173        // Note: Order errors overlap with client errors range
174        // We handle order errors separately in the match
175
176        // Unknown
177        _ => ErrorCategory::Unknown,
178    }
179}
180
181/// Determine if an error is recoverable.
182pub fn is_recoverable_error(error_code: i32) -> bool {
183    matches!(
184        classify_error_code(error_code),
185        ErrorCategory::ConnectivityError | ErrorCategory::SubscriptionError
186    )
187}
188
189/// Determine if an error requires subscription resubscription.
190pub fn requires_resubscription(error_code: i32) -> bool {
191    matches!(error_code, 10189 | 366 | 102 | 10182)
192}
193
194/// Get a human-readable error description.
195pub 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}