1use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::models::Period;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
16#[non_exhaustive]
17pub struct FinancialRatios {
18 pub symbol: Option<String>,
20 pub date: Option<String>,
22 #[serde(rename = "calendarYear")]
24 pub calendar_year: Option<String>,
25 pub period: Option<String>,
27 #[serde(rename = "currentRatio")]
29 pub current_ratio: Option<f64>,
30 #[serde(rename = "quickRatio")]
32 pub quick_ratio: Option<f64>,
33 #[serde(rename = "cashRatio")]
35 pub cash_ratio: Option<f64>,
36 #[serde(rename = "grossProfitMargin")]
38 pub gross_profit_margin: Option<f64>,
39 #[serde(rename = "operatingProfitMargin")]
41 pub operating_profit_margin: Option<f64>,
42 #[serde(rename = "netProfitMargin")]
44 pub net_profit_margin: Option<f64>,
45 #[serde(rename = "returnOnAssets")]
47 pub return_on_assets: Option<f64>,
48 #[serde(rename = "returnOnEquity")]
50 pub return_on_equity: Option<f64>,
51 #[serde(rename = "returnOnCapitalEmployed")]
53 pub return_on_capital_employed: Option<f64>,
54 #[serde(rename = "debtEquityRatio")]
56 pub debt_equity_ratio: Option<f64>,
57 #[serde(rename = "debtRatio")]
59 pub debt_ratio: Option<f64>,
60 #[serde(rename = "priceEarningsRatio")]
62 pub price_earnings_ratio: Option<f64>,
63 #[serde(rename = "priceToBookRatio")]
65 pub price_to_book_ratio: Option<f64>,
66 #[serde(rename = "priceToSalesRatio")]
68 pub price_to_sales_ratio: Option<f64>,
69 #[serde(rename = "priceToFreeCashFlowsRatio")]
71 pub price_to_free_cash_flows_ratio: Option<f64>,
72 #[serde(rename = "enterpriseValueMultiple")]
74 pub enterprise_value_multiple: Option<f64>,
75 #[serde(rename = "dividendYield")]
77 pub dividend_yield: Option<f64>,
78 #[serde(rename = "payoutRatio")]
80 pub payout_ratio: Option<f64>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85#[non_exhaustive]
86pub struct KeyMetrics {
87 pub symbol: Option<String>,
89 pub date: Option<String>,
91 #[serde(rename = "calendarYear")]
93 pub calendar_year: Option<String>,
94 pub period: Option<String>,
96 #[serde(rename = "revenuePerShare")]
98 pub revenue_per_share: Option<f64>,
99 #[serde(rename = "netIncomePerShare")]
101 pub net_income_per_share: Option<f64>,
102 #[serde(rename = "operatingCashFlowPerShare")]
104 pub operating_cash_flow_per_share: Option<f64>,
105 #[serde(rename = "freeCashFlowPerShare")]
107 pub free_cash_flow_per_share: Option<f64>,
108 #[serde(rename = "cashPerShare")]
110 pub cash_per_share: Option<f64>,
111 #[serde(rename = "bookValuePerShare")]
113 pub book_value_per_share: Option<f64>,
114 #[serde(rename = "tangibleBookValuePerShare")]
116 pub tangible_book_value_per_share: Option<f64>,
117 #[serde(rename = "shareholdersEquityPerShare")]
119 pub shareholders_equity_per_share: Option<f64>,
120 #[serde(rename = "interestDebtPerShare")]
122 pub interest_debt_per_share: Option<f64>,
123 #[serde(rename = "marketCap")]
125 pub market_cap: Option<f64>,
126 #[serde(rename = "enterpriseValue")]
128 pub enterprise_value: Option<f64>,
129 #[serde(rename = "peRatio")]
131 pub pe_ratio: Option<f64>,
132 #[serde(rename = "pbRatio")]
134 pub pb_ratio: Option<f64>,
135 #[serde(rename = "evToSales")]
137 pub ev_to_sales: Option<f64>,
138 #[serde(rename = "enterpriseValueOverEBITDA")]
140 pub enterprise_value_over_ebitda: Option<f64>,
141 #[serde(rename = "evToOperatingCashFlow")]
143 pub ev_to_operating_cash_flow: Option<f64>,
144 #[serde(rename = "evToFreeCashFlow")]
146 pub ev_to_free_cash_flow: Option<f64>,
147 #[serde(rename = "earningsYield")]
149 pub earnings_yield: Option<f64>,
150 #[serde(rename = "freeCashFlowYield")]
152 pub free_cash_flow_yield: Option<f64>,
153 #[serde(rename = "debtToEquity")]
155 pub debt_to_equity: Option<f64>,
156 #[serde(rename = "debtToAssets")]
158 pub debt_to_assets: Option<f64>,
159 #[serde(rename = "netDebtToEBITDA")]
161 pub net_debt_to_ebitda: Option<f64>,
162 #[serde(rename = "currentRatio")]
164 pub current_ratio: Option<f64>,
165 #[serde(rename = "dividendYield")]
167 pub dividend_yield: Option<f64>,
168 #[serde(rename = "payoutRatio")]
170 pub payout_ratio: Option<f64>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175#[non_exhaustive]
176pub struct EnterpriseValue {
177 pub symbol: Option<String>,
179 pub date: Option<String>,
181 #[serde(rename = "stockPrice")]
183 pub stock_price: Option<f64>,
184 #[serde(rename = "numberOfShares")]
186 pub number_of_shares: Option<f64>,
187 #[serde(rename = "marketCapitalization")]
189 pub market_capitalization: Option<f64>,
190 #[serde(rename = "minusCashAndCashEquivalents")]
192 pub minus_cash_and_cash_equivalents: Option<f64>,
193 #[serde(rename = "addTotalDebt")]
195 pub add_total_debt: Option<f64>,
196 #[serde(rename = "enterpriseValue")]
198 pub enterprise_value: Option<f64>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203#[non_exhaustive]
204pub struct DiscountedCashFlow {
205 pub symbol: Option<String>,
207 pub date: Option<String>,
209 pub dcf: Option<f64>,
211 #[serde(rename = "Stock Price")]
213 pub stock_price: Option<f64>,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218#[non_exhaustive]
219pub struct HistoricalDcf {
220 pub symbol: Option<String>,
222 pub date: Option<String>,
224 pub dcf: Option<f64>,
226 pub price: Option<f64>,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232#[non_exhaustive]
233pub struct CompanyRating {
234 pub symbol: Option<String>,
236 pub date: Option<String>,
238 pub rating: Option<String>,
240 #[serde(rename = "ratingScore")]
242 pub rating_score: Option<i32>,
243 #[serde(rename = "ratingRecommendation")]
245 pub rating_recommendation: Option<String>,
246 #[serde(rename = "ratingDetailsDCFScore")]
248 pub rating_details_dcf_score: Option<i32>,
249 #[serde(rename = "ratingDetailsDCFRecommendation")]
251 pub rating_details_dcf_recommendation: Option<String>,
252 #[serde(rename = "ratingDetailsROEScore")]
254 pub rating_details_roe_score: Option<i32>,
255 #[serde(rename = "ratingDetailsROERecommendation")]
257 pub rating_details_roe_recommendation: Option<String>,
258 #[serde(rename = "ratingDetailsROAScore")]
260 pub rating_details_roa_score: Option<i32>,
261 #[serde(rename = "ratingDetailsROARecommendation")]
263 pub rating_details_roa_recommendation: Option<String>,
264 #[serde(rename = "ratingDetailsDEScore")]
266 pub rating_details_de_score: Option<i32>,
267 #[serde(rename = "ratingDetailsDERecommendation")]
269 pub rating_details_de_recommendation: Option<String>,
270 #[serde(rename = "ratingDetailsPEScore")]
272 pub rating_details_pe_score: Option<i32>,
273 #[serde(rename = "ratingDetailsPERecommendation")]
275 pub rating_details_pe_recommendation: Option<String>,
276 #[serde(rename = "ratingDetailsPBScore")]
278 pub rating_details_pb_score: Option<i32>,
279 #[serde(rename = "ratingDetailsPBRecommendation")]
281 pub rating_details_pb_recommendation: Option<String>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286#[non_exhaustive]
287pub struct FinancialGrowth {
288 pub symbol: Option<String>,
290 pub date: Option<String>,
292 #[serde(rename = "calendarYear")]
294 pub calendar_year: Option<String>,
295 pub period: Option<String>,
297 #[serde(rename = "revenueGrowth")]
299 pub revenue_growth: Option<f64>,
300 #[serde(rename = "grossProfitGrowth")]
302 pub gross_profit_growth: Option<f64>,
303 #[serde(rename = "ebitgrowth")]
305 pub ebit_growth: Option<f64>,
306 #[serde(rename = "operatingIncomeGrowth")]
308 pub operating_income_growth: Option<f64>,
309 #[serde(rename = "netIncomeGrowth")]
311 pub net_income_growth: Option<f64>,
312 #[serde(rename = "epsgrowth")]
314 pub eps_growth: Option<f64>,
315 #[serde(rename = "epsdilutedGrowth")]
317 pub eps_diluted_growth: Option<f64>,
318 #[serde(rename = "weightedAverageSharesGrowth")]
320 pub weighted_average_shares_growth: Option<f64>,
321 #[serde(rename = "weightedAverageSharesDilutedGrowth")]
323 pub weighted_average_shares_diluted_growth: Option<f64>,
324 #[serde(rename = "dividendsperShareGrowth")]
326 pub dividends_per_share_growth: Option<f64>,
327 #[serde(rename = "operatingCashFlowGrowth")]
329 pub operating_cash_flow_growth: Option<f64>,
330 #[serde(rename = "freeCashFlowGrowth")]
332 pub free_cash_flow_growth: Option<f64>,
333 #[serde(rename = "receivablesGrowth")]
335 pub receivables_growth: Option<f64>,
336 #[serde(rename = "inventoryGrowth")]
338 pub inventory_growth: Option<f64>,
339 #[serde(rename = "assetGrowth")]
341 pub asset_growth: Option<f64>,
342 #[serde(rename = "bookValueperShareGrowth")]
344 pub book_value_per_share_growth: Option<f64>,
345 #[serde(rename = "debtGrowth")]
347 pub debt_growth: Option<f64>,
348 #[serde(rename = "rdexpenseGrowth")]
350 pub rd_expense_growth: Option<f64>,
351 #[serde(rename = "sgaexpensesGrowth")]
353 pub sga_expenses_growth: Option<f64>,
354}
355
356pub async fn financial_ratios(
362 symbol: &str,
363 period: Period,
364 limit: Option<u32>,
365) -> Result<Vec<FinancialRatios>> {
366 let client = super::build_client()?;
367 let limit_str = limit.unwrap_or(4).to_string();
368 client
369 .get(
370 &format!("/api/v3/ratios/{}", encode_path_segment(symbol)),
371 &[("period", period.as_str()), ("limit", &limit_str)],
372 )
373 .await
374}
375
376pub async fn key_metrics(
378 symbol: &str,
379 period: Period,
380 limit: Option<u32>,
381) -> Result<Vec<KeyMetrics>> {
382 let client = super::build_client()?;
383 let limit_str = limit.unwrap_or(4).to_string();
384 client
385 .get(
386 &format!("/api/v3/key-metrics/{}", encode_path_segment(symbol)),
387 &[("period", period.as_str()), ("limit", &limit_str)],
388 )
389 .await
390}
391
392pub async fn enterprise_value(
394 symbol: &str,
395 period: Period,
396 limit: Option<u32>,
397) -> Result<Vec<EnterpriseValue>> {
398 let client = super::build_client()?;
399 let limit_str = limit.unwrap_or(4).to_string();
400 client
401 .get(
402 &format!("/api/v3/enterprise-values/{}", encode_path_segment(symbol)),
403 &[("period", period.as_str()), ("limit", &limit_str)],
404 )
405 .await
406}
407
408pub async fn discounted_cash_flow(symbol: &str) -> Result<Vec<DiscountedCashFlow>> {
410 let client = super::build_client()?;
411 client
412 .get(
413 &format!(
414 "/api/v3/discounted-cash-flow/{}",
415 encode_path_segment(symbol)
416 ),
417 &[],
418 )
419 .await
420}
421
422pub async fn historical_dcf(
424 symbol: &str,
425 period: Period,
426 limit: Option<u32>,
427) -> Result<Vec<HistoricalDcf>> {
428 let client = super::build_client()?;
429 let limit_str = limit.unwrap_or(10).to_string();
430 client
431 .get(
432 &format!(
433 "/api/v3/historical-discounted-cash-flow-statement/{}",
434 encode_path_segment(symbol)
435 ),
436 &[("period", period.as_str()), ("limit", &limit_str)],
437 )
438 .await
439}
440
441pub async fn company_rating(symbol: &str) -> Result<Vec<CompanyRating>> {
443 let client = super::build_client()?;
444 client
445 .get(
446 &format!("/api/v3/rating/{}", encode_path_segment(symbol)),
447 &[],
448 )
449 .await
450}
451
452pub async fn historical_rating(symbol: &str, limit: Option<u32>) -> Result<Vec<CompanyRating>> {
454 let client = super::build_client()?;
455 let limit_str = limit.unwrap_or(100).to_string();
456 client
457 .get(
458 &format!("/api/v3/historical-rating/{}", encode_path_segment(symbol)),
459 &[("limit", &limit_str)],
460 )
461 .await
462}
463
464pub async fn financial_growth(
466 symbol: &str,
467 period: Period,
468 limit: Option<u32>,
469) -> Result<Vec<FinancialGrowth>> {
470 let client = super::build_client()?;
471 let limit_str = limit.unwrap_or(4).to_string();
472 client
473 .get(
474 &format!("/api/v3/financial-growth/{}", encode_path_segment(symbol)),
475 &[("period", period.as_str()), ("limit", &limit_str)],
476 )
477 .await
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483
484 #[tokio::test]
485 async fn test_financial_ratios_mock() {
486 let mut server = mockito::Server::new_async().await;
487 let _mock = server
488 .mock("GET", "/api/v3/ratios/AAPL")
489 .match_query(mockito::Matcher::AllOf(vec![
490 mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
491 mockito::Matcher::UrlEncoded("period".into(), "annual".into()),
492 mockito::Matcher::UrlEncoded("limit".into(), "1".into()),
493 ]))
494 .with_status(200)
495 .with_body(
496 serde_json::json!([{
497 "symbol": "AAPL",
498 "date": "2024-09-28",
499 "calendarYear": "2024",
500 "period": "FY",
501 "currentRatio": 0.8673,
502 "quickRatio": 0.8268,
503 "grossProfitMargin": 0.4623,
504 "netProfitMargin": 0.2395,
505 "returnOnEquity": 1.6067,
506 "priceEarningsRatio": 34.12,
507 "dividendYield": 0.0044
508 }])
509 .to_string(),
510 )
511 .create_async()
512 .await;
513
514 let client = super::super::build_test_client(&server.url()).unwrap();
515 let result: Vec<FinancialRatios> = client
516 .get(
517 "/api/v3/ratios/AAPL",
518 &[("period", "annual"), ("limit", "1")],
519 )
520 .await
521 .unwrap();
522
523 assert_eq!(result.len(), 1);
524 assert_eq!(result[0].symbol.as_deref(), Some("AAPL"));
525 assert_eq!(result[0].gross_profit_margin, Some(0.4623));
526 assert_eq!(result[0].price_earnings_ratio, Some(34.12));
527 }
528
529 #[tokio::test]
530 async fn test_company_rating_mock() {
531 let mut server = mockito::Server::new_async().await;
532 let _mock = server
533 .mock("GET", "/api/v3/rating/AAPL")
534 .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
535 "apikey".into(),
536 "test-key".into(),
537 )]))
538 .with_status(200)
539 .with_body(
540 serde_json::json!([{
541 "symbol": "AAPL",
542 "date": "2024-12-01",
543 "rating": "S",
544 "ratingScore": 5,
545 "ratingRecommendation": "Strong Buy",
546 "ratingDetailsDCFScore": 5,
547 "ratingDetailsDCFRecommendation": "Strong Buy",
548 "ratingDetailsROEScore": 5,
549 "ratingDetailsROERecommendation": "Strong Buy"
550 }])
551 .to_string(),
552 )
553 .create_async()
554 .await;
555
556 let client = super::super::build_test_client(&server.url()).unwrap();
557 let result: Vec<CompanyRating> = client.get("/api/v3/rating/AAPL", &[]).await.unwrap();
558
559 assert_eq!(result.len(), 1);
560 assert_eq!(result[0].rating.as_deref(), Some("S"));
561 assert_eq!(result[0].rating_score, Some(5));
562 assert_eq!(
563 result[0].rating_recommendation.as_deref(),
564 Some("Strong Buy")
565 );
566 }
567}