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}