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
79/// A map of built-in `Venue` constants.
80pub static VENUE_MAP: LazyLock<Mutex<HashMap<&str, Venue>>> = LazyLock::new(|| {
81    let mut map = HashMap::new();
82    map.insert(Venue::CBCM().inner().as_str(), Venue::CBCM());
83    map.insert(Venue::GLBX().inner().as_str(), Venue::GLBX());
84    map.insert(Venue::NYUM().inner().as_str(), Venue::NYUM());
85    map.insert(Venue::XCBT().inner().as_str(), Venue::XCBT());
86    map.insert(Venue::XCEC().inner().as_str(), Venue::XCEC());
87    map.insert(Venue::XCME().inner().as_str(), Venue::XCME());
88    map.insert(Venue::XFXS().inner().as_str(), Venue::XFXS());
89    map.insert(Venue::XNYM().inner().as_str(), Venue::XNYM());
90    Mutex::new(map)
91});
92
93#[cfg(test)]
94mod tests {
95    use rstest::*;
96
97    use super::*;
98
99    #[rstest]
100    #[case::cbcm(Venue::CBCM, "CBCM")]
101    #[case::glbx(Venue::GLBX, "GLBX")]
102    #[case::nyum(Venue::NYUM, "NYUM")]
103    #[case::xcbt(Venue::XCBT, "XCBT")]
104    #[case::xcec(Venue::XCEC, "XCEC")]
105    #[case::xcme(Venue::XCME, "XCME")]
106    #[case::xfxs(Venue::XFXS, "XFXS")]
107    #[case::xnym(Venue::XNYM, "XNYM")]
108    fn test_venue_constant(#[case] constructor: fn() -> Venue, #[case] expected: &'static str) {
109        let first = constructor();
110        let second = constructor();
111        let venue_map = VENUE_MAP.lock();
112
113        assert_eq!(first, second);
114        assert_eq!(first.inner(), expected);
115        assert_eq!(first.to_string(), expected);
116        assert_eq!(venue_map.get(expected), Some(&first));
117    }
118
119    #[rstest]
120    fn test_venue_constants_are_unique() {
121        let venues = all_venues();
122
123        for (i, venue) in venues.iter().enumerate() {
124            assert!(!venues[i + 1..].contains(venue), "duplicate venue {venue}");
125        }
126    }
127
128    #[rstest]
129    fn test_venue_map_has_expected_size() {
130        let venue_map = VENUE_MAP.lock();
131
132        assert_eq!(venue_map.len(), 8);
133    }
134
135    #[rstest]
136    #[case("INVALID")]
137    #[case("")]
138    #[case("NYSE")]
139    fn test_venue_map_lookup_returns_none(#[case] value: &str) {
140        let venue_map = VENUE_MAP.lock();
141
142        assert_eq!(venue_map.get(value), None);
143    }
144
145    #[rstest]
146    #[expect(clippy::needless_collect)] // Collect needed for thread handles
147    fn test_venue_constants_thread_safety() {
148        use std::thread;
149
150        let handles: Vec<_> = (0..4).map(|_| thread::spawn(all_venues)).collect();
151
152        let results: Vec<[Venue; 8]> = handles.into_iter().map(|h| h.join().unwrap()).collect();
153
154        for venues in &results {
155            assert_eq!(*venues, all_venues());
156        }
157    }
158
159    #[rstest]
160    #[expect(clippy::needless_collect)] // Collect needed for thread handles
161    fn test_venue_map_thread_safety() {
162        use std::thread;
163
164        let handles: Vec<_> = (0..4)
165            .map(|_| {
166                thread::spawn(|| {
167                    let venue_map = VENUE_MAP.lock();
168                    venue_map.get("XCME").copied()
169                })
170            })
171            .collect();
172
173        let results: Vec<Option<Venue>> = handles.into_iter().map(|h| h.join().unwrap()).collect();
174
175        // All threads should return the same result
176        for result in results {
177            assert_eq!(result, Some(Venue::XCME()));
178        }
179    }
180
181    fn all_venues() -> [Venue; 8] {
182        [
183            Venue::CBCM(),
184            Venue::GLBX(),
185            Venue::NYUM(),
186            Venue::XCBT(),
187            Venue::XCEC(),
188            Venue::XCME(),
189            Venue::XFXS(),
190            Venue::XNYM(),
191        ]
192    }
193}