Skip to main content

oanda_rs/models/
instrument_name.rs

1//! Instrument (symbol) names known to the OANDA v20 API.
2
3use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8macro_rules! instrument_names {
9    ($($(#[$meta:meta])* $variant:ident => $symbol:literal,)+) => {
10        /// The name of an instrument (symbol) in `BASE_QUOTE` format.
11        ///
12        /// All symbols available on OANDA's fxPractice environment are
13        /// represented as dedicated variants; anything else (new listings,
14        /// division-specific CFDs) round-trips through
15        /// [`InstrumentName::Other`], so parsing and deserialization never
16        /// fail. Known strings always normalize to their variant:
17        /// `InstrumentName::from("EUR_USD") == InstrumentName::EurUsd`.
18        #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
19        #[non_exhaustive]
20        pub enum InstrumentName {
21            $($(#[$meta])* $variant,)+
22            /// An instrument not (yet) in the known symbol table.
23            Other(String),
24        }
25
26        impl InstrumentName {
27            /// The canonical wire representation, e.g. `EUR_USD`.
28            pub fn as_str(&self) -> &str {
29                match self {
30                    $(InstrumentName::$variant => $symbol,)+
31                    InstrumentName::Other(s) => s,
32                }
33            }
34
35            /// Every known instrument, in alphabetical order.
36            pub const KNOWN: &'static [InstrumentName] = &[
37                $(InstrumentName::$variant,)+
38            ];
39
40            /// Whether this is a known symbol (i.e. not [`InstrumentName::Other`]).
41            pub fn is_known(&self) -> bool {
42                !matches!(self, InstrumentName::Other(_))
43            }
44        }
45
46        impl FromStr for InstrumentName {
47            type Err = std::convert::Infallible;
48
49            fn from_str(s: &str) -> Result<Self, Self::Err> {
50                Ok(match s {
51                    $($symbol => InstrumentName::$variant,)+
52                    other => InstrumentName::Other(other.to_owned()),
53                })
54            }
55        }
56    };
57}
58
59instrument_names! {
60    /// Australia 200 (`AU200_AUD`, cfd)
61    Au200Aud => "AU200_AUD",
62    /// AUD/CAD (`AUD_CAD`, currency)
63    AudCad => "AUD_CAD",
64    /// AUD/CHF (`AUD_CHF`, currency)
65    AudChf => "AUD_CHF",
66    /// AUD/HKD (`AUD_HKD`, currency)
67    AudHkd => "AUD_HKD",
68    /// AUD/JPY (`AUD_JPY`, currency)
69    AudJpy => "AUD_JPY",
70    /// AUD/NZD (`AUD_NZD`, currency)
71    AudNzd => "AUD_NZD",
72    /// AUD/SGD (`AUD_SGD`, currency)
73    AudSgd => "AUD_SGD",
74    /// AUD/USD (`AUD_USD`, currency)
75    AudUsd => "AUD_USD",
76    /// Brent Crude Oil (`BCO_USD`, cfd)
77    BcoUsd => "BCO_USD",
78    /// CAD/CHF (`CAD_CHF`, currency)
79    CadChf => "CAD_CHF",
80    /// CAD/HKD (`CAD_HKD`, currency)
81    CadHkd => "CAD_HKD",
82    /// CAD/JPY (`CAD_JPY`, currency)
83    CadJpy => "CAD_JPY",
84    /// CAD/SGD (`CAD_SGD`, currency)
85    CadSgd => "CAD_SGD",
86    /// Switzerland 20 (`CH20_CHF`, cfd)
87    Ch20Chf => "CH20_CHF",
88    /// CHF/HKD (`CHF_HKD`, currency)
89    ChfHkd => "CHF_HKD",
90    /// CHF/JPY (`CHF_JPY`, currency)
91    ChfJpy => "CHF_JPY",
92    /// CHF/ZAR (`CHF_ZAR`, currency)
93    ChfZar => "CHF_ZAR",
94    /// China H Shares (`CHINAH_HKD`, cfd)
95    ChinahHkd => "CHINAH_HKD",
96    /// China A50 (`CN50_USD`, cfd)
97    Cn50Usd => "CN50_USD",
98    /// Corn (`CORN_USD`, cfd)
99    CornUsd => "CORN_USD",
100    /// Bund (`DE10YB_EUR`, cfd)
101    De10YbEur => "DE10YB_EUR",
102    /// Germany 30 (`DE30_EUR`, cfd)
103    De30Eur => "DE30_EUR",
104    /// Spain 35 (`ESPIX_EUR`, cfd)
105    EspixEur => "ESPIX_EUR",
106    /// Europe 50 (`EU50_EUR`, cfd)
107    Eu50Eur => "EU50_EUR",
108    /// EUR/AUD (`EUR_AUD`, currency)
109    EurAud => "EUR_AUD",
110    /// EUR/CAD (`EUR_CAD`, currency)
111    EurCad => "EUR_CAD",
112    /// EUR/CHF (`EUR_CHF`, currency)
113    EurChf => "EUR_CHF",
114    /// EUR/CZK (`EUR_CZK`, currency)
115    EurCzk => "EUR_CZK",
116    /// EUR/DKK (`EUR_DKK`, currency)
117    EurDkk => "EUR_DKK",
118    /// EUR/GBP (`EUR_GBP`, currency)
119    EurGbp => "EUR_GBP",
120    /// EUR/HKD (`EUR_HKD`, currency)
121    EurHkd => "EUR_HKD",
122    /// EUR/HUF (`EUR_HUF`, currency)
123    EurHuf => "EUR_HUF",
124    /// EUR/JPY (`EUR_JPY`, currency)
125    EurJpy => "EUR_JPY",
126    /// EUR/NOK (`EUR_NOK`, currency)
127    EurNok => "EUR_NOK",
128    /// EUR/NZD (`EUR_NZD`, currency)
129    EurNzd => "EUR_NZD",
130    /// EUR/PLN (`EUR_PLN`, currency)
131    EurPln => "EUR_PLN",
132    /// EUR/SEK (`EUR_SEK`, currency)
133    EurSek => "EUR_SEK",
134    /// EUR/SGD (`EUR_SGD`, currency)
135    EurSgd => "EUR_SGD",
136    /// EUR/TRY (`EUR_TRY`, currency)
137    EurTry => "EUR_TRY",
138    /// EUR/USD (`EUR_USD`, currency)
139    EurUsd => "EUR_USD",
140    /// EUR/ZAR (`EUR_ZAR`, currency)
141    EurZar => "EUR_ZAR",
142    /// France 40 (`FR40_EUR`, cfd)
143    Fr40Eur => "FR40_EUR",
144    /// GBP/AUD (`GBP_AUD`, currency)
145    GbpAud => "GBP_AUD",
146    /// GBP/CAD (`GBP_CAD`, currency)
147    GbpCad => "GBP_CAD",
148    /// GBP/CHF (`GBP_CHF`, currency)
149    GbpChf => "GBP_CHF",
150    /// GBP/HKD (`GBP_HKD`, currency)
151    GbpHkd => "GBP_HKD",
152    /// GBP/JPY (`GBP_JPY`, currency)
153    GbpJpy => "GBP_JPY",
154    /// GBP/NZD (`GBP_NZD`, currency)
155    GbpNzd => "GBP_NZD",
156    /// GBP/PLN (`GBP_PLN`, currency)
157    GbpPln => "GBP_PLN",
158    /// GBP/SGD (`GBP_SGD`, currency)
159    GbpSgd => "GBP_SGD",
160    /// GBP/USD (`GBP_USD`, currency)
161    GbpUsd => "GBP_USD",
162    /// GBP/ZAR (`GBP_ZAR`, currency)
163    GbpZar => "GBP_ZAR",
164    /// Hong Kong 33 (`HK33_HKD`, cfd)
165    Hk33Hkd => "HK33_HKD",
166    /// HKD/JPY (`HKD_JPY`, currency)
167    HkdJpy => "HKD_JPY",
168    /// Japan 225 (JPY) (`JP225Y_JPY`, cfd)
169    Jp225YJpy => "JP225Y_JPY",
170    /// Japan 225 (`JP225_USD`, cfd)
171    Jp225Usd => "JP225_USD",
172    /// US Nas 100 (`NAS100_USD`, cfd)
173    Nas100Usd => "NAS100_USD",
174    /// Natural Gas (`NATGAS_USD`, cfd)
175    NatgasUsd => "NATGAS_USD",
176    /// Netherlands 25 (`NL25_EUR`, cfd)
177    Nl25Eur => "NL25_EUR",
178    /// NZD/CAD (`NZD_CAD`, currency)
179    NzdCad => "NZD_CAD",
180    /// NZD/CHF (`NZD_CHF`, currency)
181    NzdChf => "NZD_CHF",
182    /// NZD/HKD (`NZD_HKD`, currency)
183    NzdHkd => "NZD_HKD",
184    /// NZD/JPY (`NZD_JPY`, currency)
185    NzdJpy => "NZD_JPY",
186    /// NZD/SGD (`NZD_SGD`, currency)
187    NzdSgd => "NZD_SGD",
188    /// NZD/USD (`NZD_USD`, currency)
189    NzdUsd => "NZD_USD",
190    /// Singapore 30 (`SG30_SGD`, cfd)
191    Sg30Sgd => "SG30_SGD",
192    /// SGD/CHF (`SGD_CHF`, currency)
193    SgdChf => "SGD_CHF",
194    /// SGD/JPY (`SGD_JPY`, currency)
195    SgdJpy => "SGD_JPY",
196    /// Soybeans (`SOYBN_USD`, cfd)
197    SoybnUsd => "SOYBN_USD",
198    /// US SPX 500 (`SPX500_USD`, cfd)
199    Spx500Usd => "SPX500_USD",
200    /// Sugar (`SUGAR_USD`, cfd)
201    SugarUsd => "SUGAR_USD",
202    /// TRY/JPY (`TRY_JPY`, currency)
203    TryJpy => "TRY_JPY",
204    /// UK 100 (`UK100_GBP`, cfd)
205    Uk100Gbp => "UK100_GBP",
206    /// UK 10Y Gilt (`UK10YB_GBP`, cfd)
207    Uk10YbGbp => "UK10YB_GBP",
208    /// US Russ 2000 (`US2000_USD`, cfd)
209    Us2000Usd => "US2000_USD",
210    /// US Wall St 30 (`US30_USD`, cfd)
211    Us30Usd => "US30_USD",
212    /// US 2Y T-Note (`USB02Y_USD`, cfd)
213    Usb02YUsd => "USB02Y_USD",
214    /// US 5Y T-Note (`USB05Y_USD`, cfd)
215    Usb05YUsd => "USB05Y_USD",
216    /// US 10Y T-Note (`USB10Y_USD`, cfd)
217    Usb10YUsd => "USB10Y_USD",
218    /// US T-Bond (`USB30Y_USD`, cfd)
219    Usb30YUsd => "USB30Y_USD",
220    /// USD/CAD (`USD_CAD`, currency)
221    UsdCad => "USD_CAD",
222    /// USD/CHF (`USD_CHF`, currency)
223    UsdChf => "USD_CHF",
224    /// USD/CNH (`USD_CNH`, currency)
225    UsdCnh => "USD_CNH",
226    /// USD/CZK (`USD_CZK`, currency)
227    UsdCzk => "USD_CZK",
228    /// USD/DKK (`USD_DKK`, currency)
229    UsdDkk => "USD_DKK",
230    /// USD/HKD (`USD_HKD`, currency)
231    UsdHkd => "USD_HKD",
232    /// USD/HUF (`USD_HUF`, currency)
233    UsdHuf => "USD_HUF",
234    /// USD/JPY (`USD_JPY`, currency)
235    UsdJpy => "USD_JPY",
236    /// USD/MXN (`USD_MXN`, currency)
237    UsdMxn => "USD_MXN",
238    /// USD/NOK (`USD_NOK`, currency)
239    UsdNok => "USD_NOK",
240    /// USD/PLN (`USD_PLN`, currency)
241    UsdPln => "USD_PLN",
242    /// USD/SEK (`USD_SEK`, currency)
243    UsdSek => "USD_SEK",
244    /// USD/SGD (`USD_SGD`, currency)
245    UsdSgd => "USD_SGD",
246    /// USD/THB (`USD_THB`, currency)
247    UsdThb => "USD_THB",
248    /// USD/TRY (`USD_TRY`, currency)
249    UsdTry => "USD_TRY",
250    /// USD/ZAR (`USD_ZAR`, currency)
251    UsdZar => "USD_ZAR",
252    /// Wheat (`WHEAT_USD`, cfd)
253    WheatUsd => "WHEAT_USD",
254    /// West Texas Oil (`WTICO_USD`, cfd)
255    WticoUsd => "WTICO_USD",
256    /// Silver/AUD (`XAG_AUD`, metal)
257    XagAud => "XAG_AUD",
258    /// Silver/CAD (`XAG_CAD`, metal)
259    XagCad => "XAG_CAD",
260    /// Silver/CHF (`XAG_CHF`, metal)
261    XagChf => "XAG_CHF",
262    /// Silver/EUR (`XAG_EUR`, metal)
263    XagEur => "XAG_EUR",
264    /// Silver/GBP (`XAG_GBP`, metal)
265    XagGbp => "XAG_GBP",
266    /// Silver/HKD (`XAG_HKD`, metal)
267    XagHkd => "XAG_HKD",
268    /// Silver/JPY (`XAG_JPY`, metal)
269    XagJpy => "XAG_JPY",
270    /// Silver/NZD (`XAG_NZD`, metal)
271    XagNzd => "XAG_NZD",
272    /// Silver/SGD (`XAG_SGD`, metal)
273    XagSgd => "XAG_SGD",
274    /// Silver (`XAG_USD`, metal)
275    XagUsd => "XAG_USD",
276    /// Gold/AUD (`XAU_AUD`, metal)
277    XauAud => "XAU_AUD",
278    /// Gold/CAD (`XAU_CAD`, metal)
279    XauCad => "XAU_CAD",
280    /// Gold/CHF (`XAU_CHF`, metal)
281    XauChf => "XAU_CHF",
282    /// Gold/EUR (`XAU_EUR`, metal)
283    XauEur => "XAU_EUR",
284    /// Gold/GBP (`XAU_GBP`, metal)
285    XauGbp => "XAU_GBP",
286    /// Gold/HKD (`XAU_HKD`, metal)
287    XauHkd => "XAU_HKD",
288    /// Gold/JPY (`XAU_JPY`, metal)
289    XauJpy => "XAU_JPY",
290    /// Gold/NZD (`XAU_NZD`, metal)
291    XauNzd => "XAU_NZD",
292    /// Gold/SGD (`XAU_SGD`, metal)
293    XauSgd => "XAU_SGD",
294    /// Gold (`XAU_USD`, metal)
295    XauUsd => "XAU_USD",
296    /// Gold/Silver (`XAU_XAG`, metal)
297    XauXag => "XAU_XAG",
298    /// Copper (`XCU_USD`, cfd)
299    XcuUsd => "XCU_USD",
300    /// Palladium (`XPD_USD`, cfd)
301    XpdUsd => "XPD_USD",
302    /// Platinum (`XPT_USD`, cfd)
303    XptUsd => "XPT_USD",
304    /// ZAR/JPY (`ZAR_JPY`, currency)
305    ZarJpy => "ZAR_JPY",
306}
307
308impl fmt::Display for InstrumentName {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        f.write_str(self.as_str())
311    }
312}
313
314impl From<&str> for InstrumentName {
315    fn from(value: &str) -> Self {
316        value.parse().expect("infallible")
317    }
318}
319
320impl From<String> for InstrumentName {
321    fn from(value: String) -> Self {
322        value.as_str().into()
323    }
324}
325
326impl Serialize for InstrumentName {
327    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
328        serializer.serialize_str(self.as_str())
329    }
330}
331
332impl<'de> Deserialize<'de> for InstrumentName {
333    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
334        let s = String::deserialize(deserializer)?;
335        Ok(s.into())
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn known_symbols_normalize_to_variants() {
345        assert_eq!(InstrumentName::from("EUR_USD"), InstrumentName::EurUsd);
346        assert_eq!(InstrumentName::from("XAU_USD"), InstrumentName::XauUsd);
347        assert_eq!(
348            InstrumentName::from("SPX500_USD"),
349            InstrumentName::Spx500Usd
350        );
351    }
352
353    #[test]
354    fn unknown_symbols_fall_back_to_other() {
355        let i = InstrumentName::from("FOO_BAR");
356        assert_eq!(i, InstrumentName::Other("FOO_BAR".to_owned()));
357        assert!(!i.is_known());
358        assert_eq!(i.as_str(), "FOO_BAR");
359    }
360
361    #[test]
362    fn display_matches_wire_format() {
363        assert_eq!(InstrumentName::EurUsd.to_string(), "EUR_USD");
364        assert_eq!(InstrumentName::Usb02YUsd.to_string(), "USB02Y_USD");
365        assert_eq!(InstrumentName::Jp225YJpy.to_string(), "JP225Y_JPY");
366    }
367
368    #[test]
369    fn serde_roundtrips_as_plain_string() {
370        let json = serde_json::to_string(&InstrumentName::EurUsd).unwrap();
371        assert_eq!(json, r#""EUR_USD""#);
372        let back: InstrumentName = serde_json::from_str(&json).unwrap();
373        assert_eq!(back, InstrumentName::EurUsd);
374        let other: InstrumentName = serde_json::from_str(r#""NEW_SYMBOL""#).unwrap();
375        assert_eq!(other, InstrumentName::Other("NEW_SYMBOL".to_owned()));
376    }
377
378    #[test]
379    fn known_table_is_consistent() {
380        assert_eq!(InstrumentName::KNOWN.len(), 123);
381        for known in InstrumentName::KNOWN {
382            assert!(known.is_known());
383            assert_eq!(&InstrumentName::from(known.as_str()), known);
384        }
385    }
386}