Skip to main content

finance_query/adapters/polygon/stocks/
fundamentals.rs

1//! Stock fundamental data: balance sheets, cash flow, income statements, ratios, short interest, float.
2
3use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::super::build_client;
9use super::super::models::PaginatedResponse;
10
11/// A financial statement row (balance sheet, income, or cash flow).
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct FinancialResult {
15    /// Ticker symbol.
16    pub tickers: Option<Vec<String>>,
17    /// Company name.
18    pub company_name: Option<String>,
19    /// CIK number.
20    pub cik: Option<String>,
21    /// Filing date.
22    pub filing_date: Option<String>,
23    /// Period of report.
24    pub period_of_report_date: Option<String>,
25    /// Fiscal period (e.g., `"Q1"`, `"FY"`).
26    pub fiscal_period: Option<String>,
27    /// Fiscal year.
28    pub fiscal_year: Option<String>,
29    /// Source filing URL.
30    pub source_filing_url: Option<String>,
31    /// Financials data (nested by statement type).
32    pub financials: Option<serde_json::Value>,
33}
34
35/// Short interest data point.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37#[non_exhaustive]
38pub struct ShortInterest {
39    /// Settlement date.
40    pub settlement_date: Option<String>,
41    /// Short interest (shares).
42    pub short_interest: Option<f64>,
43    /// Average daily volume.
44    pub avg_daily_volume: Option<f64>,
45    /// Days to cover.
46    pub days_to_cover: Option<f64>,
47}
48
49/// Short volume data point.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[non_exhaustive]
52pub struct ShortVolume {
53    /// Date.
54    pub date: Option<String>,
55    /// Short volume.
56    pub short_volume: Option<f64>,
57    /// Short exempt volume.
58    pub short_exempt_volume: Option<f64>,
59    /// Total volume.
60    pub total_volume: Option<f64>,
61}
62
63/// Float data.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[non_exhaustive]
66pub struct FloatData {
67    /// Ticker symbol.
68    pub ticker: Option<String>,
69    /// Float shares.
70    pub float_shares: Option<f64>,
71    /// Outstanding shares.
72    pub outstanding_shares: Option<f64>,
73    /// Date.
74    pub date: Option<String>,
75}
76
77/// Financial ratios.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79#[non_exhaustive]
80pub struct FinancialRatios {
81    /// Ticker.
82    pub ticker: Option<String>,
83    /// Period.
84    pub period: Option<String>,
85    /// Fiscal year.
86    pub fiscal_year: Option<String>,
87    /// All ratio values as key-value pairs.
88    #[serde(flatten)]
89    pub ratios: std::collections::HashMap<String, serde_json::Value>,
90}
91
92/// Fetch stock financials (balance sheets, income statements, cash flow).
93///
94/// * `ticker` - Stock ticker symbol
95/// * `params` - Optional: `type` (Y, Q, YA, QA, T), `filing_date`, `period_of_report_date`, `limit`, `sort`, `order`
96pub async fn stock_financials(
97    ticker: &str,
98    params: &[(&str, &str)],
99) -> Result<PaginatedResponse<FinancialResult>> {
100    let client = build_client()?;
101    let path = "/vX/reference/financials".to_string();
102    let mut query: Vec<(&str, &str)> = vec![("ticker", ticker)];
103    query.extend_from_slice(params);
104    client.get(&path, &query).await
105}
106
107/// Fetch short interest data for a stock ticker.
108pub async fn stock_short_interest(
109    ticker: &str,
110    params: &[(&str, &str)],
111) -> Result<PaginatedResponse<ShortInterest>> {
112    let client = build_client()?;
113    let path = format!(
114        "/v3/reference/short-interest/{}",
115        encode_path_segment(ticker)
116    );
117    client.get(&path, params).await
118}
119
120/// Fetch short volume data for a stock ticker.
121pub async fn stock_short_volume(
122    ticker: &str,
123    params: &[(&str, &str)],
124) -> Result<PaginatedResponse<ShortVolume>> {
125    let client = build_client()?;
126    let path = format!("/v3/reference/short-volume/{}", encode_path_segment(ticker));
127    client.get(&path, params).await
128}
129
130/// Fetch float data for a stock ticker.
131pub async fn stock_float(ticker: &str) -> Result<PaginatedResponse<FloatData>> {
132    let client = build_client()?;
133    let path = format!("/v3/reference/float/{}", encode_path_segment(ticker));
134    client.get(&path, &[]).await
135}
136
137/// Fetch financial ratios for a stock ticker.
138pub async fn stock_ratios(
139    ticker: &str,
140    params: &[(&str, &str)],
141) -> Result<PaginatedResponse<FinancialRatios>> {
142    let client = build_client()?;
143    let path = "/vX/reference/financials/ratios".to_string();
144    let mut query: Vec<(&str, &str)> = vec![("ticker", ticker)];
145    query.extend_from_slice(params);
146    client.get(&path, &query).await
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[tokio::test]
154    async fn test_stock_financials_mock() {
155        let mut server = mockito::Server::new_async().await;
156        let _mock = server
157            .mock("GET", "/vX/reference/financials")
158            .match_query(mockito::Matcher::AllOf(vec![
159                mockito::Matcher::UrlEncoded("apiKey".into(), "test-key".into()),
160                mockito::Matcher::UrlEncoded("ticker".into(), "AAPL".into()),
161            ]))
162            .with_status(200)
163            .with_body(
164                serde_json::json!({
165                    "status": "OK",
166                    "request_id": "abc",
167                    "results": [{
168                        "tickers": ["AAPL"],
169                        "company_name": "Apple Inc",
170                        "fiscal_period": "Q1",
171                        "fiscal_year": "2024",
172                        "filing_date": "2024-02-01"
173                    }]
174                })
175                .to_string(),
176            )
177            .create_async()
178            .await;
179
180        let client = super::super::super::build_test_client(&server.url()).unwrap();
181        let resp: PaginatedResponse<FinancialResult> = client
182            .get("/vX/reference/financials", &[("ticker", "AAPL")])
183            .await
184            .unwrap();
185        let results = resp.results.unwrap();
186        assert_eq!(results[0].company_name.as_deref(), Some("Apple Inc"));
187    }
188}