Skip to main content

finance_query/domains/
indices.rs

1//! Stock market index quote handle.
2//!
3//! Created via [`Providers::index`](crate::Providers::index).
4
5use crate::constants::{Interval, TimeRange};
6use crate::error::Result;
7use crate::models::chart::Chart;
8
9domain_handle! {
10    /// A stock market index backed by configured data providers.
11    ///
12    /// Created via [`Providers::index`](crate::Providers::index).
13    pub struct Index { symbol, symbol }
14    cache: crate::models::indices::IndexQuote, chart
15}
16
17impl Index {
18    /// Fetch the current quote for this index.
19    pub async fn quote(&self) -> Result<crate::models::indices::IndexQuote> {
20        fetch_via!(
21            self,
22            symbol,
23            INDICES,
24            as_indices,
25            IndicesQuote,
26            fetch_indices_quote,
27            crate::models::indices::IndexQuote
28        )
29    }
30
31    /// Fetch historical OHLCV candles for this index.
32    ///
33    /// The symbol is passed to the `CHART` route as-is, so it should be in the
34    /// form the route expects (e.g. Yahoo index symbols like `^GSPC`).
35    pub async fn chart(&self, interval: Interval, range: TimeRange) -> Result<Chart> {
36        fetch_chart_via!(self, self.symbol.to_string(), interval, range)
37    }
38
39    /// Fetch historical candles over `range` at a sensible default interval
40    /// ([`TimeRange::default_interval`]).
41    pub async fn history(&self, range: TimeRange) -> Result<Chart> {
42        self.chart(range.default_interval(), range).await
43    }
44
45    /// The [`MajorIndex`](crate::models::indices::MajorIndex) this handle's
46    /// symbol denotes, or an error for indices without constituent support.
47    fn major_index(&self) -> Result<crate::models::indices::MajorIndex> {
48        crate::models::indices::MajorIndex::from_symbol(self.symbol()).ok_or_else(|| {
49            crate::error::FinanceError::InvalidParameter {
50                param: "symbol".into(),
51                reason: format!(
52                    "constituents are available for major indices only \
53                     (S&P 500, Nasdaq 100, Dow Jones), not {}",
54                    self.symbol()
55                ),
56            }
57        })
58    }
59
60    /// Fetch the index's current constituents (major indices only). Not
61    /// cached — constituent lists change rarely but the call is uncommon.
62    pub async fn constituents(&self) -> Result<Vec<crate::models::indices::IndexConstituent>> {
63        let index = self.major_index()?;
64        self.providers
65            .fetch(crate::providers::Capability::INDICES, move |p| {
66                let p = p.clone();
67                async move {
68                    p.as_indices()
69                        .ok_or_else(|| {
70                            p.not_supported(crate::providers::Operation::IndexConstituents)
71                        })?
72                        .fetch_index_constituents(index)
73                        .await
74                }
75            })
76            .await
77    }
78
79    /// Fetch historical changes to the index's constituency (currently
80    /// S&P 500 only on FMP).
81    pub async fn constituent_changes(
82        &self,
83    ) -> Result<Vec<crate::models::indices::IndexConstituentChange>> {
84        let index = self.major_index()?;
85        self.providers
86            .fetch(crate::providers::Capability::INDICES, move |p| {
87                let p = p.clone();
88                async move {
89                    p.as_indices()
90                        .ok_or_else(|| {
91                            p.not_supported(crate::providers::Operation::IndexConstituentChanges)
92                        })?
93                        .fetch_index_constituent_changes(index)
94                        .await
95                }
96            })
97            .await
98    }
99}
100
101impl_chartable_analytics!(Index, crate::risk::TradingCalendar::Exchange);