paft_domain/
exchange.rs

1//! Exchange enumeration with major exchanges and extensible fallback.
2//!
3//! This module provides type-safe handling of exchange identifiers while gracefully
4//! handling unknown or provider-specific exchanges through the `Other` variant.
5
6use crate::error::DomainError;
7// no module-level serde imports needed here
8use paft_utils::Canonical;
9use std::str::FromStr;
10
11/// Exchange enumeration with major exchanges and extensible fallback.
12///
13/// This enum provides type-safe handling of exchange identifiers while gracefully
14/// handling unknown or provider-specific exchanges through the `Other` variant.
15///
16/// Canonical/serde rules:
17/// - Emission uses a single canonical form per variant (UPPERCASE ASCII, no spaces)
18/// - Parser accepts a superset of tokens (aliases, case-insensitive)
19/// - `Other(s)` serializes to its canonical `code()` string (no escape prefix) and must be non-empty
20/// - `Display` output matches the canonical code for known variants and the raw `s` for `Other(s)`
21/// - Serde round-trips preserve identity for canonical variants; unknown tokens normalize to `Other(UPPERCASE)`
22#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
23#[non_exhaustive]
24pub enum Exchange {
25    /// NASDAQ Stock Market
26    #[default]
27    NASDAQ,
28    /// New York Stock Exchange
29    NYSE,
30    /// American Stock Exchange
31    AMEX,
32    /// BATS Global Markets
33    BATS,
34    /// Over-the-Counter Markets
35    OTC,
36    /// London Stock Exchange
37    LSE,
38    /// Tokyo Stock Exchange
39    TSE,
40    /// Hong Kong Stock Exchange
41    HKEX,
42    /// Shanghai Stock Exchange
43    SSE,
44    /// Shenzhen Stock Exchange
45    SZSE,
46    /// Toronto Stock Exchange
47    TSX,
48    /// Australian Securities Exchange
49    ASX,
50    /// Euronext
51    Euronext,
52    /// Deutsche Börse (XETRA)
53    XETRA,
54    /// Swiss Exchange
55    SIX,
56    /// Borsa Italiana
57    BIT,
58    /// Bolsa de Madrid
59    BME,
60    /// Euronext Amsterdam
61    AEX,
62    /// Euronext Brussels
63    BRU,
64    /// Euronext Lisbon
65    LIS,
66    /// Euronext Paris
67    EPA,
68    /// Oslo Børs
69    OSL,
70    /// Stockholm Stock Exchange
71    STO,
72    /// Copenhagen Stock Exchange
73    CPH,
74    /// Warsaw Stock Exchange
75    WSE,
76    /// Prague Stock Exchange
77    #[allow(non_camel_case_types)]
78    PSE_CZ,
79    /// Budapest Stock Exchange
80    #[allow(non_camel_case_types)]
81    BSE_HU,
82    /// Moscow Exchange
83    MOEX,
84    /// Istanbul Stock Exchange
85    BIST,
86    /// Johannesburg Stock Exchange
87    JSE,
88    /// Tel Aviv Stock Exchange
89    TASE,
90    /// Bombay Stock Exchange
91    BSE,
92    /// National Stock Exchange of India
93    NSE,
94    /// Korea Exchange
95    KRX,
96    /// Singapore Exchange
97    SGX,
98    /// Thailand Stock Exchange
99    SET,
100    /// Bursa Malaysia
101    KLSE,
102    /// Philippine Stock Exchange
103    PSE,
104    /// Indonesia Stock Exchange
105    IDX,
106    /// Ho Chi Minh Stock Exchange
107    HOSE,
108    /// Unknown or provider-specific exchange
109    Other(Canonical),
110}
111
112impl Exchange {
113    /// Attempts to parse an exchange identifier.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if `input` is empty or contains only whitespace.
118    #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", err))]
119    pub fn try_from_str(input: &str) -> Result<Self, DomainError> {
120        let trimmed = input.trim();
121        if trimmed.is_empty() {
122            return Err(DomainError::InvalidExchangeValue {
123                value: input.to_string(),
124            });
125        }
126
127        Self::from_str(trimmed).map_err(|_| DomainError::InvalidExchangeValue {
128            value: input.to_string(),
129        })
130    }
131
132    /// Returns true if this is a major US exchange
133    #[must_use]
134    pub const fn is_us_exchange(&self) -> bool {
135        matches!(
136            self,
137            Self::NASDAQ | Self::NYSE | Self::AMEX | Self::BATS | Self::OTC
138        )
139    }
140
141    /// Returns true if this is a European exchange
142    #[must_use]
143    pub const fn is_european_exchange(&self) -> bool {
144        matches!(
145            self,
146            Self::LSE
147                | Self::Euronext
148                | Self::XETRA
149                | Self::SIX
150                | Self::BIT
151                | Self::BME
152                | Self::AEX
153                | Self::BRU
154                | Self::LIS
155                | Self::EPA
156                | Self::OSL
157                | Self::STO
158                | Self::CPH
159                | Self::WSE
160                | Self::PSE_CZ
161                | Self::BSE_HU
162        )
163    }
164
165    /// Returns the human-readable name for this exchange.
166    #[must_use]
167    pub fn full_name(&self) -> &str {
168        match self {
169            Self::NASDAQ => "Nasdaq",
170            Self::NYSE => "NYSE",
171            Self::AMEX => "AMEX",
172            Self::BATS => "BATS",
173            Self::OTC => "OTC",
174            Self::LSE => "London Stock Exchange",
175            Self::TSE => "Tokyo Stock Exchange",
176            Self::HKEX => "Hong Kong Stock Exchange",
177            Self::SSE => "Shanghai Stock Exchange",
178            Self::SZSE => "Shenzhen Stock Exchange",
179            Self::TSX => "Toronto Stock Exchange",
180            Self::ASX => "Australian Securities Exchange",
181            Self::Euronext => "Euronext",
182            Self::XETRA => "Xetra",
183            Self::SIX => "Swiss Exchange",
184            Self::BIT => "Borsa Italiana",
185            Self::BME => "Bolsa de Madrid",
186            Self::AEX => "Euronext Amsterdam",
187            Self::BRU => "Euronext Brussels",
188            Self::LIS => "Euronext Lisbon",
189            Self::EPA => "Euronext Paris",
190            Self::OSL => "Oslo Børs",
191            Self::STO => "Stockholm Stock Exchange",
192            Self::CPH => "Copenhagen Stock Exchange",
193            Self::WSE => "Warsaw Stock Exchange",
194            Self::PSE_CZ => "Prague Stock Exchange",
195            Self::BSE_HU => "Budapest Stock Exchange",
196            Self::MOEX => "Moscow Exchange",
197            Self::BIST => "Istanbul Stock Exchange",
198            Self::JSE => "Johannesburg Stock Exchange",
199            Self::TASE => "Tel Aviv Stock Exchange",
200            Self::BSE => "Bombay Stock Exchange",
201            Self::NSE => "National Stock Exchange of India",
202            Self::KRX => "Korea Exchange",
203            Self::SGX => "Singapore Exchange",
204            Self::SET => "Stock Exchange of Thailand",
205            Self::KLSE => "Bursa Malaysia",
206            Self::PSE => "Philippine Stock Exchange",
207            Self::IDX => "Indonesia Stock Exchange",
208            Self::HOSE => "Ho Chi Minh Stock Exchange",
209            Self::Other(code) => code.as_ref(),
210        }
211    }
212}
213
214// Implement code() and string impls via macro (open enum)
215crate::string_enum_with_code!(
216    Exchange, Other, "Exchange",
217    {
218        "NASDAQ" => Exchange::NASDAQ,
219        "NYSE" => Exchange::NYSE,
220        "AMEX" => Exchange::AMEX,
221        "BATS" => Exchange::BATS,
222        "OTC" => Exchange::OTC,
223        "LSE" => Exchange::LSE,
224        "TSE" => Exchange::TSE,
225        "HKEX" => Exchange::HKEX,
226        "SSE" => Exchange::SSE,
227        "SZSE" => Exchange::SZSE,
228        "TSX" => Exchange::TSX,
229        "ASX" => Exchange::ASX,
230        "EURONEXT" => Exchange::Euronext,
231        "XETRA" => Exchange::XETRA,
232        "SIX" => Exchange::SIX,
233        "BIT" => Exchange::BIT,
234        "BME" => Exchange::BME,
235        "AEX" => Exchange::AEX,
236        "BRU" => Exchange::BRU,
237        "LIS" => Exchange::LIS,
238        "EPA" => Exchange::EPA,
239        "OSL" => Exchange::OSL,
240        "STO" => Exchange::STO,
241        "CPH" => Exchange::CPH,
242        "WSE" => Exchange::WSE,
243        "PSE_CZ" => Exchange::PSE_CZ,
244        "BSE_HU" => Exchange::BSE_HU,
245        "MOEX" => Exchange::MOEX,
246        "BIST" => Exchange::BIST,
247        "JSE" => Exchange::JSE,
248        "TASE" => Exchange::TASE,
249        "BSE" => Exchange::BSE,
250        "NSE" => Exchange::NSE,
251        "KRX" => Exchange::KRX,
252        "SGX" => Exchange::SGX,
253        "SET" => Exchange::SET,
254        "KLSE" => Exchange::KLSE,
255        "PSE" => Exchange::PSE,
256        "IDX" => Exchange::IDX,
257        "HOSE" => Exchange::HOSE
258    },
259    {
260        // Provider aliases
261        "EURONEXT_PARIS" => Exchange::EPA,
262        "BOMBAY" => Exchange::BSE,
263        "BSE_INDIA" => Exchange::BSE
264    }
265);
266
267crate::impl_display_via_code!(Exchange);