Skip to main content

ibapi/wsh/
mod.rs

1//! Wall Street Horizon: Earnings Calendar & Event Data.
2//!
3//! This module provides access to Wall Street Horizon data including
4//! earnings calendars, corporate events, and other fundamental data
5//! events that may impact trading decisions.
6
7use std::str;
8
9use serde::{Deserialize, Serialize};
10
11mod common;
12
13// Re-export common functionality
14#[cfg(test)]
15use common::decoders;
16use common::encoders;
17
18// Feature-specific implementations
19#[cfg(feature = "sync")]
20mod sync;
21
22#[cfg(feature = "async")]
23mod r#async;
24
25/// Wall Street Horizon metadata containing configuration and setup information.
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct WshMetadata {
28    /// JSON string containing metadata information from Wall Street Horizon.
29    pub data_json: String,
30}
31
32/// Wall Street Horizon event data containing earnings calendar and corporate events.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct WshEventData {
35    /// JSON string containing event data from Wall Street Horizon.
36    pub data_json: String,
37}
38
39/// Configuration for automatic filling of Wall Street Horizon event data.
40///
41/// This struct controls which types of securities should be automatically
42/// included when requesting WSH event data. When enabled, the API will
43/// include related securities based on the specified criteria.
44#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize, Deserialize)]
45pub struct AutoFill {
46    /// Automatically fill in competitor values of existing positions.
47    pub competitors: bool,
48    /// Automatically fill in portfolio values.
49    pub portfolio: bool,
50    /// Automatically fill in watchlist values.
51    pub watchlist: bool,
52}
53
54impl AutoFill {
55    /// Returns true if any auto-fill option is enabled.
56    pub fn is_specified(&self) -> bool {
57        self.competitors || self.portfolio || self.watchlist
58    }
59}
60
61// Re-export API functions based on active feature
62#[cfg(feature = "sync")]
63/// Blocking WSH helpers utilizing the synchronous transport.
64pub mod blocking {
65    pub(crate) use super::sync::{wsh_event_data_by_contract, wsh_event_data_by_filter, wsh_metadata};
66}
67
68#[cfg(feature = "async")]
69pub(crate) use r#async::{wsh_event_data_by_contract, wsh_event_data_by_filter, wsh_metadata};
70
71// Tests that work with both sync and async features
72#[cfg(test)]
73mod common_tests {
74    use super::*;
75    use crate::messages::ResponseMessage;
76
77    #[test]
78    fn test_autofill_is_specified() {
79        assert!(!AutoFill::default().is_specified());
80
81        assert!(AutoFill {
82            competitors: true,
83            portfolio: false,
84            watchlist: false,
85        }
86        .is_specified());
87
88        assert!(AutoFill {
89            competitors: false,
90            portfolio: true,
91            watchlist: false,
92        }
93        .is_specified());
94
95        assert!(AutoFill {
96            competitors: false,
97            portfolio: false,
98            watchlist: true,
99        }
100        .is_specified());
101    }
102
103    #[test]
104    fn test_autofill_combinations() {
105        // Test all possible combinations
106        let combinations = vec![
107            (false, false, false, false),
108            (true, false, false, true),
109            (false, true, false, true),
110            (false, false, true, true),
111            (true, true, false, true),
112            (true, false, true, true),
113            (false, true, true, true),
114            (true, true, true, true),
115        ];
116
117        for (competitors, portfolio, watchlist, expected) in combinations {
118            let autofill = AutoFill {
119                competitors,
120                portfolio,
121                watchlist,
122            };
123            assert_eq!(
124                autofill.is_specified(),
125                expected,
126                "Failed for combination: competitors={}, portfolio={}, watchlist={}",
127                competitors,
128                portfolio,
129                watchlist
130            );
131        }
132    }
133
134    #[test]
135    fn test_decode_wsh_metadata() {
136        use super::decoders::decode_wsh_metadata;
137
138        let message = ResponseMessage::from("104\09000\0{\"test\":\"data\"}\0");
139        let result = decode_wsh_metadata(message);
140
141        assert!(result.is_ok(), "failed to decode wsh metadata: {}", result.err().unwrap());
142        assert_eq!(result.unwrap().data_json, "{\"test\":\"data\"}");
143    }
144
145    #[test]
146    fn test_decode_wsh_event_data() {
147        use super::decoders::decode_wsh_event_data;
148
149        let message = ResponseMessage::from("105\09000\0{\"test\":\"data\"}\0");
150        let result = decode_wsh_event_data(message);
151
152        assert!(result.is_ok(), "failed to decode wsh event data: {}", result.err().unwrap());
153        assert_eq!(result.unwrap().data_json, "{\"test\":\"data\"}");
154    }
155
156    #[test]
157    fn test_encode_request_wsh_metadata() {
158        use super::encoders::encode_request_wsh_metadata;
159
160        let result = encode_request_wsh_metadata(9000);
161        assert!(result.is_ok());
162        assert_eq!(result.unwrap().encode_simple(), "100|9000|");
163    }
164
165    #[test]
166    fn test_encode_cancel_wsh_metadata() {
167        use super::encoders::encode_cancel_wsh_metadata;
168
169        let result = encode_cancel_wsh_metadata(9000);
170        assert!(result.is_ok());
171        assert_eq!(result.unwrap().encode_simple(), "101|9000|");
172    }
173
174    #[test]
175    fn test_encode_request_wsh_event_data() {
176        use super::encoders::encode_request_wsh_event_data;
177        use crate::server_versions;
178        use time::macros::date;
179
180        // Test with minimal params
181        let result = encode_request_wsh_event_data(server_versions::WSHE_CALENDAR, 9000, Some(12345), None, None, None, None, None);
182        assert!(result.is_ok());
183        assert_eq!(result.unwrap().encode_simple(), "102|9000|12345|");
184
185        // Test with all params
186        let result = encode_request_wsh_event_data(
187            server_versions::WSH_EVENT_DATA_FILTERS_DATE,
188            9000,
189            Some(12345),
190            Some("filter"),
191            Some(date!(2024 - 01 - 01)),
192            Some(date!(2024 - 12 - 31)),
193            Some(100),
194            Some(AutoFill {
195                competitors: true,
196                portfolio: false,
197                watchlist: true,
198            }),
199        );
200        assert!(result.is_ok());
201        assert_eq!(result.unwrap().encode_simple(), "102|9000|12345|filter|1|0|1|20240101|20241231|100|");
202    }
203
204    #[test]
205    fn test_encode_cancel_wsh_event_data() {
206        use super::encoders::encode_cancel_wsh_event_data;
207
208        let result = encode_cancel_wsh_event_data(9000);
209        assert!(result.is_ok());
210        assert_eq!(result.unwrap().encode_simple(), "103|9000|");
211    }
212
213    #[test]
214    fn test_decode_wsh_metadata_empty_json() {
215        use super::decoders::decode_wsh_metadata;
216
217        let message = ResponseMessage::from("104\09000\0\0");
218        let result = decode_wsh_metadata(message);
219
220        assert!(result.is_ok(), "failed to decode empty wsh metadata: {}", result.err().unwrap());
221        assert_eq!(result.unwrap().data_json, "");
222    }
223
224    #[test]
225    fn test_decode_wsh_event_data_empty_json() {
226        use super::decoders::decode_wsh_event_data;
227
228        let message = ResponseMessage::from("105\09000\0\0");
229        let result = decode_wsh_event_data(message);
230
231        assert!(result.is_ok(), "failed to decode empty wsh event data: {}", result.err().unwrap());
232        assert_eq!(result.unwrap().data_json, "");
233    }
234
235    #[test]
236    fn test_decode_wsh_metadata_with_special_chars() {
237        use super::decoders::decode_wsh_metadata;
238
239        let message = ResponseMessage::from("104\09000\0{\"data\":\"test\\nwith\\tspecial\\rchars\"}\0");
240        let result = decode_wsh_metadata(message);
241
242        assert!(result.is_ok());
243        assert_eq!(result.unwrap().data_json, "{\"data\":\"test\\nwith\\tspecial\\rchars\"}");
244    }
245
246    #[test]
247    fn test_encode_request_wsh_event_data_edge_cases() {
248        use super::encoders::encode_request_wsh_event_data;
249        use crate::server_versions;
250
251        // Test with empty filter string
252        let result = encode_request_wsh_event_data(server_versions::WSH_EVENT_DATA_FILTERS, 9000, None, Some(""), None, None, None, None);
253        assert!(result.is_ok());
254        assert_eq!(result.unwrap().encode_simple(), "102|9000|||0|0|0|");
255
256        // Test with special characters in filter
257        let result = encode_request_wsh_event_data(
258            server_versions::WSH_EVENT_DATA_FILTERS,
259            9001,
260            None,
261            Some("filter=\"test\" AND type='earnings'"),
262            None,
263            None,
264            None,
265            None,
266        );
267        assert!(result.is_ok());
268        assert_eq!(result.unwrap().encode_simple(), "102|9001||filter=\"test\" AND type='earnings'|0|0|0|");
269
270        // Test with negative limit (should still encode)
271        let result = encode_request_wsh_event_data(
272            server_versions::WSH_EVENT_DATA_FILTERS_DATE,
273            9002,
274            Some(12345),
275            None,
276            None,
277            None,
278            Some(-10),
279            None,
280        );
281        assert!(result.is_ok());
282        assert_eq!(result.unwrap().encode_simple(), "102|9002|12345||0|0|0|||-10|");
283    }
284}