#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use crate::adapters::common::encode_path_segment;
use crate::error::Result;
use crate::models::fundamentals::FinancialStatement;
use crate::providers::build_financial_statement;
use crate::{Frequency, Provider, StatementType};
use super::build_client;
use super::models::PaginatedResponseDTO;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FinancialResultDTO {
pub tickers: Option<Vec<String>>,
pub company_name: Option<String>,
pub cik: Option<String>,
pub filing_date: Option<String>,
pub period_of_report_date: Option<String>,
pub fiscal_period: Option<String>,
pub fiscal_year: Option<String>,
pub source_filing_url: Option<String>,
pub financials: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ShortInterestDTO {
pub settlement_date: Option<String>,
pub short_interest: Option<f64>,
pub avg_daily_volume: Option<f64>,
pub days_to_cover: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ShortVolumeDTO {
pub date: Option<String>,
pub short_volume: Option<f64>,
pub short_exempt_volume: Option<f64>,
pub total_volume: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FloatDataDTO {
pub ticker: Option<String>,
pub float_shares: Option<f64>,
pub outstanding_shares: Option<f64>,
pub date: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FinancialRatiosDTO {
pub ticker: Option<String>,
pub period: Option<String>,
pub fiscal_year: Option<String>,
#[serde(flatten)]
pub ratios: std::collections::HashMap<String, serde_json::Value>,
}
pub async fn stock_financials(
ticker: &str,
params: &[(&str, &str)],
) -> Result<PaginatedResponseDTO<FinancialResultDTO>> {
let client = build_client()?;
let path = "/vX/reference/financials".to_string();
let mut query: Vec<(&str, &str)> = vec![("ticker", ticker)];
query.extend_from_slice(params);
client.get(&path, &query).await
}
pub async fn fetch_financials_response(
symbol: &str,
stmt_type: StatementType,
frequency: Frequency,
) -> Result<FinancialStatement> {
let poly_type = match frequency {
Frequency::Annual => "Y",
Frequency::Quarterly => "Q",
};
let paginated = stock_financials(symbol, &[("type", poly_type), ("limit", "100")]).await?;
let results = paginated.results.unwrap_or_default();
let statement_key = match stmt_type {
StatementType::Income => "income_statement",
StatementType::Balance => "balance_sheet",
StatementType::CashFlow => "cash_flow_statement",
};
let mut data: std::collections::HashMap<
String,
std::collections::HashMap<String, serde_json::Value>,
> = std::collections::HashMap::new();
for result in &results {
let period = result
.period_of_report_date
.as_deref()
.or(result.filing_date.as_deref())
.unwrap_or("unknown");
if let Some(ref financials) = result.financials
&& let Some(stmt_section) = financials.get(statement_key)
&& let Some(section_obj) = stmt_section.as_object()
{
for (metric, metric_obj) in section_obj {
let metric_value = metric_obj
.get("value")
.cloned()
.unwrap_or_else(|| metric_obj.clone());
data.entry(metric.clone())
.or_default()
.insert(period.to_string(), metric_value);
}
}
}
Ok(build_financial_statement(
symbol.to_string(),
stmt_type.as_str().to_string(),
frequency.as_str().to_string(),
Provider::Polygon,
data,
))
}
pub async fn stock_short_interest(
ticker: &str,
params: &[(&str, &str)],
) -> Result<PaginatedResponseDTO<ShortInterestDTO>> {
let client = build_client()?;
let path = format!(
"/v3/reference/short-interest/{}",
encode_path_segment(ticker)
);
client.get(&path, params).await
}
pub async fn stock_short_volume(
ticker: &str,
params: &[(&str, &str)],
) -> Result<PaginatedResponseDTO<ShortVolumeDTO>> {
let client = build_client()?;
let path = format!("/v3/reference/short-volume/{}", encode_path_segment(ticker));
client.get(&path, params).await
}
pub async fn stock_float(ticker: &str) -> Result<PaginatedResponseDTO<FloatDataDTO>> {
let client = build_client()?;
let path = format!("/v3/reference/float/{}", encode_path_segment(ticker));
client.get(&path, &[]).await
}
pub async fn stock_ratios(
ticker: &str,
params: &[(&str, &str)],
) -> Result<PaginatedResponseDTO<FinancialRatiosDTO>> {
let client = build_client()?;
let path = "/vX/reference/financials/ratios".to_string();
let mut query: Vec<(&str, &str)> = vec![("ticker", ticker)];
query.extend_from_slice(params);
client.get(&path, &query).await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_stock_financials_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/vX/reference/financials")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apiKey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("ticker".into(), "AAPL".into()),
]))
.with_status(200)
.with_body(
serde_json::json!({
"status": "OK",
"request_id": "abc",
"results": [{
"tickers": ["AAPL"],
"company_name": "Apple Inc",
"fiscal_period": "Q1",
"fiscal_year": "2024",
"filing_date": "2024-02-01"
}]
})
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let resp: PaginatedResponseDTO<FinancialResultDTO> = client
.get("/vX/reference/financials", &[("ticker", "AAPL")])
.await
.unwrap();
let results = resp.results.unwrap();
assert_eq!(results[0].company_name.as_deref(), Some("Apple Inc"));
}
}