Skip to main content

nautilus_model/
venues.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//! Common `Venue` constants.
17
18use std::{
19    collections::HashMap,
20    sync::{LazyLock, OnceLock},
21};
22
23use parking_lot::Mutex;
24
25use crate::identifiers::Venue;
26
27static CBCM_LOCK: OnceLock<Venue> = OnceLock::new();
28static GLBX_LOCK: OnceLock<Venue> = OnceLock::new();
29static NYUM_LOCK: OnceLock<Venue> = OnceLock::new();
30static XCBT_LOCK: OnceLock<Venue> = OnceLock::new();
31static XCEC_LOCK: OnceLock<Venue> = OnceLock::new();
32static XCME_LOCK: OnceLock<Venue> = OnceLock::new();
33static XFXS_LOCK: OnceLock<Venue> = OnceLock::new();
34static XNYM_LOCK: OnceLock<Venue> = OnceLock::new();
35
36impl Venue {
37    /// Returns the CBCM (Chicago Board of Trade) venue.
38    #[allow(non_snake_case)]
39    pub fn CBCM() -> Self {
40        *CBCM_LOCK.get_or_init(|| Self::from("CBCM"))
41    }
42    /// Returns the GLBX (Globex) venue.
43    #[allow(non_snake_case)]
44    pub fn GLBX() -> Self {
45        *GLBX_LOCK.get_or_init(|| Self::from("GLBX"))
46    }
47    /// Returns the NYUM (New York Mercantile Exchange) venue.
48    #[allow(non_snake_case)]
49    pub fn NYUM() -> Self {
50        *NYUM_LOCK.get_or_init(|| Self::from("NYUM"))
51    }
52    /// Returns the XCBT (Chicago Board of Trade) venue.
53    #[allow(non_snake_case)]
54    pub fn XCBT() -> Self {
55        *XCBT_LOCK.get_or_init(|| Self::from("XCBT"))
56    }
57    /// Returns the XCEC (Chicago Mercantile Exchange Center) venue.
58    #[allow(non_snake_case)]
59    pub fn XCEC() -> Self {
60        *XCEC_LOCK.get_or_init(|| Self::from("XCEC"))
61    }
62    /// Returns the XCME (Chicago Mercantile Exchange) venue.
63    #[allow(non_snake_case)]
64    pub fn XCME() -> Self {
65        *XCME_LOCK.get_or_init(|| Self::from("XCME"))
66    }
67    /// Returns the XFXS (CME FX) venue.
68    #[allow(non_snake_case)]
69    pub fn XFXS() -> Self {
70        *XFXS_LOCK.get_or_init(|| Self::from("XFXS"))
71    }
72    /// Returns the XNYM (New York Mercantile Exchange) venue.
73    #[allow(non_snake_case)]
74    pub fn XNYM() -> Self {
75        *XNYM_LOCK.get_or_init(|| Self::from("XNYM"))
76    }
77}
78
79pub static VENUE_MAP: LazyLock<Mutex<HashMap<&str, Venue>>> = LazyLock::new(|| {
80    let mut map = HashMap::new();
81    map.insert(Venue::CBCM().inner().as_str(), Venue::CBCM());
82    map.insert(Venue::GLBX().inner().as_str(), Venue::GLBX());
83    map.insert(Venue::NYUM().inner().as_str(), Venue::NYUM());
84    map.insert(Venue::XCBT().inner().as_str(), Venue::XCBT());
85    map.insert(Venue::XCEC().inner().as_str(), Venue::XCEC());
86    map.insert(Venue::XCME().inner().as_str(), Venue::XCME());
87    map.insert(Venue::XFXS().inner().as_str(), Venue::XFXS());
88    map.insert(Venue::XNYM().inner().as_str(), Venue::XNYM());
89    Mutex::new(map)
90});
91
92#[cfg(test)]
93mod tests {
94    use rstest::*;
95
96    use super::*;
97
98    #[rstest]
99    fn test_venue_constants() {
100        // Test that all venue constants return consistent values
101        let cbcm1 = Venue::CBCM();
102        let cbcm2 = Venue::CBCM();
103        assert_eq!(cbcm1, cbcm2);
104        assert_eq!(cbcm1.inner().as_str(), "CBCM");
105
106        let glbx1 = Venue::GLBX();
107        let glbx2 = Venue::GLBX();
108        assert_eq!(glbx1, glbx2);
109        assert_eq!(glbx1.inner().as_str(), "GLBX");
110
111        let nyum1 = Venue::NYUM();
112        let nyum2 = Venue::NYUM();
113        assert_eq!(nyum1, nyum2);
114        assert_eq!(nyum1.inner().as_str(), "NYUM");
115
116        let xcbt1 = Venue::XCBT();
117        let xcbt2 = Venue::XCBT();
118        assert_eq!(xcbt1, xcbt2);
119        assert_eq!(xcbt1.inner().as_str(), "XCBT");
120
121        let xcec1 = Venue::XCEC();
122        let xcec2 = Venue::XCEC();
123        assert_eq!(xcec1, xcec2);
124        assert_eq!(xcec1.inner().as_str(), "XCEC");
125
126        let xcme1 = Venue::XCME();
127        let xcme2 = Venue::XCME();
128        assert_eq!(xcme1, xcme2);
129        assert_eq!(xcme1.inner().as_str(), "XCME");
130
131        let xfxs1 = Venue::XFXS();
132        let xfxs2 = Venue::XFXS();
133        assert_eq!(xfxs1, xfxs2);
134        assert_eq!(xfxs1.inner().as_str(), "XFXS");
135
136        let xnym1 = Venue::XNYM();
137        let xnym2 = Venue::XNYM();
138        assert_eq!(xnym1, xnym2);
139        assert_eq!(xnym1.inner().as_str(), "XNYM");
140    }
141
142    #[rstest]
143    fn test_venue_constants_uniqueness() {
144        // Test that all venue constants are different from each other
145        let venues = [
146            Venue::CBCM(),
147            Venue::GLBX(),
148            Venue::NYUM(),
149            Venue::XCBT(),
150            Venue::XCEC(),
151            Venue::XCME(),
152            Venue::XFXS(),
153            Venue::XNYM(),
154        ];
155
156        // Check that all venues are unique
157        for (i, venue1) in venues.iter().enumerate() {
158            for (j, venue2) in venues.iter().enumerate() {
159                if i != j {
160                    assert_ne!(
161                        venue1, venue2,
162                        "Venues at indices {i} and {j} should be different"
163                    );
164                }
165            }
166        }
167    }
168
169    #[rstest]
170    fn test_venue_map_contains_all_venues() {
171        let venue_map = VENUE_MAP.lock();
172
173        // Test that all venue constants are in the map
174        assert!(venue_map.contains_key("CBCM"));
175        assert!(venue_map.contains_key("GLBX"));
176        assert!(venue_map.contains_key("NYUM"));
177        assert!(venue_map.contains_key("XCBT"));
178        assert!(venue_map.contains_key("XCEC"));
179        assert!(venue_map.contains_key("XCME"));
180        assert!(venue_map.contains_key("XFXS"));
181        assert!(venue_map.contains_key("XNYM"));
182
183        // Test that the map has exactly 8 entries
184        assert_eq!(venue_map.len(), 8);
185    }
186
187    #[rstest]
188    fn test_venue_map_values_match_constants() {
189        let venue_map = VENUE_MAP.lock();
190
191        // Test that map values match the venue constants
192        assert_eq!(venue_map.get("CBCM").unwrap(), &Venue::CBCM());
193        assert_eq!(venue_map.get("GLBX").unwrap(), &Venue::GLBX());
194        assert_eq!(venue_map.get("NYUM").unwrap(), &Venue::NYUM());
195        assert_eq!(venue_map.get("XCBT").unwrap(), &Venue::XCBT());
196        assert_eq!(venue_map.get("XCEC").unwrap(), &Venue::XCEC());
197        assert_eq!(venue_map.get("XCME").unwrap(), &Venue::XCME());
198        assert_eq!(venue_map.get("XFXS").unwrap(), &Venue::XFXS());
199        assert_eq!(venue_map.get("XNYM").unwrap(), &Venue::XNYM());
200    }
201
202    #[rstest]
203    fn test_venue_map_lookup_nonexistent() {
204        let venue_map = VENUE_MAP.lock();
205
206        // Test that non-existent venues return None
207        assert!(venue_map.get("INVALID").is_none());
208        assert!(venue_map.get("").is_none());
209        assert!(venue_map.get("NYSE").is_none()); // Valid venue but not in our constants
210    }
211
212    #[rstest]
213    fn test_venue_constants_lazy_initialization() {
214        // Test that venue constants work with lazy initialization
215        // This implicitly tests that OnceLock works correctly
216
217        // Multiple calls should return the same instance
218        let cbcm_calls = (0..10).map(|_| Venue::CBCM()).collect::<Vec<_>>();
219        let first_cbcm = cbcm_calls[0];
220
221        for cbcm in cbcm_calls {
222            assert_eq!(cbcm, first_cbcm);
223        }
224    }
225
226    #[rstest]
227    fn test_all_venue_strings() {
228        // Test the string representations of all venues
229        let expected_venues = vec![
230            ("CBCM", Venue::CBCM()),
231            ("GLBX", Venue::GLBX()),
232            ("NYUM", Venue::NYUM()),
233            ("XCBT", Venue::XCBT()),
234            ("XCEC", Venue::XCEC()),
235            ("XCME", Venue::XCME()),
236            ("XFXS", Venue::XFXS()),
237            ("XNYM", Venue::XNYM()),
238        ];
239
240        for (expected_str, venue) in expected_venues {
241            assert_eq!(venue.inner().as_str(), expected_str);
242            assert_eq!(format!("{venue}"), expected_str);
243        }
244    }
245
246    #[rstest]
247    #[expect(clippy::needless_collect)] // Collect needed for thread handles
248    fn test_venue_constants_thread_safety() {
249        use std::thread;
250
251        // Test that venue constants are thread-safe
252        let handles: Vec<_> = (0..4)
253            .map(|_| {
254                thread::spawn(|| {
255                    // Access all venue constants from different threads
256                    let venues = vec![
257                        Venue::CBCM(),
258                        Venue::GLBX(),
259                        Venue::NYUM(),
260                        Venue::XCBT(),
261                        Venue::XCEC(),
262                        Venue::XCME(),
263                        Venue::XFXS(),
264                        Venue::XNYM(),
265                    ];
266                    venues
267                })
268            })
269            .collect();
270
271        let results: Vec<Vec<Venue>> = handles.into_iter().map(|h| h.join().unwrap()).collect();
272
273        // All threads should return the same venue instances
274        for venues in &results {
275            assert_eq!(venues[0], Venue::CBCM());
276            assert_eq!(venues[1], Venue::GLBX());
277            assert_eq!(venues[2], Venue::NYUM());
278            assert_eq!(venues[3], Venue::XCBT());
279            assert_eq!(venues[4], Venue::XCEC());
280            assert_eq!(venues[5], Venue::XCME());
281            assert_eq!(venues[6], Venue::XFXS());
282            assert_eq!(venues[7], Venue::XNYM());
283        }
284    }
285
286    #[rstest]
287    #[expect(clippy::needless_collect)] // Collect needed for thread handles
288    fn test_venue_map_thread_safety() {
289        use std::thread;
290
291        // Test that VENUE_MAP is thread-safe
292        let handles: Vec<_> = (0..4)
293            .map(|_| {
294                thread::spawn(|| {
295                    let venue_map = VENUE_MAP.lock();
296                    venue_map.get("XCME").copied()
297                })
298            })
299            .collect();
300
301        let results: Vec<Option<Venue>> = handles.into_iter().map(|h| h.join().unwrap()).collect();
302
303        // All threads should return the same result
304        for result in results {
305            assert_eq!(result, Some(Venue::XCME()));
306        }
307    }
308}