Skip to main content

finance_query/models/fundamentals/
financial_data.rs

1use crate::models::format::{Both, Format};
2use finance_query_derive::FormatConvert;
3use serde::{Deserialize, Serialize};
4
5/// Financial data and key metrics
6///
7/// Contains financial ratios, margins, cash flow, and analyst recommendations.
8///
9/// The type parameter `F` controls how numeric fields are represented:
10/// - `FinancialData` / `FinancialData<Both>` — **default**; fields hold `FormattedValue<T>`
11/// - `FinancialData<Raw>` — fields hold `T` directly (e.g. `Option<f64>`)
12/// - `FinancialData<Pretty>` — fields hold `Option<String>` (human-readable)
13#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, FormatConvert)]
14#[serde(rename_all = "camelCase", bound = "")]
15#[non_exhaustive]
16pub struct FinancialData<F: Format = Both> {
17    /// Current stock price
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub current_price: Option<F::Value<f64>>,
20
21    /// Current ratio (current assets / current liabilities)
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub current_ratio: Option<F::Value<f64>>,
24
25    /// Debt to equity ratio
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub debt_to_equity: Option<F::Value<f64>>,
28
29    /// Earnings growth rate
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub earnings_growth: Option<F::Value<f64>>,
32
33    /// EBITDA (Earnings Before Interest, Taxes, Depreciation, and Amortization)
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub ebitda: Option<F::Value<i64>>,
36
37    /// EBITDA margins
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub ebitda_margins: Option<F::Value<f64>>,
40
41    /// Currency code for financial data
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub financial_currency: Option<String>,
44
45    /// Free cash flow
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub free_cashflow: Option<F::Value<i64>>,
48
49    /// Gross profit margins
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub gross_margins: Option<F::Value<f64>>,
52
53    /// Total gross profits
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub gross_profits: Option<F::Value<i64>>,
56
57    /// Maximum age of data in seconds
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub max_age: Option<i64>,
60
61    /// Number of analyst opinions
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub number_of_analyst_opinions: Option<F::Value<i64>>,
64
65    /// Operating cash flow
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub operating_cashflow: Option<F::Value<i64>>,
68
69    /// Operating margins
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub operating_margins: Option<F::Value<f64>>,
72
73    /// Profit margins
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub profit_margins: Option<F::Value<f64>>,
76
77    /// Quick ratio (quick assets / current liabilities)
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub quick_ratio: Option<F::Value<f64>>,
80
81    /// Recommendation key (e.g., "buy", "hold", "sell")
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub recommendation_key: Option<String>,
84
85    /// Mean analyst recommendation (1.0 = strong buy, 5.0 = sell)
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub recommendation_mean: Option<F::Value<f64>>,
88
89    /// Return on assets (ROA)
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub return_on_assets: Option<F::Value<f64>>,
92
93    /// Return on equity (ROE)
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub return_on_equity: Option<F::Value<f64>>,
96
97    /// Revenue growth rate
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub revenue_growth: Option<F::Value<f64>>,
100
101    /// Revenue per share
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub revenue_per_share: Option<F::Value<f64>>,
104
105    /// Highest analyst price target
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub target_high_price: Option<F::Value<f64>>,
108
109    /// Lowest analyst price target
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub target_low_price: Option<F::Value<f64>>,
112
113    /// Mean analyst price target
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub target_mean_price: Option<F::Value<f64>>,
116
117    /// Median analyst price target
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub target_median_price: Option<F::Value<f64>>,
120
121    /// Total cash and cash equivalents
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub total_cash: Option<F::Value<i64>>,
124
125    /// Total cash per share
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub total_cash_per_share: Option<F::Value<f64>>,
128
129    /// Total debt
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub total_debt: Option<F::Value<i64>>,
132
133    /// Total revenue
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub total_revenue: Option<F::Value<i64>>,
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_deserialize_financial_data() {
144        let json = r#"{
145            "currentPrice": {
146                "fmt": "276.97",
147                "raw": 276.97
148            },
149            "ebitda": {
150                "fmt": "144.75B",
151                "longFmt": "144,748,003,328",
152                "raw": 144748003328
153            },
154            "financialCurrency": "USD",
155            "recommendationKey": "buy"
156        }"#;
157
158        let data: FinancialData = serde_json::from_str(json).unwrap();
159        assert_eq!(
160            data.current_price.as_ref().map(|v| v.raw),
161            Some(Some(276.97))
162        );
163        assert_eq!(
164            data.ebitda.as_ref().map(|v| v.raw),
165            Some(Some(144748003328))
166        );
167        assert_eq!(data.financial_currency.as_deref(), Some("USD"));
168        assert_eq!(data.recommendation_key.as_deref(), Some("buy"));
169    }
170
171    #[test]
172    fn test_into_raw() {
173        let json = r#"{
174            "currentPrice": {"fmt": "276.97", "raw": 276.97},
175            "financialCurrency": "USD",
176            "recommendationKey": "buy"
177        }"#;
178
179        let data: FinancialData = serde_json::from_str(json).unwrap();
180        let raw = data.into_raw();
181        assert_eq!(raw.current_price, Some(276.97));
182        assert_eq!(raw.financial_currency.as_deref(), Some("USD"));
183        assert_eq!(raw.recommendation_key.as_deref(), Some("buy"));
184    }
185
186    #[test]
187    fn test_into_pretty() {
188        let json = r#"{
189            "currentPrice": {"fmt": "276.97", "raw": 276.97},
190            "ebitda": {"fmt": "144.75B", "longFmt": "144,748,003,328", "raw": 144748003328}
191        }"#;
192
193        let data: FinancialData = serde_json::from_str(json).unwrap();
194        let pretty = data.into_pretty();
195        assert_eq!(pretty.current_price.as_deref(), Some("276.97"));
196        assert_eq!(pretty.ebitda.as_deref(), Some("144.75B"));
197    }
198}