Skip to main content

finance_query/adapters/fmp/
fundamentals.rs

1//! FMP financial statement endpoints.
2
3use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::models::Period;
9
10// ============================================================================
11// Response types
12// ============================================================================
13
14/// Income statement from FMP.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[non_exhaustive]
17pub struct IncomeStatement {
18    /// Filing date.
19    pub date: Option<String>,
20    /// Ticker symbol.
21    pub symbol: Option<String>,
22    /// Reporting period (annual/quarter).
23    #[serde(rename = "reportedCurrency")]
24    pub reported_currency: Option<String>,
25    /// CIK number.
26    pub cik: Option<String>,
27    /// Filing date.
28    #[serde(rename = "fillingDate")]
29    pub filling_date: Option<String>,
30    /// Accepted date.
31    #[serde(rename = "acceptedDate")]
32    pub accepted_date: Option<String>,
33    /// Calendar year.
34    #[serde(rename = "calendarYear")]
35    pub calendar_year: Option<String>,
36    /// Fiscal period (e.g., "Q1", "FY").
37    pub period: Option<String>,
38    /// Total revenue.
39    pub revenue: Option<f64>,
40    /// Cost of revenue.
41    #[serde(rename = "costOfRevenue")]
42    pub cost_of_revenue: Option<f64>,
43    /// Gross profit.
44    #[serde(rename = "grossProfit")]
45    pub gross_profit: Option<f64>,
46    /// Gross profit ratio.
47    #[serde(rename = "grossProfitRatio")]
48    pub gross_profit_ratio: Option<f64>,
49    /// Research and development expenses.
50    #[serde(rename = "researchAndDevelopmentExpenses")]
51    pub research_and_development_expenses: Option<f64>,
52    /// General and administrative expenses.
53    #[serde(rename = "generalAndAdministrativeExpenses")]
54    pub general_and_administrative_expenses: Option<f64>,
55    /// Selling and marketing expenses.
56    #[serde(rename = "sellingAndMarketingExpenses")]
57    pub selling_and_marketing_expenses: Option<f64>,
58    /// Selling, general and administrative expenses.
59    #[serde(rename = "sellingGeneralAndAdministrativeExpenses")]
60    pub selling_general_and_administrative_expenses: Option<f64>,
61    /// Other expenses.
62    #[serde(rename = "otherExpenses")]
63    pub other_expenses: Option<f64>,
64    /// Operating expenses.
65    #[serde(rename = "operatingExpenses")]
66    pub operating_expenses: Option<f64>,
67    /// Cost and expenses.
68    #[serde(rename = "costAndExpenses")]
69    pub cost_and_expenses: Option<f64>,
70    /// Interest income.
71    #[serde(rename = "interestIncome")]
72    pub interest_income: Option<f64>,
73    /// Interest expense.
74    #[serde(rename = "interestExpense")]
75    pub interest_expense: Option<f64>,
76    /// Depreciation and amortization.
77    #[serde(rename = "depreciationAndAmortization")]
78    pub depreciation_and_amortization: Option<f64>,
79    /// EBITDA.
80    pub ebitda: Option<f64>,
81    /// EBITDA ratio.
82    #[serde(rename = "ebitdaratio")]
83    pub ebitda_ratio: Option<f64>,
84    /// Operating income.
85    #[serde(rename = "operatingIncome")]
86    pub operating_income: Option<f64>,
87    /// Operating income ratio.
88    #[serde(rename = "operatingIncomeRatio")]
89    pub operating_income_ratio: Option<f64>,
90    /// Total other income/expenses net.
91    #[serde(rename = "totalOtherIncomeExpensesNet")]
92    pub total_other_income_expenses_net: Option<f64>,
93    /// Income before tax.
94    #[serde(rename = "incomeBeforeTax")]
95    pub income_before_tax: Option<f64>,
96    /// Income before tax ratio.
97    #[serde(rename = "incomeBeforeTaxRatio")]
98    pub income_before_tax_ratio: Option<f64>,
99    /// Income tax expense.
100    #[serde(rename = "incomeTaxExpense")]
101    pub income_tax_expense: Option<f64>,
102    /// Net income.
103    #[serde(rename = "netIncome")]
104    pub net_income: Option<f64>,
105    /// Net income ratio.
106    #[serde(rename = "netIncomeRatio")]
107    pub net_income_ratio: Option<f64>,
108    /// Earnings per share (basic).
109    pub eps: Option<f64>,
110    /// Earnings per share (diluted).
111    #[serde(rename = "epsdiluted")]
112    pub eps_diluted: Option<f64>,
113    /// Weighted average shares outstanding.
114    #[serde(rename = "weightedAverageShsOut")]
115    pub weighted_average_shs_out: Option<f64>,
116    /// Weighted average shares outstanding (diluted).
117    #[serde(rename = "weightedAverageShsOutDil")]
118    pub weighted_average_shs_out_dil: Option<f64>,
119    /// Link to SEC filing.
120    pub link: Option<String>,
121    /// Final link to filing.
122    #[serde(rename = "finalLink")]
123    pub final_link: Option<String>,
124}
125
126/// Balance sheet statement from FMP.
127#[derive(Debug, Clone, Serialize, Deserialize)]
128#[non_exhaustive]
129pub struct BalanceSheet {
130    /// Filing date.
131    pub date: Option<String>,
132    /// Ticker symbol.
133    pub symbol: Option<String>,
134    /// Reported currency.
135    #[serde(rename = "reportedCurrency")]
136    pub reported_currency: Option<String>,
137    /// CIK number.
138    pub cik: Option<String>,
139    /// Filing date.
140    #[serde(rename = "fillingDate")]
141    pub filling_date: Option<String>,
142    /// Accepted date.
143    #[serde(rename = "acceptedDate")]
144    pub accepted_date: Option<String>,
145    /// Calendar year.
146    #[serde(rename = "calendarYear")]
147    pub calendar_year: Option<String>,
148    /// Fiscal period.
149    pub period: Option<String>,
150    /// Cash and cash equivalents.
151    #[serde(rename = "cashAndCashEquivalents")]
152    pub cash_and_cash_equivalents: Option<f64>,
153    /// Short-term investments.
154    #[serde(rename = "shortTermInvestments")]
155    pub short_term_investments: Option<f64>,
156    /// Cash and short-term investments.
157    #[serde(rename = "cashAndShortTermInvestments")]
158    pub cash_and_short_term_investments: Option<f64>,
159    /// Net receivables.
160    #[serde(rename = "netReceivables")]
161    pub net_receivables: Option<f64>,
162    /// Inventory.
163    pub inventory: Option<f64>,
164    /// Other current assets.
165    #[serde(rename = "otherCurrentAssets")]
166    pub other_current_assets: Option<f64>,
167    /// Total current assets.
168    #[serde(rename = "totalCurrentAssets")]
169    pub total_current_assets: Option<f64>,
170    /// Property, plant and equipment net.
171    #[serde(rename = "propertyPlantEquipmentNet")]
172    pub property_plant_equipment_net: Option<f64>,
173    /// Goodwill.
174    pub goodwill: Option<f64>,
175    /// Intangible assets.
176    #[serde(rename = "intangibleAssets")]
177    pub intangible_assets: Option<f64>,
178    /// Goodwill and intangible assets.
179    #[serde(rename = "goodwillAndIntangibleAssets")]
180    pub goodwill_and_intangible_assets: Option<f64>,
181    /// Long-term investments.
182    #[serde(rename = "longTermInvestments")]
183    pub long_term_investments: Option<f64>,
184    /// Tax assets.
185    #[serde(rename = "taxAssets")]
186    pub tax_assets: Option<f64>,
187    /// Other non-current assets.
188    #[serde(rename = "otherNonCurrentAssets")]
189    pub other_non_current_assets: Option<f64>,
190    /// Total non-current assets.
191    #[serde(rename = "totalNonCurrentAssets")]
192    pub total_non_current_assets: Option<f64>,
193    /// Other assets.
194    #[serde(rename = "otherAssets")]
195    pub other_assets: Option<f64>,
196    /// Total assets.
197    #[serde(rename = "totalAssets")]
198    pub total_assets: Option<f64>,
199    /// Account payables.
200    #[serde(rename = "accountPayables")]
201    pub account_payables: Option<f64>,
202    /// Short-term debt.
203    #[serde(rename = "shortTermDebt")]
204    pub short_term_debt: Option<f64>,
205    /// Tax payables.
206    #[serde(rename = "taxPayables")]
207    pub tax_payables: Option<f64>,
208    /// Deferred revenue.
209    #[serde(rename = "deferredRevenue")]
210    pub deferred_revenue: Option<f64>,
211    /// Other current liabilities.
212    #[serde(rename = "otherCurrentLiabilities")]
213    pub other_current_liabilities: Option<f64>,
214    /// Total current liabilities.
215    #[serde(rename = "totalCurrentLiabilities")]
216    pub total_current_liabilities: Option<f64>,
217    /// Long-term debt.
218    #[serde(rename = "longTermDebt")]
219    pub long_term_debt: Option<f64>,
220    /// Deferred revenue non-current.
221    #[serde(rename = "deferredRevenueNonCurrent")]
222    pub deferred_revenue_non_current: Option<f64>,
223    /// Deferred tax liabilities non-current.
224    #[serde(rename = "deferredTaxLiabilitiesNonCurrent")]
225    pub deferred_tax_liabilities_non_current: Option<f64>,
226    /// Other non-current liabilities.
227    #[serde(rename = "otherNonCurrentLiabilities")]
228    pub other_non_current_liabilities: Option<f64>,
229    /// Total non-current liabilities.
230    #[serde(rename = "totalNonCurrentLiabilities")]
231    pub total_non_current_liabilities: Option<f64>,
232    /// Other liabilities.
233    #[serde(rename = "otherLiabilities")]
234    pub other_liabilities: Option<f64>,
235    /// Capital lease obligations.
236    #[serde(rename = "capitalLeaseObligations")]
237    pub capital_lease_obligations: Option<f64>,
238    /// Total liabilities.
239    #[serde(rename = "totalLiabilities")]
240    pub total_liabilities: Option<f64>,
241    /// Preferred stock.
242    #[serde(rename = "preferredStock")]
243    pub preferred_stock: Option<f64>,
244    /// Common stock.
245    #[serde(rename = "commonStock")]
246    pub common_stock: Option<f64>,
247    /// Retained earnings.
248    #[serde(rename = "retainedEarnings")]
249    pub retained_earnings: Option<f64>,
250    /// Accumulated other comprehensive income/loss.
251    #[serde(rename = "accumulatedOtherComprehensiveIncomeLoss")]
252    pub accumulated_other_comprehensive_income_loss: Option<f64>,
253    /// Other total stockholders equity.
254    #[serde(rename = "othertotalStockholdersEquity")]
255    pub other_total_stockholders_equity: Option<f64>,
256    /// Total stockholders equity.
257    #[serde(rename = "totalStockholdersEquity")]
258    pub total_stockholders_equity: Option<f64>,
259    /// Total equity.
260    #[serde(rename = "totalEquity")]
261    pub total_equity: Option<f64>,
262    /// Total liabilities and stockholders equity.
263    #[serde(rename = "totalLiabilitiesAndStockholdersEquity")]
264    pub total_liabilities_and_stockholders_equity: Option<f64>,
265    /// Minority interest.
266    #[serde(rename = "minorityInterest")]
267    pub minority_interest: Option<f64>,
268    /// Total liabilities and total equity.
269    #[serde(rename = "totalLiabilitiesAndTotalEquity")]
270    pub total_liabilities_and_total_equity: Option<f64>,
271    /// Total investments.
272    #[serde(rename = "totalInvestments")]
273    pub total_investments: Option<f64>,
274    /// Total debt.
275    #[serde(rename = "totalDebt")]
276    pub total_debt: Option<f64>,
277    /// Net debt.
278    #[serde(rename = "netDebt")]
279    pub net_debt: Option<f64>,
280    /// Link to SEC filing.
281    pub link: Option<String>,
282    /// Final link to filing.
283    #[serde(rename = "finalLink")]
284    pub final_link: Option<String>,
285}
286
287/// Cash flow statement from FMP.
288#[derive(Debug, Clone, Serialize, Deserialize)]
289#[non_exhaustive]
290pub struct CashFlow {
291    /// Filing date.
292    pub date: Option<String>,
293    /// Ticker symbol.
294    pub symbol: Option<String>,
295    /// Reported currency.
296    #[serde(rename = "reportedCurrency")]
297    pub reported_currency: Option<String>,
298    /// CIK number.
299    pub cik: Option<String>,
300    /// Filing date.
301    #[serde(rename = "fillingDate")]
302    pub filling_date: Option<String>,
303    /// Accepted date.
304    #[serde(rename = "acceptedDate")]
305    pub accepted_date: Option<String>,
306    /// Calendar year.
307    #[serde(rename = "calendarYear")]
308    pub calendar_year: Option<String>,
309    /// Fiscal period.
310    pub period: Option<String>,
311    /// Net income.
312    #[serde(rename = "netIncome")]
313    pub net_income: Option<f64>,
314    /// Depreciation and amortization.
315    #[serde(rename = "depreciationAndAmortization")]
316    pub depreciation_and_amortization: Option<f64>,
317    /// Deferred income tax.
318    #[serde(rename = "deferredIncomeTax")]
319    pub deferred_income_tax: Option<f64>,
320    /// Stock-based compensation.
321    #[serde(rename = "stockBasedCompensation")]
322    pub stock_based_compensation: Option<f64>,
323    /// Change in working capital.
324    #[serde(rename = "changeInWorkingCapital")]
325    pub change_in_working_capital: Option<f64>,
326    /// Accounts receivables.
327    #[serde(rename = "accountsReceivables")]
328    pub accounts_receivables: Option<f64>,
329    /// Inventory.
330    pub inventory: Option<f64>,
331    /// Accounts payables.
332    #[serde(rename = "accountsPayables")]
333    pub accounts_payables: Option<f64>,
334    /// Other working capital.
335    #[serde(rename = "otherWorkingCapital")]
336    pub other_working_capital: Option<f64>,
337    /// Other non-cash items.
338    #[serde(rename = "otherNonCashItems")]
339    pub other_non_cash_items: Option<f64>,
340    /// Net cash provided by operating activities.
341    #[serde(rename = "netCashProvidedByOperatingActivities")]
342    pub net_cash_provided_by_operating_activities: Option<f64>,
343    /// Investments in property, plant and equipment.
344    #[serde(rename = "investmentsInPropertyPlantAndEquipment")]
345    pub investments_in_property_plant_and_equipment: Option<f64>,
346    /// Acquisitions net.
347    #[serde(rename = "acquisitionsNet")]
348    pub acquisitions_net: Option<f64>,
349    /// Purchases of investments.
350    #[serde(rename = "purchasesOfInvestments")]
351    pub purchases_of_investments: Option<f64>,
352    /// Sales/maturities of investments.
353    #[serde(rename = "salesMaturitiesOfInvestments")]
354    pub sales_maturities_of_investments: Option<f64>,
355    /// Other investing activities.
356    #[serde(rename = "otherInvestingActivites")]
357    pub other_investing_activities: Option<f64>,
358    /// Net cash used for investing activities.
359    #[serde(rename = "netCashUsedForInvestingActivites")]
360    pub net_cash_used_for_investing_activities: Option<f64>,
361    /// Debt repayment.
362    #[serde(rename = "debtRepayment")]
363    pub debt_repayment: Option<f64>,
364    /// Common stock issued.
365    #[serde(rename = "commonStockIssued")]
366    pub common_stock_issued: Option<f64>,
367    /// Common stock repurchased.
368    #[serde(rename = "commonStockRepurchased")]
369    pub common_stock_repurchased: Option<f64>,
370    /// Dividends paid.
371    #[serde(rename = "dividendsPaid")]
372    pub dividends_paid: Option<f64>,
373    /// Other financing activities.
374    #[serde(rename = "otherFinancingActivites")]
375    pub other_financing_activities: Option<f64>,
376    /// Net cash used/provided by financing activities.
377    #[serde(rename = "netCashUsedProvidedByFinancingActivities")]
378    pub net_cash_used_provided_by_financing_activities: Option<f64>,
379    /// Effect of forex changes on cash.
380    #[serde(rename = "effectOfForexChangesOnCash")]
381    pub effect_of_forex_changes_on_cash: Option<f64>,
382    /// Net change in cash.
383    #[serde(rename = "netChangeInCash")]
384    pub net_change_in_cash: Option<f64>,
385    /// Cash at end of period.
386    #[serde(rename = "cashAtEndOfPeriod")]
387    pub cash_at_end_of_period: Option<f64>,
388    /// Cash at beginning of period.
389    #[serde(rename = "cashAtBeginningOfPeriod")]
390    pub cash_at_beginning_of_period: Option<f64>,
391    /// Operating cash flow.
392    #[serde(rename = "operatingCashFlow")]
393    pub operating_cash_flow: Option<f64>,
394    /// Capital expenditure.
395    #[serde(rename = "capitalExpenditure")]
396    pub capital_expenditure: Option<f64>,
397    /// Free cash flow.
398    #[serde(rename = "freeCashFlow")]
399    pub free_cash_flow: Option<f64>,
400    /// Link to SEC filing.
401    pub link: Option<String>,
402    /// Final link to filing.
403    #[serde(rename = "finalLink")]
404    pub final_link: Option<String>,
405}
406
407// ============================================================================
408// Query functions
409// ============================================================================
410
411/// Fetch income statements for a symbol.
412///
413/// Returns quarterly or annual income statements. FMP returns an array.
414pub async fn income_statement(
415    symbol: &str,
416    period: Period,
417    limit: Option<u32>,
418) -> Result<Vec<IncomeStatement>> {
419    let client = super::build_client()?;
420    let limit_str = limit.unwrap_or(4).to_string();
421    client
422        .get(
423            &format!("/api/v3/income-statement/{}", encode_path_segment(symbol)),
424            &[("period", period.as_str()), ("limit", &limit_str)],
425        )
426        .await
427}
428
429/// Fetch balance sheet statements for a symbol.
430pub async fn balance_sheet(
431    symbol: &str,
432    period: Period,
433    limit: Option<u32>,
434) -> Result<Vec<BalanceSheet>> {
435    let client = super::build_client()?;
436    let limit_str = limit.unwrap_or(4).to_string();
437    client
438        .get(
439            &format!(
440                "/api/v3/balance-sheet-statement/{}",
441                encode_path_segment(symbol)
442            ),
443            &[("period", period.as_str()), ("limit", &limit_str)],
444        )
445        .await
446}
447
448/// Fetch cash flow statements for a symbol.
449pub async fn cash_flow(symbol: &str, period: Period, limit: Option<u32>) -> Result<Vec<CashFlow>> {
450    let client = super::build_client()?;
451    let limit_str = limit.unwrap_or(4).to_string();
452    client
453        .get(
454            &format!(
455                "/api/v3/cash-flow-statement/{}",
456                encode_path_segment(symbol)
457            ),
458            &[("period", period.as_str()), ("limit", &limit_str)],
459        )
460        .await
461}
462
463/// Fetch income statements as reported for a symbol.
464pub async fn income_statement_as_reported(
465    symbol: &str,
466    period: Period,
467    limit: Option<u32>,
468) -> Result<Vec<serde_json::Value>> {
469    let client = super::build_client()?;
470    let limit_str = limit.unwrap_or(4).to_string();
471    client
472        .get(
473            &format!(
474                "/api/v3/income-statement-as-reported/{}",
475                encode_path_segment(symbol)
476            ),
477            &[("period", period.as_str()), ("limit", &limit_str)],
478        )
479        .await
480}
481
482/// Fetch balance sheet statements as reported for a symbol.
483pub async fn balance_sheet_as_reported(
484    symbol: &str,
485    period: Period,
486    limit: Option<u32>,
487) -> Result<Vec<serde_json::Value>> {
488    let client = super::build_client()?;
489    let limit_str = limit.unwrap_or(4).to_string();
490    client
491        .get(
492            &format!(
493                "/api/v3/balance-sheet-statement-as-reported/{}",
494                encode_path_segment(symbol)
495            ),
496            &[("period", period.as_str()), ("limit", &limit_str)],
497        )
498        .await
499}
500
501/// Fetch cash flow statements as reported for a symbol.
502pub async fn cash_flow_as_reported(
503    symbol: &str,
504    period: Period,
505    limit: Option<u32>,
506) -> Result<Vec<serde_json::Value>> {
507    let client = super::build_client()?;
508    let limit_str = limit.unwrap_or(4).to_string();
509    client
510        .get(
511            &format!(
512                "/api/v3/cash-flow-statement-as-reported/{}",
513                encode_path_segment(symbol)
514            ),
515            &[("period", period.as_str()), ("limit", &limit_str)],
516        )
517        .await
518}
519
520/// Fetch full financial statement as reported for a symbol.
521pub async fn full_financial_statement(
522    symbol: &str,
523    period: Period,
524) -> Result<Vec<serde_json::Value>> {
525    let client = super::build_client()?;
526    client
527        .get(
528            &format!(
529                "/api/v3/financial-statement-full-as-reported/{}",
530                encode_path_segment(symbol)
531            ),
532            &[("period", period.as_str())],
533        )
534        .await
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540
541    #[tokio::test]
542    async fn test_income_statement_mock() {
543        let mut server = mockito::Server::new_async().await;
544        let _mock = server
545            .mock("GET", "/api/v3/income-statement/AAPL")
546            .match_query(mockito::Matcher::AllOf(vec![
547                mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
548                mockito::Matcher::UrlEncoded("period".into(), "quarter".into()),
549                mockito::Matcher::UrlEncoded("limit".into(), "2".into()),
550            ]))
551            .with_status(200)
552            .with_body(
553                serde_json::json!([{
554                    "date": "2024-03-30",
555                    "symbol": "AAPL",
556                    "reportedCurrency": "USD",
557                    "calendarYear": "2024",
558                    "period": "Q2",
559                    "revenue": 90753000000.0,
560                    "costOfRevenue": 49141000000.0,
561                    "grossProfit": 41612000000.0,
562                    "netIncome": 23636000000.0,
563                    "eps": 1.53,
564                    "epsdiluted": 1.52
565                }])
566                .to_string(),
567            )
568            .create_async()
569            .await;
570
571        let client = super::super::build_test_client(&server.url()).unwrap();
572        let result: Vec<IncomeStatement> = client
573            .get(
574                "/api/v3/income-statement/AAPL",
575                &[("period", "quarter"), ("limit", "2")],
576            )
577            .await
578            .unwrap();
579
580        assert_eq!(result.len(), 1);
581        assert_eq!(result[0].symbol.as_deref(), Some("AAPL"));
582        assert_eq!(result[0].revenue, Some(90753000000.0));
583        assert_eq!(result[0].eps, Some(1.53));
584    }
585
586    #[tokio::test]
587    async fn test_balance_sheet_mock() {
588        let mut server = mockito::Server::new_async().await;
589        let _mock = server
590            .mock("GET", "/api/v3/balance-sheet-statement/AAPL")
591            .match_query(mockito::Matcher::AllOf(vec![
592                mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
593                mockito::Matcher::UrlEncoded("period".into(), "annual".into()),
594                mockito::Matcher::UrlEncoded("limit".into(), "1".into()),
595            ]))
596            .with_status(200)
597            .with_body(
598                serde_json::json!([{
599                    "date": "2024-09-28",
600                    "symbol": "AAPL",
601                    "totalAssets": 364980000000.0,
602                    "totalLiabilities": 308030000000.0,
603                    "totalStockholdersEquity": 56950000000.0,
604                    "cashAndCashEquivalents": 29943000000.0
605                }])
606                .to_string(),
607            )
608            .create_async()
609            .await;
610
611        let client = super::super::build_test_client(&server.url()).unwrap();
612        let result: Vec<BalanceSheet> = client
613            .get(
614                "/api/v3/balance-sheet-statement/AAPL",
615                &[("period", "annual"), ("limit", "1")],
616            )
617            .await
618            .unwrap();
619
620        assert_eq!(result.len(), 1);
621        assert_eq!(result[0].total_assets, Some(364980000000.0));
622        assert_eq!(result[0].total_stockholders_equity, Some(56950000000.0));
623    }
624}