finance_query/domains/snapshot.rs
1//! Cross-market snapshot handle.
2//!
3//! Created via [`Providers::snapshot`](crate::Providers::snapshot).
4
5use std::sync::Arc;
6
7use crate::error::Result;
8use crate::models::quote::snapshot::MarketSnapshot;
9use crate::providers::{Capability, Operation};
10
11domain_handle! {
12 /// Snapshots for a watchlist spanning several asset classes, from one request.
13 ///
14 /// Routes through [`Capability::QUOTE`]. Unlike
15 /// [`Tickers`](crate::Tickers) — which is equity-shaped and returns full quote
16 /// summaries — this takes provider-spelled symbols from any market (`"AAPL"`,
17 /// `"X:BTCUSD"`, `"I:SPX"`, `"C:EURUSD"`, `"O:NCLH221014C00005000"`) and
18 /// returns one flattened row each. Polygon is currently the only provider whose
19 /// snapshot endpoint spans markets.
20 ///
21 /// Created via [`Providers::snapshot`](crate::Providers::snapshot).
22 pub struct Snapshot
23 caches: { cache: Vec<MarketSnapshot> }
24}
25
26impl Snapshot {
27 /// Fetch snapshots for `symbols`, which may span asset classes.
28 ///
29 /// Cached per symbol list. Symbols the provider could not resolve come back
30 /// as rows with [`MarketSnapshot::error`] set rather than being dropped, so
31 /// the result can be aligned with the request.
32 pub async fn get<S: AsRef<str>>(&self, symbols: &[S]) -> Result<Vec<MarketSnapshot>> {
33 let owned: Vec<String> = symbols.iter().map(|s| s.as_ref().to_string()).collect();
34 let key = owned.join("\u{1f}");
35 let providers = Arc::clone(&self.providers);
36
37 self.cache
38 .get_or_try(key, move || async move {
39 providers
40 .fetch(Capability::QUOTE, move |p| {
41 let owned = owned.clone();
42 let p = p.clone();
43 async move {
44 let refs: Vec<&str> = owned.iter().map(String::as_str).collect();
45 p.as_quote()
46 .ok_or_else(|| p.not_supported(Operation::UnifiedSnapshot))?
47 .fetch_unified_snapshot(&refs)
48 .await
49 }
50 })
51 .await
52 })
53 .await
54 }
55}