use serde::{Deserialize, Serialize};
use crate::adapters::common::encode_path_segment;
use crate::error::Result;
use super::models::Period;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FinancialRatios {
pub symbol: Option<String>,
pub date: Option<String>,
#[serde(rename = "calendarYear")]
pub calendar_year: Option<String>,
pub period: Option<String>,
#[serde(rename = "currentRatio")]
pub current_ratio: Option<f64>,
#[serde(rename = "quickRatio")]
pub quick_ratio: Option<f64>,
#[serde(rename = "cashRatio")]
pub cash_ratio: Option<f64>,
#[serde(rename = "grossProfitMargin")]
pub gross_profit_margin: Option<f64>,
#[serde(rename = "operatingProfitMargin")]
pub operating_profit_margin: Option<f64>,
#[serde(rename = "netProfitMargin")]
pub net_profit_margin: Option<f64>,
#[serde(rename = "returnOnAssets")]
pub return_on_assets: Option<f64>,
#[serde(rename = "returnOnEquity")]
pub return_on_equity: Option<f64>,
#[serde(rename = "returnOnCapitalEmployed")]
pub return_on_capital_employed: Option<f64>,
#[serde(rename = "debtEquityRatio")]
pub debt_equity_ratio: Option<f64>,
#[serde(rename = "debtRatio")]
pub debt_ratio: Option<f64>,
#[serde(rename = "priceEarningsRatio")]
pub price_earnings_ratio: Option<f64>,
#[serde(rename = "priceToBookRatio")]
pub price_to_book_ratio: Option<f64>,
#[serde(rename = "priceToSalesRatio")]
pub price_to_sales_ratio: Option<f64>,
#[serde(rename = "priceToFreeCashFlowsRatio")]
pub price_to_free_cash_flows_ratio: Option<f64>,
#[serde(rename = "enterpriseValueMultiple")]
pub enterprise_value_multiple: Option<f64>,
#[serde(rename = "dividendYield")]
pub dividend_yield: Option<f64>,
#[serde(rename = "payoutRatio")]
pub payout_ratio: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct KeyMetrics {
pub symbol: Option<String>,
pub date: Option<String>,
#[serde(rename = "calendarYear")]
pub calendar_year: Option<String>,
pub period: Option<String>,
#[serde(rename = "revenuePerShare")]
pub revenue_per_share: Option<f64>,
#[serde(rename = "netIncomePerShare")]
pub net_income_per_share: Option<f64>,
#[serde(rename = "operatingCashFlowPerShare")]
pub operating_cash_flow_per_share: Option<f64>,
#[serde(rename = "freeCashFlowPerShare")]
pub free_cash_flow_per_share: Option<f64>,
#[serde(rename = "cashPerShare")]
pub cash_per_share: Option<f64>,
#[serde(rename = "bookValuePerShare")]
pub book_value_per_share: Option<f64>,
#[serde(rename = "tangibleBookValuePerShare")]
pub tangible_book_value_per_share: Option<f64>,
#[serde(rename = "shareholdersEquityPerShare")]
pub shareholders_equity_per_share: Option<f64>,
#[serde(rename = "interestDebtPerShare")]
pub interest_debt_per_share: Option<f64>,
#[serde(rename = "marketCap")]
pub market_cap: Option<f64>,
#[serde(rename = "enterpriseValue")]
pub enterprise_value: Option<f64>,
#[serde(rename = "peRatio")]
pub pe_ratio: Option<f64>,
#[serde(rename = "pbRatio")]
pub pb_ratio: Option<f64>,
#[serde(rename = "evToSales")]
pub ev_to_sales: Option<f64>,
#[serde(rename = "enterpriseValueOverEBITDA")]
pub enterprise_value_over_ebitda: Option<f64>,
#[serde(rename = "evToOperatingCashFlow")]
pub ev_to_operating_cash_flow: Option<f64>,
#[serde(rename = "evToFreeCashFlow")]
pub ev_to_free_cash_flow: Option<f64>,
#[serde(rename = "earningsYield")]
pub earnings_yield: Option<f64>,
#[serde(rename = "freeCashFlowYield")]
pub free_cash_flow_yield: Option<f64>,
#[serde(rename = "debtToEquity")]
pub debt_to_equity: Option<f64>,
#[serde(rename = "debtToAssets")]
pub debt_to_assets: Option<f64>,
#[serde(rename = "netDebtToEBITDA")]
pub net_debt_to_ebitda: Option<f64>,
#[serde(rename = "currentRatio")]
pub current_ratio: Option<f64>,
#[serde(rename = "dividendYield")]
pub dividend_yield: Option<f64>,
#[serde(rename = "payoutRatio")]
pub payout_ratio: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct EnterpriseValue {
pub symbol: Option<String>,
pub date: Option<String>,
#[serde(rename = "stockPrice")]
pub stock_price: Option<f64>,
#[serde(rename = "numberOfShares")]
pub number_of_shares: Option<f64>,
#[serde(rename = "marketCapitalization")]
pub market_capitalization: Option<f64>,
#[serde(rename = "minusCashAndCashEquivalents")]
pub minus_cash_and_cash_equivalents: Option<f64>,
#[serde(rename = "addTotalDebt")]
pub add_total_debt: Option<f64>,
#[serde(rename = "enterpriseValue")]
pub enterprise_value: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct DiscountedCashFlow {
pub symbol: Option<String>,
pub date: Option<String>,
pub dcf: Option<f64>,
#[serde(rename = "Stock Price")]
pub stock_price: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HistoricalDcf {
pub symbol: Option<String>,
pub date: Option<String>,
pub dcf: Option<f64>,
pub price: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CompanyRating {
pub symbol: Option<String>,
pub date: Option<String>,
pub rating: Option<String>,
#[serde(rename = "ratingScore")]
pub rating_score: Option<i32>,
#[serde(rename = "ratingRecommendation")]
pub rating_recommendation: Option<String>,
#[serde(rename = "ratingDetailsDCFScore")]
pub rating_details_dcf_score: Option<i32>,
#[serde(rename = "ratingDetailsDCFRecommendation")]
pub rating_details_dcf_recommendation: Option<String>,
#[serde(rename = "ratingDetailsROEScore")]
pub rating_details_roe_score: Option<i32>,
#[serde(rename = "ratingDetailsROERecommendation")]
pub rating_details_roe_recommendation: Option<String>,
#[serde(rename = "ratingDetailsROAScore")]
pub rating_details_roa_score: Option<i32>,
#[serde(rename = "ratingDetailsROARecommendation")]
pub rating_details_roa_recommendation: Option<String>,
#[serde(rename = "ratingDetailsDEScore")]
pub rating_details_de_score: Option<i32>,
#[serde(rename = "ratingDetailsDERecommendation")]
pub rating_details_de_recommendation: Option<String>,
#[serde(rename = "ratingDetailsPEScore")]
pub rating_details_pe_score: Option<i32>,
#[serde(rename = "ratingDetailsPERecommendation")]
pub rating_details_pe_recommendation: Option<String>,
#[serde(rename = "ratingDetailsPBScore")]
pub rating_details_pb_score: Option<i32>,
#[serde(rename = "ratingDetailsPBRecommendation")]
pub rating_details_pb_recommendation: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FinancialGrowth {
pub symbol: Option<String>,
pub date: Option<String>,
#[serde(rename = "calendarYear")]
pub calendar_year: Option<String>,
pub period: Option<String>,
#[serde(rename = "revenueGrowth")]
pub revenue_growth: Option<f64>,
#[serde(rename = "grossProfitGrowth")]
pub gross_profit_growth: Option<f64>,
#[serde(rename = "ebitgrowth")]
pub ebit_growth: Option<f64>,
#[serde(rename = "operatingIncomeGrowth")]
pub operating_income_growth: Option<f64>,
#[serde(rename = "netIncomeGrowth")]
pub net_income_growth: Option<f64>,
#[serde(rename = "epsgrowth")]
pub eps_growth: Option<f64>,
#[serde(rename = "epsdilutedGrowth")]
pub eps_diluted_growth: Option<f64>,
#[serde(rename = "weightedAverageSharesGrowth")]
pub weighted_average_shares_growth: Option<f64>,
#[serde(rename = "weightedAverageSharesDilutedGrowth")]
pub weighted_average_shares_diluted_growth: Option<f64>,
#[serde(rename = "dividendsperShareGrowth")]
pub dividends_per_share_growth: Option<f64>,
#[serde(rename = "operatingCashFlowGrowth")]
pub operating_cash_flow_growth: Option<f64>,
#[serde(rename = "freeCashFlowGrowth")]
pub free_cash_flow_growth: Option<f64>,
#[serde(rename = "receivablesGrowth")]
pub receivables_growth: Option<f64>,
#[serde(rename = "inventoryGrowth")]
pub inventory_growth: Option<f64>,
#[serde(rename = "assetGrowth")]
pub asset_growth: Option<f64>,
#[serde(rename = "bookValueperShareGrowth")]
pub book_value_per_share_growth: Option<f64>,
#[serde(rename = "debtGrowth")]
pub debt_growth: Option<f64>,
#[serde(rename = "rdexpenseGrowth")]
pub rd_expense_growth: Option<f64>,
#[serde(rename = "sgaexpensesGrowth")]
pub sga_expenses_growth: Option<f64>,
}
pub async fn financial_ratios(
symbol: &str,
period: Period,
limit: Option<u32>,
) -> Result<Vec<FinancialRatios>> {
let client = super::build_client()?;
let limit_str = limit.unwrap_or(4).to_string();
client
.get(
&format!("/api/v3/ratios/{}", encode_path_segment(symbol)),
&[("period", period.as_str()), ("limit", &limit_str)],
)
.await
}
pub async fn key_metrics(
symbol: &str,
period: Period,
limit: Option<u32>,
) -> Result<Vec<KeyMetrics>> {
let client = super::build_client()?;
let limit_str = limit.unwrap_or(4).to_string();
client
.get(
&format!("/api/v3/key-metrics/{}", encode_path_segment(symbol)),
&[("period", period.as_str()), ("limit", &limit_str)],
)
.await
}
pub async fn enterprise_value(
symbol: &str,
period: Period,
limit: Option<u32>,
) -> Result<Vec<EnterpriseValue>> {
let client = super::build_client()?;
let limit_str = limit.unwrap_or(4).to_string();
client
.get(
&format!("/api/v3/enterprise-values/{}", encode_path_segment(symbol)),
&[("period", period.as_str()), ("limit", &limit_str)],
)
.await
}
pub async fn discounted_cash_flow(symbol: &str) -> Result<Vec<DiscountedCashFlow>> {
let client = super::build_client()?;
client
.get(
&format!(
"/api/v3/discounted-cash-flow/{}",
encode_path_segment(symbol)
),
&[],
)
.await
}
pub async fn historical_dcf(
symbol: &str,
period: Period,
limit: Option<u32>,
) -> Result<Vec<HistoricalDcf>> {
let client = super::build_client()?;
let limit_str = limit.unwrap_or(10).to_string();
client
.get(
&format!(
"/api/v3/historical-discounted-cash-flow-statement/{}",
encode_path_segment(symbol)
),
&[("period", period.as_str()), ("limit", &limit_str)],
)
.await
}
pub async fn company_rating(symbol: &str) -> Result<Vec<CompanyRating>> {
let client = super::build_client()?;
client
.get(
&format!("/api/v3/rating/{}", encode_path_segment(symbol)),
&[],
)
.await
}
pub async fn historical_rating(symbol: &str, limit: Option<u32>) -> Result<Vec<CompanyRating>> {
let client = super::build_client()?;
let limit_str = limit.unwrap_or(100).to_string();
client
.get(
&format!("/api/v3/historical-rating/{}", encode_path_segment(symbol)),
&[("limit", &limit_str)],
)
.await
}
pub async fn financial_growth(
symbol: &str,
period: Period,
limit: Option<u32>,
) -> Result<Vec<FinancialGrowth>> {
let client = super::build_client()?;
let limit_str = limit.unwrap_or(4).to_string();
client
.get(
&format!("/api/v3/financial-growth/{}", encode_path_segment(symbol)),
&[("period", period.as_str()), ("limit", &limit_str)],
)
.await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_financial_ratios_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/ratios/AAPL")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("period".into(), "annual".into()),
mockito::Matcher::UrlEncoded("limit".into(), "1".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([{
"symbol": "AAPL",
"date": "2024-09-28",
"calendarYear": "2024",
"period": "FY",
"currentRatio": 0.8673,
"quickRatio": 0.8268,
"grossProfitMargin": 0.4623,
"netProfitMargin": 0.2395,
"returnOnEquity": 1.6067,
"priceEarningsRatio": 34.12,
"dividendYield": 0.0044
}])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let result: Vec<FinancialRatios> = client
.get(
"/api/v3/ratios/AAPL",
&[("period", "annual"), ("limit", "1")],
)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].symbol.as_deref(), Some("AAPL"));
assert_eq!(result[0].gross_profit_margin, Some(0.4623));
assert_eq!(result[0].price_earnings_ratio, Some(34.12));
}
#[tokio::test]
async fn test_company_rating_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/rating/AAPL")
.match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
"apikey".into(),
"test-key".into(),
)]))
.with_status(200)
.with_body(
serde_json::json!([{
"symbol": "AAPL",
"date": "2024-12-01",
"rating": "S",
"ratingScore": 5,
"ratingRecommendation": "Strong Buy",
"ratingDetailsDCFScore": 5,
"ratingDetailsDCFRecommendation": "Strong Buy",
"ratingDetailsROEScore": 5,
"ratingDetailsROERecommendation": "Strong Buy"
}])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let result: Vec<CompanyRating> = client.get("/api/v3/rating/AAPL", &[]).await.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].rating.as_deref(), Some("S"));
assert_eq!(result[0].rating_score, Some(5));
assert_eq!(
result[0].rating_recommendation.as_deref(),
Some("Strong Buy")
);
}
}