nautilus_model/
venues.rs

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