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")]
67 Ftse, #[serde(rename = "dax")]
69 Gdaxi, #[serde(rename = "cac-40")]
71 Fchi, #[serde(rename = "euro-stoxx-50")]
73 Stoxx50e, #[serde(rename = "euronext-100")]
75 N100, #[serde(rename = "bel-20")]
77 Bfx, #[serde(rename = "moex")]
79 MoexMe, #[serde(rename = "aex")]
81 Aex, #[serde(rename = "ibex-35")]
83 Ibex, #[serde(rename = "ftse-mib")]
85 Ftsemib, #[serde(rename = "smi")]
87 Ssmi, #[serde(rename = "psi")]
89 Psi, #[serde(rename = "atx")]
91 Atx, #[serde(rename = "omxs30")]
93 Omxs30, #[serde(rename = "omxc25")]
95 Omxc25, #[serde(rename = "wig20")]
97 Wig20, #[serde(rename = "budapest-se")]
99 Bux, #[serde(rename = "moex-russia")]
101 Imoex, #[serde(rename = "rtsi")]
103 Rtsi, #[serde(rename = "hang-seng")]
107 Hsi, #[serde(rename = "sti")]
109 Sti, #[serde(rename = "sensex")]
111 Bsesn, #[serde(rename = "idx-composite")]
113 Jkse, #[serde(rename = "ftse-bursa")]
115 Klse, #[serde(rename = "kospi")]
117 Ks11, #[serde(rename = "twse")]
119 Twii, #[serde(rename = "nikkei-225")]
121 N225, #[serde(rename = "shanghai")]
123 Shanghai, #[serde(rename = "szse-component")]
125 Szse, #[serde(rename = "set")]
127 Set, #[serde(rename = "nifty-50")]
129 Nsei, #[serde(rename = "nifty-200")]
131 Cnx200, #[serde(rename = "psei-composite")]
133 Psei, #[serde(rename = "china-a50")]
135 ChinaA50, #[serde(rename = "dj-shanghai")]
137 Djsh, #[serde(rename = "india-vix")]
139 Indiavix, #[serde(rename = "egx-30")]
143 Case30, #[serde(rename = "jse-40")]
145 Jn0uJo, #[serde(rename = "ftse-jse")]
147 Ftsejse, #[serde(rename = "afr-40")]
149 Afr40, #[serde(rename = "raf-40")]
151 Raf40, #[serde(rename = "sa-40")]
153 Sa40, #[serde(rename = "alt-15")]
155 Alt15, #[serde(rename = "ta-125")]
159 Ta125Ta, #[serde(rename = "ta-35")]
161 Ta35, #[serde(rename = "tadawul-all-share")]
163 Tasi, #[serde(rename = "tamayuz")]
165 Tamayuz, #[serde(rename = "bist-100")]
167 Bist100, #[serde(rename = "asx-200")]
171 Axjo, #[serde(rename = "all-ordinaries")]
173 Aord, #[serde(rename = "nzx-50")]
175 Nz50, #[serde(rename = "usd")]
179 DxYNyb, #[serde(rename = "msci-europe")]
181 UsdStrd, #[serde(rename = "gbp")]
183 Xdb, #[serde(rename = "euro")]
185 Xde, #[serde(rename = "yen")]
187 Xdn, #[serde(rename = "australian")]
189 Xda, #[serde(rename = "msci-world")]
191 MsciWorld, #[serde(rename = "cboe-uk-100")]
193 Buk100p, }
195
196
197impl Index {
198 pub fn as_str(&self) -> &'static str {
199 match self {
200 Index::Gspc => "snp",
201 Index::Dji => "djia",
202 Index::Ixic => "nasdaq",
203 Index::Nya => "nyse-composite",
204 Index::Xax => "nyse-amex",
205 Index::Rut => "rut",
206 Index::Vix => "vix",
207 Index::Gsptse => "tsx-composite",
208 Index::Bvsp => "ibovespa",
209 Index::Mxx => "ipc-mexico",
210 Index::Ipsa => "ipsa",
211 Index::Merv => "merval",
212 Index::Ivbx => "ivbx",
213 Index::Ibrx50 => "ibrx-50",
214 Index::Ftse => "ftse-100",
215 Index::Gdaxi => "dax",
216 Index::Fchi => "cac-40",
217 Index::Stoxx50e => "euro-stoxx-50",
218 Index::N100 => "euronext-100",
219 Index::Bfx => "bel-20",
220 Index::MoexMe => "moex",
221 Index::Aex => "aex",
222 Index::Ibex => "ibex-35",
223 Index::Ftsemib => "ftse-mib",
224 Index::Ssmi => "smi",
225 Index::Psi => "psi",
226 Index::Atx => "atx",
227 Index::Omxs30 => "omxs30",
228 Index::Omxc25 => "omxc25",
229 Index::Wig20 => "wig20",
230 Index::Bux => "budapest-se",
231 Index::Imoex => "moex-russia",
232 Index::Rtsi => "rtsi",
233 Index::Hsi => "hang-seng",
234 Index::Sti => "sti",
235 Index::Bsesn => "sensex",
236 Index::Jkse => "idx-composite",
237 Index::Klse => "ftse-bursa",
238 Index::Ks11 => "kospi",
239 Index::Twii => "twse",
240 Index::N225 => "nikkei-225",
241 Index::Shanghai => "shanghai",
242 Index::Szse => "szse-component",
243 Index::Set => "set",
244 Index::Nsei => "nifty-50",
245 Index::Cnx200 => "nifty-200",
246 Index::Psei => "psei-composite",
247 Index::ChinaA50 => "china-a50",
248 Index::Djsh => "dj-shanghai",
249 Index::Indiavix => "india-vix",
250 Index::Case30 => "egx-30",
251 Index::Jn0uJo => "jse-40",
252 Index::Ftsejse => "ftse-jse",
253 Index::Afr40 => "afr-40",
254 Index::Raf40 => "raf-40",
255 Index::Sa40 => "sa-40",
256 Index::Alt15 => "alt-15",
257 Index::Ta125Ta => "ta-125",
258 Index::Ta35 => "ta-35",
259 Index::Tasi => "tadawul-all-share",
260 Index::Tamayuz => "tamayuz",
261 Index::Bist100 => "bist-100",
262 Index::Axjo => "asx-200",
263 Index::Aord => "all-ordinaries",
264 Index::Nz50 => "nzx-50",
265 Index::DxYNyb => "usd",
266 Index::UsdStrd => "msci-europe",
267 Index::Xdb => "gbp",
268 Index::Xde => "euro",
269 Index::Xdn => "yen",
270 Index::Xda => "australian",
271 Index::MsciWorld => "msci-world",
272 Index::Buk100p => "cboe-uk-100",
273 }
274 }
275
276
277 pub fn parse(s: &str) -> Option<Self> {
278 match s {
279 "snp" => Some(Index::Gspc),
280 "djia" => Some(Index::Dji),
281 "nasdaq" => Some(Index::Ixic),
282 "nyse-composite" => Some(Index::Nya),
283 "nyse-amex" => Some(Index::Xax),
284 "rut" => Some(Index::Rut),
285 "vix" => Some(Index::Vix),
286 "tsx-composite" => Some(Index::Gsptse),
287 "ibovespa" => Some(Index::Bvsp),
288 "ipc-mexico" => Some(Index::Mxx),
289 "ipsa" => Some(Index::Ipsa),
290 "merval" => Some(Index::Merv),
291 "ivbx" => Some(Index::Ivbx),
292 "ibrx-50" => Some(Index::Ibrx50),
293 "ftse-100" => Some(Index::Ftse),
294 "dax" => Some(Index::Gdaxi),
295 "cac-40" => Some(Index::Fchi),
296 "euro-stoxx-50" => Some(Index::Stoxx50e),
297 "euronext-100" => Some(Index::N100),
298 "bel-20" => Some(Index::Bfx),
299 "moex" => Some(Index::MoexMe),
300 "aex" => Some(Index::Aex),
301 "ibex-35" => Some(Index::Ibex),
302 "ftse-mib" => Some(Index::Ftsemib),
303 "smi" => Some(Index::Ssmi),
304 "psi" => Some(Index::Psi),
305 "atx" => Some(Index::Atx),
306 "omxs30" => Some(Index::Omxs30),
307 "omxc25" => Some(Index::Omxc25),
308 "wig20" => Some(Index::Wig20),
309 "budapest-se" => Some(Index::Bux),
310 "moex-russia" => Some(Index::Imoex),
311 "rtsi" => Some(Index::Rtsi),
312 "hang-seng" => Some(Index::Hsi),
313 "sti" => Some(Index::Sti),
314 "sensex" => Some(Index::Bsesn),
315 "idx-composite" => Some(Index::Jkse),
316 "ftse-bursa" => Some(Index::Klse),
317 "kospi" => Some(Index::Ks11),
318 "twse" => Some(Index::Twii),
319 "nikkei-225" => Some(Index::N225),
320 "shanghai" => Some(Index::Shanghai),
321 "szse-component" => Some(Index::Szse),
322 "set" => Some(Index::Set),
323 "nifty-50" => Some(Index::Nsei),
324 "nifty-200" => Some(Index::Cnx200),
325 "psei-composite" => Some(Index::Psei),
326 "china-a50" => Some(Index::ChinaA50),
327 "dj-shanghai" => Some(Index::Djsh),
328 "india-vix" => Some(Index::Indiavix),
329 "egx-30" => Some(Index::Case30),
330 "jse-40" => Some(Index::Jn0uJo),
331 "ftse-jse" => Some(Index::Ftsejse),
332 "afr-40" => Some(Index::Afr40),
333 "raf-40" => Some(Index::Raf40),
334 "sa-40" => Some(Index::Sa40),
335 "alt-15" => Some(Index::Alt15),
336 "ta-125" => Some(Index::Ta125Ta),
337 "ta-35" => Some(Index::Ta35),
338 "tadawul-all-share" => Some(Index::Tasi),
339 "tamayuz" => Some(Index::Tamayuz),
340 "bist-100" => Some(Index::Bist100),
341 "asx-200" => Some(Index::Axjo),
342 "all-ordinaries" => Some(Index::Aord),
343 "nzx-50" => Some(Index::Nz50),
344 "usd" => Some(Index::DxYNyb),
345 "msci-europe" => Some(Index::UsdStrd),
346 "gbp" => Some(Index::Xdb),
347 "euro" => Some(Index::Xde),
348 "yen" => Some(Index::Xdn),
349 "australian" => Some(Index::Xda),
350 "msci-world" => Some(Index::MsciWorld),
351 "cboe-uk-100" => Some(Index::Buk100p),
352 _ => None,
353 }
354 }
355
356 pub fn all() -> Vec<Index> {
357 vec![
358 Index::Gspc, Index::Dji, Index::Ixic, Index::Nya, Index::Xax, Index::Rut, Index::Vix,
359 Index::Gsptse,
360 Index::Bvsp, Index::Mxx, Index::Ipsa, Index::Merv, Index::Ivbx, Index::Ibrx50,
361 Index::Ftse, Index::Gdaxi, Index::Fchi, Index::Stoxx50e, Index::N100, Index::Bfx,
362 Index::MoexMe, Index::Aex, Index::Ibex, Index::Ftsemib, Index::Ssmi, Index::Psi,
363 Index::Atx, Index::Omxs30, Index::Omxc25, Index::Wig20, Index::Bux, Index::Imoex,
364 Index::Rtsi,
365 Index::Hsi, Index::Sti, Index::Bsesn, Index::Jkse, Index::Klse, Index::Ks11,
366 Index::Twii, Index::N225, Index::Shanghai, Index::Szse, Index::Set, Index::Nsei,
367 Index::Cnx200, Index::Psei, Index::ChinaA50, Index::Djsh, Index::Indiavix,
368 Index::Case30, Index::Jn0uJo, Index::Ftsejse, Index::Afr40, Index::Raf40, Index::Sa40,
369 Index::Alt15,
370 Index::Ta125Ta, Index::Ta35, Index::Tasi, Index::Tamayuz, Index::Bist100,
371 Index::Axjo, Index::Aord, Index::Nz50,
372 Index::DxYNyb, Index::UsdStrd, Index::Xdb, Index::Xde, Index::Xdn, Index::Xda,
373 Index::MsciWorld, Index::Buk100p,
374 ]
375 }
376}
377
378
379pub fn get_index_regions() -> HashMap<Index, Region> {
380 let mut map = HashMap::new();
381 map.insert(Index::Gspc, Region::UnitedStates);
383 map.insert(Index::Dji, Region::UnitedStates);
384 map.insert(Index::Ixic, Region::UnitedStates);
385 map.insert(Index::Nya, Region::UnitedStates);
386 map.insert(Index::Xax, Region::UnitedStates);
387 map.insert(Index::Rut, Region::UnitedStates);
388 map.insert(Index::Vix, Region::UnitedStates);
389 map.insert(Index::Gsptse, Region::NorthAmerica);
391 map.insert(Index::Bvsp, Region::SouthAmerica);
393 map.insert(Index::Mxx, Region::SouthAmerica);
394 map.insert(Index::Ipsa, Region::SouthAmerica);
395 map.insert(Index::Merv, Region::SouthAmerica);
396 map.insert(Index::Ivbx, Region::SouthAmerica);
397 map.insert(Index::Ibrx50, Region::SouthAmerica);
398 map.insert(Index::Ftse, Region::Europe);
400 map.insert(Index::Gdaxi, Region::Europe);
401 map.insert(Index::Fchi, Region::Europe);
402 map.insert(Index::Stoxx50e, Region::Europe);
403 map.insert(Index::N100, Region::Europe);
404 map.insert(Index::Bfx, Region::Europe);
405 map.insert(Index::MoexMe, Region::Europe);
406 map.insert(Index::Aex, Region::Europe);
407 map.insert(Index::Ibex, Region::Europe);
408 map.insert(Index::Ftsemib, Region::Europe);
409 map.insert(Index::Ssmi, Region::Europe);
410 map.insert(Index::Psi, Region::Europe);
411 map.insert(Index::Atx, Region::Europe);
412 map.insert(Index::Omxs30, Region::Europe);
413 map.insert(Index::Omxc25, Region::Europe);
414 map.insert(Index::Wig20, Region::Europe);
415 map.insert(Index::Bux, Region::Europe);
416 map.insert(Index::Imoex, Region::Europe);
417 map.insert(Index::Rtsi, Region::Europe);
418 map.insert(Index::Hsi, Region::Asia);
420 map.insert(Index::Sti, Region::Asia);
421 map.insert(Index::Bsesn, Region::Asia);
422 map.insert(Index::Jkse, Region::Asia);
423 map.insert(Index::Klse, Region::Asia);
424 map.insert(Index::Ks11, Region::Asia);
425 map.insert(Index::Twii, Region::Asia);
426 map.insert(Index::N225, Region::Asia);
427 map.insert(Index::Shanghai, Region::Asia);
428 map.insert(Index::Szse, Region::Asia);
429 map.insert(Index::Set, Region::Asia);
430 map.insert(Index::Nsei, Region::Asia);
431 map.insert(Index::Cnx200, Region::Asia);
432 map.insert(Index::Psei, Region::Asia);
433 map.insert(Index::ChinaA50, Region::Asia);
434 map.insert(Index::Djsh, Region::Asia);
435 map.insert(Index::Indiavix, Region::Asia);
436 map.insert(Index::Case30, Region::Africa);
438 map.insert(Index::Jn0uJo, Region::Africa);
439 map.insert(Index::Ftsejse, Region::Africa);
440 map.insert(Index::Afr40, Region::Africa);
441 map.insert(Index::Sa40, Region::Africa);
442 map.insert(Index::Raf40, Region::Africa);
443 map.insert(Index::Alt15, Region::Africa);
444 map.insert(Index::Ta125Ta, Region::MiddleEast);
446 map.insert(Index::Ta35, Region::MiddleEast);
447 map.insert(Index::Tasi, Region::MiddleEast);
448 map.insert(Index::Tamayuz, Region::MiddleEast);
449 map.insert(Index::Bist100, Region::MiddleEast);
450 map.insert(Index::Axjo, Region::Oceania);
452 map.insert(Index::Aord, Region::Oceania);
453 map.insert(Index::Nz50, Region::Oceania);
454 map.insert(Index::DxYNyb, Region::Global);
456 map.insert(Index::UsdStrd, Region::Global);
457 map.insert(Index::Xdb, Region::Global);
458 map.insert(Index::Xde, Region::Global);
459 map.insert(Index::Xdn, Region::Global);
460 map.insert(Index::Xda, Region::Global);
461 map.insert(Index::MsciWorld, Region::Global);
462 map.insert(Index::Buk100p, Region::Global);
463 map
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
467#[serde(rename_all = "snake_case")]
468pub struct MarketIndex {
469 pub name: String,
470 pub value: f64,
471 pub change: String,
472 #[serde(rename = "percentChange")]
473 pub percent_change: String,
474 #[serde(skip_serializing_if = "Option::is_none", rename = "fiveDaysReturn")]
475 pub five_days_return: Option<String>,
476 #[serde(skip_serializing_if = "Option::is_none", rename = "oneMonthReturn")]
477 pub one_month_return: Option<String>,
478 #[serde(skip_serializing_if = "Option::is_none", rename = "threeMonthReturn")]
479 pub three_month_return: Option<String>,
480 #[serde(skip_serializing_if = "Option::is_none", rename = "sixMonthReturn")]
481 pub six_month_return: Option<String>,
482 #[serde(skip_serializing_if = "Option::is_none", rename = "ytdReturn")]
483 pub ytd_return: Option<String>,
484 #[serde(skip_serializing_if = "Option::is_none", rename = "yearReturn")]
485 pub year_return: Option<String>,
486 #[serde(skip_serializing_if = "Option::is_none", rename = "threeYearReturn")]
487 pub three_year_return: Option<String>,
488 #[serde(skip_serializing_if = "Option::is_none", rename = "fiveYearReturn")]
489 pub five_year_return: Option<String>,
490 #[serde(skip_serializing_if = "Option::is_none", rename = "tenYearReturn")]
491 pub ten_year_return: Option<String>,
492 #[serde(skip_serializing_if = "Option::is_none", rename = "maxReturn")]
493 pub max_return: Option<String>,
494}
495
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500 use proptest::prelude::*;
501
502 fn optional_string() -> impl Strategy<Value = Option<String>> {
503 proptest::option::of("[A-Za-z0-9 .%-]{0,20}")
504 }
505
506 proptest! {
509 #![proptest_config(ProptestConfig::with_cases(100))]
510
511 #[test]
512 fn market_index_roundtrip(
513 name in "[A-Za-z0-9 ]{1,30}",
514 value in 0.01f64..100000.0f64,
515 change in "-?[0-9]{1,5}\\.[0-9]{2}",
516 percent_change in "-?[0-9]{1,3}\\.[0-9]{2}%",
517 five_days_return in optional_string(),
518 one_month_return in optional_string(),
519 ) {
520 let index = MarketIndex {
521 name: name.clone(),
522 value,
523 change: change.clone(),
524 percent_change: percent_change.clone(),
525 five_days_return: five_days_return.clone(),
526 one_month_return: one_month_return.clone(),
527 three_month_return: None,
528 six_month_return: None,
529 ytd_return: None,
530 year_return: None,
531 three_year_return: None,
532 five_year_return: None,
533 ten_year_return: None,
534 max_return: None,
535 };
536
537 let json = serde_json::to_string(&index).unwrap();
538 let parsed: MarketIndex = serde_json::from_str(&json).unwrap();
539
540 prop_assert_eq!(index.name, parsed.name);
541 prop_assert!((index.value - parsed.value).abs() < 1e-10);
542 prop_assert_eq!(index.change, parsed.change);
543 prop_assert_eq!(index.percent_change, parsed.percent_change);
544 }
545
546 #[test]
547 fn region_roundtrip(region in prop_oneof![
548 Just(Region::UnitedStates),
549 Just(Region::NorthAmerica),
550 Just(Region::SouthAmerica),
551 Just(Region::Europe),
552 Just(Region::Asia),
553 Just(Region::Africa),
554 Just(Region::MiddleEast),
555 Just(Region::Oceania),
556 Just(Region::Global),
557 ]) {
558 let json = serde_json::to_string(®ion).unwrap();
559 let parsed: Region = serde_json::from_str(&json).unwrap();
560
561 prop_assert_eq!(region, parsed);
562 }
563 }
564}