Skip to main content

finance_query/domains/
market.rs

1//! Market-wide calendar and performance handles.
2//!
3//! Created via [`Providers::calendar`](crate::Providers::calendar) and
4//! [`Providers::market`](crate::Providers::market).
5
6use std::sync::Arc;
7
8use crate::error::Result;
9use crate::models::calendar::market::{CalendarKind, MarketCalendarEntry};
10use crate::models::chart::Candle;
11use crate::models::market::performance::{
12    IndustryPe, MoverDirection, MoverQuote, SectorPe, SectorPerformance,
13};
14use crate::providers::Capability;
15use crate::providers::ProviderSet;
16
17domain_handle! {
18    /// Market-wide event calendars backed by configured data providers.
19    ///
20    /// Routes through [`Capability::CALENDAR`]. Unlike
21    /// [`Ticker::calendar`](crate::Ticker::calendar), which builds a per-symbol
22    /// timeline, these span the whole market over a date range.
23    ///
24    /// Created via [`Providers::calendar`](crate::Providers::calendar).
25    pub struct MarketCalendar
26    caches: { cache: Vec<MarketCalendarEntry> }
27}
28
29impl MarketCalendar {
30    /// Fetch a calendar of `kind` over `[from, to]` (`YYYY-MM-DD` dates).
31    ///
32    /// Cached per `(kind, from, to)`.
33    pub async fn fetch(
34        &self,
35        kind: CalendarKind,
36        from: &str,
37        to: &str,
38    ) -> Result<Vec<MarketCalendarEntry>> {
39        let key = format!("{kind:?}\u{1f}{from}\u{1f}{to}");
40        let providers = Arc::clone(&self.providers);
41        let (from, to) = (from.to_string(), to.to_string());
42        self.cache
43            .get_or_try(key, move || async move {
44                providers
45                    .fetch(Capability::CALENDAR, move |p| {
46                        let (from, to) = (from.clone(), to.clone());
47                        let p = p.clone();
48                        async move {
49                            p.as_calendar()
50                                .ok_or_else(|| p.not_supported(kind.operation()))?
51                                .fetch_market_calendar(kind, &from, &to)
52                                .await
53                        }
54                    })
55                    .await
56            })
57            .await
58    }
59
60    /// Earnings releases scheduled or reported over `[from, to]`.
61    pub async fn earnings(&self, from: &str, to: &str) -> Result<Vec<MarketCalendarEntry>> {
62        self.fetch(CalendarKind::Earnings, from, to).await
63    }
64
65    /// Initial public offerings over `[from, to]`.
66    pub async fn ipos(&self, from: &str, to: &str) -> Result<Vec<MarketCalendarEntry>> {
67        self.fetch(CalendarKind::Ipo, from, to).await
68    }
69
70    /// Dividend payments over `[from, to]`.
71    pub async fn dividends(&self, from: &str, to: &str) -> Result<Vec<MarketCalendarEntry>> {
72        self.fetch(CalendarKind::Dividend, from, to).await
73    }
74
75    /// Stock splits over `[from, to]`.
76    pub async fn splits(&self, from: &str, to: &str) -> Result<Vec<MarketCalendarEntry>> {
77        self.fetch(CalendarKind::Split, from, to).await
78    }
79
80    /// Macro-economic releases over `[from, to]`.
81    pub async fn economic(&self, from: &str, to: &str) -> Result<Vec<MarketCalendarEntry>> {
82        self.fetch(CalendarKind::Economic, from, to).await
83    }
84
85    /// Upcoming market holidays and early closes. Providers return their
86    /// upcoming set, so no date range is taken.
87    pub async fn holidays(&self) -> Result<Vec<MarketCalendarEntry>> {
88        self.fetch(CalendarKind::MarketHoliday, "", "").await
89    }
90
91    /// Live open/closed status per exchange. A snapshot rather than a dated
92    /// event, so no date range is taken. Currently Alpha Vantage only.
93    pub async fn market_status(&self) -> Result<Vec<MarketCalendarEntry>> {
94        self.fetch(CalendarKind::MarketStatus, "", "").await
95    }
96}
97
98/// Market-wide performance statistics backed by configured data providers.
99///
100/// Routes through [`crate::Capability::MARKET`]. Unlike [`crate::finance::sector`] and
101/// [`crate::finance::market_summary`] — Yahoo-only convenience shortcuts — these
102/// honour the configured provider priority.
103///
104/// Created via [`Providers::market`](crate::Providers::market).
105pub struct Market {
106    providers: Arc<ProviderSet>,
107}
108
109impl Market {
110    /// Download one minute or daily stock-bar page with an explicit adjustment policy.
111    /// Pages bypass the response cache so applications can bound and checkpoint downloads.
112    pub async fn stock_bars_page(
113        &self,
114        request: &crate::StockBarsRequest,
115        cursor: Option<&crate::PageCursor>,
116    ) -> Result<crate::ProviderPage<crate::StockBar>> {
117        self.providers
118            .fetch_paged(
119                crate::Capability::CHART,
120                crate::Operation::StockBarsPage,
121                &request.cursor_identity(),
122                cursor,
123                |p| {
124                    let p = Arc::clone(p);
125                    async move {
126                        p.as_chart()
127                            .ok_or_else(|| p.not_supported(crate::Operation::StockBarsPage))?
128                            .fetch_stock_bars_page(request, cursor)
129                            .await
130                    }
131                },
132            )
133            .await
134    }
135
136    pub(crate) fn with_providers(providers: Arc<ProviderSet>) -> Self {
137        Self { providers }
138    }
139
140    /// Aggregate performance for every sector.
141    pub async fn sector_performance(&self) -> Result<Vec<SectorPerformance>> {
142        dispatch_via!(
143            self,
144            MARKET,
145            as_market,
146            SectorPerformance,
147            fetch_sector_performance,
148            []
149        )
150    }
151
152    /// Historical aggregate sector performance, most recent first.
153    pub async fn sector_performance_history(
154        &self,
155        limit: u32,
156    ) -> Result<Vec<crate::models::market::performance::SectorPerformanceHistory>> {
157        dispatch_via!(
158            self,
159            MARKET,
160            as_market,
161            SectorPerformanceHistory,
162            fetch_sector_performance_history,
163            [],
164            limit
165        )
166    }
167
168    /// Price/earnings ratios by sector.
169    pub async fn sector_pe(&self) -> Result<Vec<SectorPe>> {
170        dispatch_via!(
171            self,
172            MARKET,
173            as_market,
174            SectorPerformance,
175            fetch_sector_pe,
176            []
177        )
178    }
179
180    /// Price/earnings ratios by industry.
181    pub async fn industry_pe(&self) -> Result<Vec<IndustryPe>> {
182        dispatch_via!(
183            self,
184            MARKET,
185            as_market,
186            SectorPerformance,
187            fetch_industry_pe,
188            []
189        )
190    }
191
192    /// Market movers for `direction`.
193    pub async fn movers(&self, direction: MoverDirection) -> Result<Vec<MoverQuote>> {
194        dispatch_via!(
195            self,
196            MARKET,
197            as_market,
198            MarketMovers,
199            fetch_market_movers,
200            [],
201            direction
202        )
203    }
204
205    /// Largest percentage gainers.
206    pub async fn gainers(&self) -> Result<Vec<MoverQuote>> {
207        self.movers(MoverDirection::Gainers).await
208    }
209
210    /// Largest percentage losers.
211    pub async fn losers(&self) -> Result<Vec<MoverQuote>> {
212        self.movers(MoverDirection::Losers).await
213    }
214
215    /// Highest traded volume.
216    pub async fn most_active(&self) -> Result<Vec<MoverQuote>> {
217        self.movers(MoverDirection::MostActive).await
218    }
219
220    /// Fetch grouped daily OHLCV bars for every stock ticker on `date`
221    /// (`YYYY-MM-DD`) in one call — "give me every ticker's OHLC for this
222    /// date" rather than one symbol at a time.
223    ///
224    /// Routes through [`Capability::CHART`](crate::providers::Capability::CHART)
225    /// (the same capability backing per-symbol chart methods) rather than
226    /// `MARKET`, since it's OHLCV data rather than a performance statistic.
227    /// Currently Polygon only. Not cached — one date is one request either way.
228    pub async fn grouped_daily(&self, date: &str) -> Result<Vec<(String, Candle)>> {
229        let date = date.to_string();
230        dispatch_via!(
231            self,
232            CHART,
233            as_chart,
234            GroupedDaily,
235            fetch_grouped_daily,
236            [date],
237            &date
238        )
239    }
240
241    /// Fetch grouped daily OHLCV bars for every crypto ticker on `date`
242    /// (`YYYY-MM-DD`) in one call. See [`grouped_daily`](Self::grouped_daily).
243    pub async fn crypto_grouped_daily(&self, date: &str) -> Result<Vec<(String, Candle)>> {
244        let date = date.to_string();
245        dispatch_via!(
246            self,
247            CHART,
248            as_chart,
249            CryptoGroupedDaily,
250            fetch_crypto_grouped_daily,
251            [date],
252            &date
253        )
254    }
255
256    /// Fetch grouped daily OHLCV bars for every forex ticker on `date`
257    /// (`YYYY-MM-DD`) in one call. See [`grouped_daily`](Self::grouped_daily).
258    pub async fn forex_grouped_daily(&self, date: &str) -> Result<Vec<(String, Candle)>> {
259        let date = date.to_string();
260        dispatch_via!(
261            self,
262            CHART,
263            as_chart,
264            ForexGroupedDaily,
265            fetch_forex_grouped_daily,
266            [date],
267            &date
268        )
269    }
270
271    /// Fetch coins/nfts/categories trending in the last 24h.
272    ///
273    /// Routes through [`Capability::CRYPTO`](crate::providers::Capability::CRYPTO).
274    /// Currently CoinGecko only.
275    #[cfg(feature = "crypto")]
276    pub async fn crypto_trending(&self) -> Result<Vec<crate::models::crypto::TrendingCoin>> {
277        dispatch_via!(
278            self,
279            CRYPTO,
280            as_crypto,
281            CryptoTrending,
282            fetch_crypto_trending,
283            []
284        )
285    }
286
287    /// Fetch aggregate global cryptocurrency market statistics.
288    ///
289    /// Routes through [`Capability::CRYPTO`](crate::providers::Capability::CRYPTO).
290    /// Currently CoinGecko only.
291    #[cfg(feature = "crypto")]
292    pub async fn crypto_global(&self) -> Result<crate::models::crypto::GlobalCryptoStats> {
293        dispatch_via!(
294            self,
295            CRYPTO,
296            as_crypto,
297            CryptoGlobal,
298            fetch_crypto_global,
299            []
300        )
301    }
302}