Skip to main content

finance_query/models/indices/
mod.rs

1//! Stock market index data models.
2//!
3//! Canonical public types for index quotes and constituents,
4//! shared across Polygon and FMP providers.
5
6use serde::{Deserialize, Serialize};
7
8/// A stock market index quote (e.g., S&P 500, NASDAQ, Dow Jones).
9///
10/// Obtain via [`Providers::index`](crate::Providers::index)`(symbol).quote()`.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[non_exhaustive]
13pub struct IndexQuote {
14    /// Index ticker symbol (e.g., `"^GSPC"`, `"^IXIC"`)
15    pub symbol: String,
16    /// Human-readable index name (e.g., `"S&P 500"`)
17    pub name: Option<String>,
18    /// Current index value
19    pub price: Option<f64>,
20    /// Price change
21    pub change: Option<f64>,
22    /// Price change percentage
23    pub change_percent: Option<f64>,
24    /// Unix timestamp of the last update
25    pub timestamp: Option<i64>,
26}
27
28/// A major stock market index whose constituent lists providers can serve.
29///
30/// Passed to [`Index::constituents`](crate::Index::constituents) (derived from
31/// the handle's symbol) and the INDICES provider route.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33#[non_exhaustive]
34pub enum MajorIndex {
35    /// S&P 500 (`^GSPC` / `SPX`).
36    Sp500,
37    /// Nasdaq 100 (`^NDX` / `NDX`).
38    Nasdaq100,
39    /// Dow Jones Industrial Average (`^DJI` / `DJIA`).
40    DowJones,
41}
42
43impl MajorIndex {
44    /// Short lowercase identifier (`"sp500"`, `"nasdaq100"`, `"dowjones"`).
45    pub fn as_str(self) -> &'static str {
46        match self {
47            Self::Sp500 => "sp500",
48            Self::Nasdaq100 => "nasdaq100",
49            Self::DowJones => "dowjones",
50        }
51    }
52
53    /// Map a common index symbol or name to the major index it denotes.
54    ///
55    /// Accepts the Yahoo caret form, the bare ticker, and common names,
56    /// case-insensitively (e.g. `"^GSPC"`, `"SPX"`, `"sp500"`, `"^DJI"`,
57    /// `"DJIA"`, `"^NDX"`, `"nasdaq 100"`). Returns `None` for anything else.
58    pub fn from_symbol(symbol: &str) -> Option<Self> {
59        let s: String = symbol
60            .trim()
61            .trim_start_matches('^')
62            .chars()
63            .filter(|c| !c.is_whitespace() && *c != '&' && *c != '-')
64            .collect::<String>()
65            .to_ascii_lowercase();
66        match s.as_str() {
67            "gspc" | "spx" | "sp500" => Some(Self::Sp500),
68            "ndx" | "nasdaq100" => Some(Self::Nasdaq100),
69            "dji" | "djia" | "dowjones" | "dow" | "dowjones30" => Some(Self::DowJones),
70            _ => None,
71        }
72    }
73}
74
75impl std::fmt::Display for MajorIndex {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.write_str(self.as_str())
78    }
79}
80
81/// A constituent (member) of a major stock market index.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83#[non_exhaustive]
84pub struct IndexConstituent {
85    /// Ticker symbol of the constituent company
86    pub symbol: String,
87    /// Company name
88    pub name: Option<String>,
89    /// Sector classification
90    pub sector: Option<String>,
91    /// Sub-sector classification
92    pub sub_sector: Option<String>,
93    /// Headquarters location
94    pub headquarters: Option<String>,
95    /// Date the company was first added to the index (`YYYY-MM-DD`)
96    pub date_first_added: Option<String>,
97    /// SEC CIK number
98    pub cik: Option<String>,
99    /// Year the company was founded
100    pub founded: Option<String>,
101}
102
103/// A historical change in a major index's constituency.
104#[derive(Debug, Clone, Serialize, Deserialize)]
105#[non_exhaustive]
106pub struct IndexConstituentChange {
107    /// Date of the change (`YYYY-MM-DD`)
108    pub date: Option<String>,
109    /// Ticker symbol the change concerns
110    pub symbol: Option<String>,
111    /// Security that was added
112    pub added_security: Option<String>,
113    /// Ticker that was removed
114    pub removed_ticker: Option<String>,
115    /// Security that was removed
116    pub removed_security: Option<String>,
117    /// Reason for the change
118    pub reason: Option<String>,
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn major_index_maps_common_symbols() {
127        for s in ["^GSPC", "GSPC", "SPX", "sp500", "S&P 500"] {
128            assert_eq!(MajorIndex::from_symbol(s), Some(MajorIndex::Sp500), "{s}");
129        }
130        for s in ["^NDX", "ndx", "NASDAQ 100", "nasdaq100"] {
131            assert_eq!(
132                MajorIndex::from_symbol(s),
133                Some(MajorIndex::Nasdaq100),
134                "{s}"
135            );
136        }
137        for s in ["^DJI", "DJIA", "dow", "Dow Jones"] {
138            assert_eq!(
139                MajorIndex::from_symbol(s),
140                Some(MajorIndex::DowJones),
141                "{s}"
142            );
143        }
144        assert_eq!(MajorIndex::from_symbol("AAPL"), None);
145        assert_eq!(MajorIndex::from_symbol("^IXIC"), None);
146    }
147}