1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum Region {
7 #[serde(rename = "US")]
8 UnitedStates,
9 #[serde(rename = "NA")]
10 NorthAmerica,
11 #[serde(rename = "SA")]
12 SouthAmerica,
13 #[serde(rename = "EU")]
14 Europe,
15 #[serde(rename = "AS")]
16 Asia,
17 #[serde(rename = "AF")]
18 Africa,
19 #[serde(rename = "ME")]
20 MiddleEast,
21 #[serde(rename = "OCE")]
22 Oceania,
23 #[serde(rename = "global")]
24 Global,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum Index {
30 #[serde(rename = "snp")]
32 Gspc, #[serde(rename = "djia")]
34 Dji, #[serde(rename = "nasdaq")]
36 Ixic, #[serde(rename = "nyse-composite")]
38 Nya, #[serde(rename = "nyse-amex")]
40 Xax, #[serde(rename = "rut")]
42 Rut, #[serde(rename = "vix")]
44 Vix, #[serde(rename = "tsx-composite")]
48 Gsptse, #[serde(rename = "ibovespa")]
52 Bvsp, #[serde(rename = "ipc-mexico")]
54 Mxx, #[serde(rename = "ipsa")]
56 Ipsa, #[serde(rename = "merval")]
58 Merv, #[serde(rename = "ivbx")]
60 Ivbx, #[serde(rename = "ibrx-50")]
62 Ibrx50, #[serde(rename = "ftse-100")]
66 Ftse, #[serde(rename = "dax")]
68 Gdaxi, #[serde(rename = "cac-40")]
70 Fchi, #[serde(rename = "euro-stoxx-50")]
72 Stoxx50e, #[serde(rename = "euronext-100")]
74 N100, #[serde(rename = "bel-20")]
76 Bfx, #[serde(rename = "moex")]
78 MoexMe, #[serde(rename = "aex")]
80 Aex, #[serde(rename = "ibex-35")]
82 Ibex, #[serde(rename = "ftse-mib")]
84 Ftsemib, #[serde(rename = "smi")]
86 Ssmi, #[serde(rename = "psi")]
88 Psi, #[serde(rename = "atx")]
90 Atx, #[serde(rename = "omxs30")]
92 Omxs30, #[serde(rename = "omxc25")]
94 Omxc25, #[serde(rename = "wig20")]
96 Wig20, #[serde(rename = "budapest-se")]
98 Bux, #[serde(rename = "moex-russia")]
100 Imoex, #[serde(rename = "rtsi")]
102 Rtsi, #[serde(rename = "hang-seng")]
106 Hsi, #[serde(rename = "sti")]
108 Sti, #[serde(rename = "sensex")]
110 Bsesn, #[serde(rename = "idx-composite")]
112 Jkse, #[serde(rename = "ftse-bursa")]
114 Klse, #[serde(rename = "kospi")]
116 Ks11, #[serde(rename = "twse")]
118 Twii, #[serde(rename = "nikkei-225")]
120 N225, #[serde(rename = "shanghai")]
122 Shanghai, #[serde(rename = "szse-component")]
124 Szse, #[serde(rename = "set")]
126 Set, #[serde(rename = "nifty-50")]
128 Nsei, #[serde(rename = "nifty-200")]
130 Cnx200, #[serde(rename = "psei-composite")]
132 Psei, #[serde(rename = "china-a50")]
134 ChinaA50, #[serde(rename = "dj-shanghai")]
136 Djsh, #[serde(rename = "india-vix")]
138 Indiavix, #[serde(rename = "egx-30")]
142 Case30, #[serde(rename = "jse-40")]
144 Jn0uJo, #[serde(rename = "ftse-jse")]
146 Ftsejse, #[serde(rename = "afr-40")]
148 Afr40, #[serde(rename = "raf-40")]
150 Raf40, #[serde(rename = "sa-40")]
152 Sa40, #[serde(rename = "alt-15")]
154 Alt15, #[serde(rename = "ta-125")]
158 Ta125Ta, #[serde(rename = "ta-35")]
160 Ta35, #[serde(rename = "tadawul-all-share")]
162 Tasi, #[serde(rename = "tamayuz")]
164 Tamayuz, #[serde(rename = "bist-100")]
166 Bist100, #[serde(rename = "asx-200")]
170 Axjo, #[serde(rename = "all-ordinaries")]
172 Aord, #[serde(rename = "nzx-50")]
174 Nz50, #[serde(rename = "usd")]
178 DxYNyb, #[serde(rename = "msci-europe")]
180 UsdStrd, #[serde(rename = "gbp")]
182 Xdb, #[serde(rename = "euro")]
184 Xde, #[serde(rename = "yen")]
186 Xdn, #[serde(rename = "australian")]
188 Xda, #[serde(rename = "msci-world")]
190 MsciWorld, #[serde(rename = "cboe-uk-100")]
192 Buk100p, }
194
195impl Index {
196 pub fn as_str(&self) -> &'static str {
197 match self {
198 Index::Gspc => "snp",
199 Index::Dji => "djia",
200 Index::Ixic => "nasdaq",
201 Index::Nya => "nyse-composite",
202 Index::Xax => "nyse-amex",
203 Index::Rut => "rut",
204 Index::Vix => "vix",
205 Index::Gsptse => "tsx-composite",
206 Index::Bvsp => "ibovespa",
207 Index::Mxx => "ipc-mexico",
208 Index::Ipsa => "ipsa",
209 Index::Merv => "merval",
210 Index::Ivbx => "ivbx",
211 Index::Ibrx50 => "ibrx-50",
212 Index::Ftse => "ftse-100",
213 Index::Gdaxi => "dax",
214 Index::Fchi => "cac-40",
215 Index::Stoxx50e => "euro-stoxx-50",
216 Index::N100 => "euronext-100",
217 Index::Bfx => "bel-20",
218 Index::MoexMe => "moex",
219 Index::Aex => "aex",
220 Index::Ibex => "ibex-35",
221 Index::Ftsemib => "ftse-mib",
222 Index::Ssmi => "smi",
223 Index::Psi => "psi",
224 Index::Atx => "atx",
225 Index::Omxs30 => "omxs30",
226 Index::Omxc25 => "omxc25",
227 Index::Wig20 => "wig20",
228 Index::Bux => "budapest-se",
229 Index::Imoex => "moex-russia",
230 Index::Rtsi => "rtsi",
231 Index::Hsi => "hang-seng",
232 Index::Sti => "sti",
233 Index::Bsesn => "sensex",
234 Index::Jkse => "idx-composite",
235 Index::Klse => "ftse-bursa",
236 Index::Ks11 => "kospi",
237 Index::Twii => "twse",
238 Index::N225 => "nikkei-225",
239 Index::Shanghai => "shanghai",
240 Index::Szse => "szse-component",
241 Index::Set => "set",
242 Index::Nsei => "nifty-50",
243 Index::Cnx200 => "nifty-200",
244 Index::Psei => "psei-composite",
245 Index::ChinaA50 => "china-a50",
246 Index::Djsh => "dj-shanghai",
247 Index::Indiavix => "india-vix",
248 Index::Case30 => "egx-30",
249 Index::Jn0uJo => "jse-40",
250 Index::Ftsejse => "ftse-jse",
251 Index::Afr40 => "afr-40",
252 Index::Raf40 => "raf-40",
253 Index::Sa40 => "sa-40",
254 Index::Alt15 => "alt-15",
255 Index::Ta125Ta => "ta-125",
256 Index::Ta35 => "ta-35",
257 Index::Tasi => "tadawul-all-share",
258 Index::Tamayuz => "tamayuz",
259 Index::Bist100 => "bist-100",
260 Index::Axjo => "asx-200",
261 Index::Aord => "all-ordinaries",
262 Index::Nz50 => "nzx-50",
263 Index::DxYNyb => "usd",
264 Index::UsdStrd => "msci-europe",
265 Index::Xdb => "gbp",
266 Index::Xde => "euro",
267 Index::Xdn => "yen",
268 Index::Xda => "australian",
269 Index::MsciWorld => "msci-world",
270 Index::Buk100p => "cboe-uk-100",
271 }
272 }
273
274 pub fn parse(s: &str) -> Option<Self> {
275 match s {
276 "snp" => Some(Index::Gspc),
277 "djia" => Some(Index::Dji),
278 "nasdaq" => Some(Index::Ixic),
279 "nyse-composite" => Some(Index::Nya),
280 "nyse-amex" => Some(Index::Xax),
281 "rut" => Some(Index::Rut),
282 "vix" => Some(Index::Vix),
283 "tsx-composite" => Some(Index::Gsptse),
284 "ibovespa" => Some(Index::Bvsp),
285 "ipc-mexico" => Some(Index::Mxx),
286 "ipsa" => Some(Index::Ipsa),
287 "merval" => Some(Index::Merv),
288 "ivbx" => Some(Index::Ivbx),
289 "ibrx-50" => Some(Index::Ibrx50),
290 "ftse-100" => Some(Index::Ftse),
291 "dax" => Some(Index::Gdaxi),
292 "cac-40" => Some(Index::Fchi),
293 "euro-stoxx-50" => Some(Index::Stoxx50e),
294 "euronext-100" => Some(Index::N100),
295 "bel-20" => Some(Index::Bfx),
296 "moex" => Some(Index::MoexMe),
297 "aex" => Some(Index::Aex),
298 "ibex-35" => Some(Index::Ibex),
299 "ftse-mib" => Some(Index::Ftsemib),
300 "smi" => Some(Index::Ssmi),
301 "psi" => Some(Index::Psi),
302 "atx" => Some(Index::Atx),
303 "omxs30" => Some(Index::Omxs30),
304 "omxc25" => Some(Index::Omxc25),
305 "wig20" => Some(Index::Wig20),
306 "budapest-se" => Some(Index::Bux),
307 "moex-russia" => Some(Index::Imoex),
308 "rtsi" => Some(Index::Rtsi),
309 "hang-seng" => Some(Index::Hsi),
310 "sti" => Some(Index::Sti),
311 "sensex" => Some(Index::Bsesn),
312 "idx-composite" => Some(Index::Jkse),
313 "ftse-bursa" => Some(Index::Klse),
314 "kospi" => Some(Index::Ks11),
315 "twse" => Some(Index::Twii),
316 "nikkei-225" => Some(Index::N225),
317 "shanghai" => Some(Index::Shanghai),
318 "szse-component" => Some(Index::Szse),
319 "set" => Some(Index::Set),
320 "nifty-50" => Some(Index::Nsei),
321 "nifty-200" => Some(Index::Cnx200),
322 "psei-composite" => Some(Index::Psei),
323 "china-a50" => Some(Index::ChinaA50),
324 "dj-shanghai" => Some(Index::Djsh),
325 "india-vix" => Some(Index::Indiavix),
326 "egx-30" => Some(Index::Case30),
327 "jse-40" => Some(Index::Jn0uJo),
328 "ftse-jse" => Some(Index::Ftsejse),
329 "afr-40" => Some(Index::Afr40),
330 "raf-40" => Some(Index::Raf40),
331 "sa-40" => Some(Index::Sa40),
332 "alt-15" => Some(Index::Alt15),
333 "ta-125" => Some(Index::Ta125Ta),
334 "ta-35" => Some(Index::Ta35),
335 "tadawul-all-share" => Some(Index::Tasi),
336 "tamayuz" => Some(Index::Tamayuz),
337 "bist-100" => Some(Index::Bist100),
338 "asx-200" => Some(Index::Axjo),
339 "all-ordinaries" => Some(Index::Aord),
340 "nzx-50" => Some(Index::Nz50),
341 "usd" => Some(Index::DxYNyb),
342 "msci-europe" => Some(Index::UsdStrd),
343 "gbp" => Some(Index::Xdb),
344 "euro" => Some(Index::Xde),
345 "yen" => Some(Index::Xdn),
346 "australian" => Some(Index::Xda),
347 "msci-world" => Some(Index::MsciWorld),
348 "cboe-uk-100" => Some(Index::Buk100p),
349 _ => None,
350 }
351 }
352
353 pub fn all() -> Vec<Index> {
354 vec![
355 Index::Gspc,
356 Index::Dji,
357 Index::Ixic,
358 Index::Nya,
359 Index::Xax,
360 Index::Rut,
361 Index::Vix,
362 Index::Gsptse,
363 Index::Bvsp,
364 Index::Mxx,
365 Index::Ipsa,
366 Index::Merv,
367 Index::Ivbx,
368 Index::Ibrx50,
369 Index::Ftse,
370 Index::Gdaxi,
371 Index::Fchi,
372 Index::Stoxx50e,
373 Index::N100,
374 Index::Bfx,
375 Index::MoexMe,
376 Index::Aex,
377 Index::Ibex,
378 Index::Ftsemib,
379 Index::Ssmi,
380 Index::Psi,
381 Index::Atx,
382 Index::Omxs30,
383 Index::Omxc25,
384 Index::Wig20,
385 Index::Bux,
386 Index::Imoex,
387 Index::Rtsi,
388 Index::Hsi,
389 Index::Sti,
390 Index::Bsesn,
391 Index::Jkse,
392 Index::Klse,
393 Index::Ks11,
394 Index::Twii,
395 Index::N225,
396 Index::Shanghai,
397 Index::Szse,
398 Index::Set,
399 Index::Nsei,
400 Index::Cnx200,
401 Index::Psei,
402 Index::ChinaA50,
403 Index::Djsh,
404 Index::Indiavix,
405 Index::Case30,
406 Index::Jn0uJo,
407 Index::Ftsejse,
408 Index::Afr40,
409 Index::Raf40,
410 Index::Sa40,
411 Index::Alt15,
412 Index::Ta125Ta,
413 Index::Ta35,
414 Index::Tasi,
415 Index::Tamayuz,
416 Index::Bist100,
417 Index::Axjo,
418 Index::Aord,
419 Index::Nz50,
420 Index::DxYNyb,
421 Index::UsdStrd,
422 Index::Xdb,
423 Index::Xde,
424 Index::Xdn,
425 Index::Xda,
426 Index::MsciWorld,
427 Index::Buk100p,
428 ]
429 }
430}
431
432pub fn get_index_regions() -> HashMap<Index, Region> {
433 let mut map = HashMap::new();
434 map.insert(Index::Gspc, Region::UnitedStates);
436 map.insert(Index::Dji, Region::UnitedStates);
437 map.insert(Index::Ixic, Region::UnitedStates);
438 map.insert(Index::Nya, Region::UnitedStates);
439 map.insert(Index::Xax, Region::UnitedStates);
440 map.insert(Index::Rut, Region::UnitedStates);
441 map.insert(Index::Vix, Region::UnitedStates);
442 map.insert(Index::Gsptse, Region::NorthAmerica);
444 map.insert(Index::Bvsp, Region::SouthAmerica);
446 map.insert(Index::Mxx, Region::SouthAmerica);
447 map.insert(Index::Ipsa, Region::SouthAmerica);
448 map.insert(Index::Merv, Region::SouthAmerica);
449 map.insert(Index::Ivbx, Region::SouthAmerica);
450 map.insert(Index::Ibrx50, Region::SouthAmerica);
451 map.insert(Index::Ftse, Region::Europe);
453 map.insert(Index::Gdaxi, Region::Europe);
454 map.insert(Index::Fchi, Region::Europe);
455 map.insert(Index::Stoxx50e, Region::Europe);
456 map.insert(Index::N100, Region::Europe);
457 map.insert(Index::Bfx, Region::Europe);
458 map.insert(Index::MoexMe, Region::Europe);
459 map.insert(Index::Aex, Region::Europe);
460 map.insert(Index::Ibex, Region::Europe);
461 map.insert(Index::Ftsemib, Region::Europe);
462 map.insert(Index::Ssmi, Region::Europe);
463 map.insert(Index::Psi, Region::Europe);
464 map.insert(Index::Atx, Region::Europe);
465 map.insert(Index::Omxs30, Region::Europe);
466 map.insert(Index::Omxc25, Region::Europe);
467 map.insert(Index::Wig20, Region::Europe);
468 map.insert(Index::Bux, Region::Europe);
469 map.insert(Index::Imoex, Region::Europe);
470 map.insert(Index::Rtsi, Region::Europe);
471 map.insert(Index::Hsi, Region::Asia);
473 map.insert(Index::Sti, Region::Asia);
474 map.insert(Index::Bsesn, Region::Asia);
475 map.insert(Index::Jkse, Region::Asia);
476 map.insert(Index::Klse, Region::Asia);
477 map.insert(Index::Ks11, Region::Asia);
478 map.insert(Index::Twii, Region::Asia);
479 map.insert(Index::N225, Region::Asia);
480 map.insert(Index::Shanghai, Region::Asia);
481 map.insert(Index::Szse, Region::Asia);
482 map.insert(Index::Set, Region::Asia);
483 map.insert(Index::Nsei, Region::Asia);
484 map.insert(Index::Cnx200, Region::Asia);
485 map.insert(Index::Psei, Region::Asia);
486 map.insert(Index::ChinaA50, Region::Asia);
487 map.insert(Index::Djsh, Region::Asia);
488 map.insert(Index::Indiavix, Region::Asia);
489 map.insert(Index::Case30, Region::Africa);
491 map.insert(Index::Jn0uJo, Region::Africa);
492 map.insert(Index::Ftsejse, Region::Africa);
493 map.insert(Index::Afr40, Region::Africa);
494 map.insert(Index::Sa40, Region::Africa);
495 map.insert(Index::Raf40, Region::Africa);
496 map.insert(Index::Alt15, Region::Africa);
497 map.insert(Index::Ta125Ta, Region::MiddleEast);
499 map.insert(Index::Ta35, Region::MiddleEast);
500 map.insert(Index::Tasi, Region::MiddleEast);
501 map.insert(Index::Tamayuz, Region::MiddleEast);
502 map.insert(Index::Bist100, Region::MiddleEast);
503 map.insert(Index::Axjo, Region::Oceania);
505 map.insert(Index::Aord, Region::Oceania);
506 map.insert(Index::Nz50, Region::Oceania);
507 map.insert(Index::DxYNyb, Region::Global);
509 map.insert(Index::UsdStrd, Region::Global);
510 map.insert(Index::Xdb, Region::Global);
511 map.insert(Index::Xde, Region::Global);
512 map.insert(Index::Xdn, Region::Global);
513 map.insert(Index::Xda, Region::Global);
514 map.insert(Index::MsciWorld, Region::Global);
515 map.insert(Index::Buk100p, Region::Global);
516 map
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
520#[serde(rename_all = "snake_case")]
521pub struct MarketIndex {
522 pub name: String,
523 pub value: f64,
524 pub change: String,
525 #[serde(rename = "percentChange")]
526 pub percent_change: String,
527 #[serde(skip_serializing_if = "Option::is_none", rename = "fiveDaysReturn")]
528 pub five_days_return: Option<String>,
529 #[serde(skip_serializing_if = "Option::is_none", rename = "oneMonthReturn")]
530 pub one_month_return: Option<String>,
531 #[serde(skip_serializing_if = "Option::is_none", rename = "threeMonthReturn")]
532 pub three_month_return: Option<String>,
533 #[serde(skip_serializing_if = "Option::is_none", rename = "sixMonthReturn")]
534 pub six_month_return: Option<String>,
535 #[serde(skip_serializing_if = "Option::is_none", rename = "ytdReturn")]
536 pub ytd_return: Option<String>,
537 #[serde(skip_serializing_if = "Option::is_none", rename = "yearReturn")]
538 pub year_return: Option<String>,
539 #[serde(skip_serializing_if = "Option::is_none", rename = "threeYearReturn")]
540 pub three_year_return: Option<String>,
541 #[serde(skip_serializing_if = "Option::is_none", rename = "fiveYearReturn")]
542 pub five_year_return: Option<String>,
543 #[serde(skip_serializing_if = "Option::is_none", rename = "tenYearReturn")]
544 pub ten_year_return: Option<String>,
545 #[serde(skip_serializing_if = "Option::is_none", rename = "maxReturn")]
546 pub max_return: Option<String>,
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552 use proptest::prelude::*;
553
554 fn optional_string() -> impl Strategy<Value = Option<String>> {
555 proptest::option::of("[A-Za-z0-9 .%-]{0,20}")
556 }
557
558 proptest! {
561 #![proptest_config(ProptestConfig::with_cases(100))]
562
563 #[test]
564 fn market_index_roundtrip(
565 name in "[A-Za-z0-9 ]{1,30}",
566 value in 0.01f64..100000.0f64,
567 change in "-?[0-9]{1,5}\\.[0-9]{2}",
568 percent_change in "-?[0-9]{1,3}\\.[0-9]{2}%",
569 five_days_return in optional_string(),
570 one_month_return in optional_string(),
571 ) {
572 let index = MarketIndex {
573 name: name.clone(),
574 value,
575 change: change.clone(),
576 percent_change: percent_change.clone(),
577 five_days_return: five_days_return.clone(),
578 one_month_return: one_month_return.clone(),
579 three_month_return: None,
580 six_month_return: None,
581 ytd_return: None,
582 year_return: None,
583 three_year_return: None,
584 five_year_return: None,
585 ten_year_return: None,
586 max_return: None,
587 };
588
589 let json = serde_json::to_string(&index).unwrap();
590 let parsed: MarketIndex = serde_json::from_str(&json).unwrap();
591
592 prop_assert_eq!(index.name, parsed.name);
593 prop_assert!((index.value - parsed.value).abs() < 1e-10);
594 prop_assert_eq!(index.change, parsed.change);
595 prop_assert_eq!(index.percent_change, parsed.percent_change);
596 }
597
598 #[test]
599 fn region_roundtrip(region in prop_oneof![
600 Just(Region::UnitedStates),
601 Just(Region::NorthAmerica),
602 Just(Region::SouthAmerica),
603 Just(Region::Europe),
604 Just(Region::Asia),
605 Just(Region::Africa),
606 Just(Region::MiddleEast),
607 Just(Region::Oceania),
608 Just(Region::Global),
609 ]) {
610 let json = serde_json::to_string(®ion).unwrap();
611 let parsed: Region = serde_json::from_str(&json).unwrap();
612
613 prop_assert_eq!(region, parsed);
614 }
615 }
616}