Skip to main content

finance_query/ticker/
polygon.rs

1//! Polygon.io adapter integration for [`Ticker`].
2//!
3//! Exposes a curated subset of the [`crate::adapters::polygon`] free
4//! functions as methods on a typed handle obtained via
5//! [`crate::Ticker::polygon`].
6//!
7//! Requires the `polygon` feature flag and a one-time call to
8//! [`crate::adapters::polygon::init`] before any method is invoked.
9//!
10//! All methods return adapter-native types unchanged (no translation, no
11//! fallback). If the adapter has not been initialized, the underlying
12//! free function returns
13//! [`crate::error::FinanceError::InvalidParameter`].
14
15use std::sync::Arc;
16
17use crate::adapters::polygon;
18use crate::error::Result;
19
20/// Financial report period filter for [`PolygonHandle::financials`].
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum FinancialPeriod {
23    /// Annual filings.
24    Annual,
25    /// Quarterly filings.
26    Quarterly,
27    /// Trailing twelve-month filings.
28    Ttm,
29}
30
31impl FinancialPeriod {
32    fn as_str(self) -> &'static str {
33        match self {
34            Self::Annual => "annual",
35            Self::Quarterly => "quarterly",
36            Self::Ttm => "ttm",
37        }
38    }
39}
40
41/// Typed accessor for the Polygon.io adapter, scoped to a single ticker.
42///
43/// Construct via [`crate::Ticker::polygon`]. See the module docs for
44/// usage and feature/init requirements.
45#[derive(Clone, Debug)]
46pub struct PolygonHandle {
47    symbol: Arc<str>,
48}
49
50impl PolygonHandle {
51    /// Construct a handle. Crate-internal — public callers go through
52    /// [`crate::Ticker::polygon`].
53    pub(crate) fn new(symbol: Arc<str>) -> Self {
54        Self { symbol }
55    }
56
57    /// The ticker symbol this handle is scoped to.
58    pub fn symbol(&self) -> &str {
59        &self.symbol
60    }
61
62    /// Most-recent stock snapshot (price, volume, last trade, last quote).
63    ///
64    /// Wraps [`polygon::stock_snapshot`].
65    pub async fn snapshot(&self) -> Result<polygon::SingleSnapshotResponse> {
66        polygon::stock_snapshot(&self.symbol).await
67    }
68
69    /// Aggregate (OHLCV) bars between two dates.
70    ///
71    /// Wraps [`polygon::stock_aggregates`].
72    pub async fn aggregates(
73        &self,
74        multiplier: u32,
75        timespan: polygon::Timespan,
76        from: &str,
77        to: &str,
78        params: Option<polygon::AggregateParams>,
79    ) -> Result<polygon::AggregateResponse> {
80        polygon::stock_aggregates(&self.symbol, multiplier, timespan, from, to, params).await
81    }
82
83    /// Previous trading day's open/close/high/low/volume bar.
84    ///
85    /// Wraps [`polygon::stock_previous_close`].
86    pub async fn previous_close(
87        &self,
88        adjusted: Option<bool>,
89    ) -> Result<polygon::AggregateResponse> {
90        polygon::stock_previous_close(&self.symbol, adjusted).await
91    }
92
93    /// Most-recent tick-level trade.
94    ///
95    /// Wraps [`polygon::stock_last_trade`].
96    pub async fn last_trade(&self) -> Result<polygon::LastTradeResponse> {
97        polygon::stock_last_trade(&self.symbol).await
98    }
99
100    /// News articles for this ticker, most recent first. `limit` is capped
101    /// by Polygon at 1000.
102    ///
103    /// Wraps [`polygon::stock_news`].
104    pub async fn news(
105        &self,
106        limit: u32,
107    ) -> Result<polygon::PaginatedResponse<polygon::NewsArticle>> {
108        let limit_str = limit.to_string();
109        polygon::stock_news(&[("ticker", &self.symbol), ("limit", &limit_str)]).await
110    }
111
112    /// Historical dividends for this ticker.
113    ///
114    /// Wraps [`polygon::stock_dividends`].
115    pub async fn dividends(&self) -> Result<polygon::PaginatedResponse<polygon::Dividend>> {
116        polygon::stock_dividends(&[("ticker", &self.symbol)]).await
117    }
118
119    /// Historical stock splits for this ticker.
120    ///
121    /// Wraps [`polygon::stock_splits`].
122    pub async fn splits(&self) -> Result<polygon::PaginatedResponse<polygon::Split>> {
123        polygon::stock_splits(&[("ticker", &self.symbol)]).await
124    }
125
126    /// Financial statements filtered by annual, quarterly, or TTM period.
127    ///
128    /// Wraps [`polygon::stock_financials`].
129    pub async fn financials(
130        &self,
131        period: FinancialPeriod,
132        limit: Option<u32>,
133    ) -> Result<polygon::PaginatedResponse<polygon::FinancialResult>> {
134        let mut params: Vec<(&str, &str)> = vec![("period_of_report_type", period.as_str())];
135        let limit_str;
136        if let Some(n) = limit {
137            limit_str = n.to_string();
138            params.push(("limit", &limit_str));
139        }
140        polygon::stock_financials(&self.symbol, &params).await
141    }
142
143    /// Ticker reference data (profile, shares outstanding, market cap).
144    ///
145    /// Wraps [`polygon::ticker_details`].
146    pub async fn details(&self) -> Result<polygon::TickerDetailsResponse> {
147        polygon::ticker_details(&self.symbol).await
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn handle_holds_symbol() {
157        let h = PolygonHandle::new(Arc::from("AAPL"));
158        assert_eq!(h.symbol(), "AAPL");
159    }
160
161    #[test]
162    fn handle_clone_is_cheap_arc_clone() {
163        let h1 = PolygonHandle::new(Arc::from("AAPL"));
164        let h2 = h1.clone();
165        assert_eq!(h1.symbol(), h2.symbol());
166    }
167
168    // Compile-time existence tests: verify each method's signature compiles.
169    // These do not run at runtime — if they compile, they pass. If a wrapped
170    // adapter function changes shape and the wrapper isn't updated, this fails
171    // to compile.
172    #[allow(dead_code, clippy::manual_async_fn)]
173    async fn _polygon_method_signatures_compile(h: &super::PolygonHandle) {
174        use crate::adapters::polygon;
175        let _ = h.snapshot().await;
176        let _ = h
177            .aggregates(1, polygon::Timespan::Day, "2024-01-01", "2024-01-31", None)
178            .await;
179        let _ = h.previous_close(None).await;
180        let _ = h.last_trade().await;
181        let _ = h.news(10).await;
182        let _ = h.dividends().await;
183        let _ = h.splits().await;
184        let _ = h.financials(FinancialPeriod::Annual, None).await;
185        let _ = h.details().await;
186    }
187}