Skip to main content

finance_query/models/fundamentals/
default_key_statistics.rs

1use crate::models::format::{Both, Format};
2use finance_query_derive::FormatConvert;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// Default key statistics for a symbol
7///
8/// Contains extensive statistical data including valuation metrics, share data, and financial ratios.
9///
10/// The type parameter `F` controls how numeric fields are represented:
11/// - `DefaultKeyStatistics` / `DefaultKeyStatistics<Both>` — **default**; fields hold `FormattedValue<T>`
12/// - `DefaultKeyStatistics<Raw>` — fields hold `T` directly (e.g. `Option<f64>`)
13/// - `DefaultKeyStatistics<Pretty>` — fields hold `Option<String>` (human-readable)
14#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, FormatConvert)]
15#[serde(rename_all = "camelCase", bound = "")]
16#[non_exhaustive]
17pub struct DefaultKeyStatistics<F: Format = Both> {
18    /// 52-week price change percentage
19    #[serde(rename = "52WeekChange", skip_serializing_if = "Option::is_none")]
20    pub week_52_change: Option<F::Value<f64>>,
21
22    /// S&P 500 52-week change percentage
23    #[serde(rename = "SandP52WeekChange", skip_serializing_if = "Option::is_none")]
24    pub sand_p_52_week_change: Option<F::Value<f64>>,
25
26    /// Annual holdings turnover (for funds)
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub annual_holdings_turnover: Option<Value>,
29
30    /// Annual report expense ratio (for funds)
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub annual_report_expense_ratio: Option<Value>,
33
34    /// Beta coefficient (volatility vs market)
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub beta: Option<F::Value<f64>>,
37
38    /// 3-year beta (for funds)
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub beta3_year: Option<Value>,
41
42    /// Book value per share
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub book_value: Option<F::Value<f64>>,
45
46    /// Fund category
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub category: Option<String>,
49
50    /// Date of short interest data
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub date_short_interest: Option<F::Value<i64>>,
53
54    /// Quarterly earnings growth rate
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub earnings_quarterly_growth: Option<F::Value<f64>>,
57
58    /// Enterprise value to EBITDA ratio
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub enterprise_to_ebitda: Option<F::Value<f64>>,
61
62    /// Enterprise value to revenue ratio
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub enterprise_to_revenue: Option<F::Value<f64>>,
65
66    /// Total enterprise value
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub enterprise_value: Option<F::Value<i64>>,
69
70    /// 5-year average return (for funds)
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub five_year_average_return: Option<Value>,
73
74    /// Number of floating shares
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub float_shares: Option<F::Value<i64>>,
77
78    /// Forward earnings per share
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub forward_eps: Option<F::Value<f64>>,
81
82    /// Forward price-to-earnings ratio
83    #[serde(rename = "forwardPE", skip_serializing_if = "Option::is_none")]
84    pub forward_pe: Option<F::Value<f64>>,
85
86    /// Fund family name
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub fund_family: Option<String>,
89
90    /// Fund inception date
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub fund_inception_date: Option<Value>,
93
94    /// Funding to date (for private companies)
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub funding_to_date: Option<Value>,
97
98    /// Percentage of shares held by insiders
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub held_percent_insiders: Option<F::Value<f64>>,
101
102    /// Percentage of shares held by institutions
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub held_percent_institutions: Option<F::Value<f64>>,
105
106    /// Implied shares outstanding
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub implied_shares_outstanding: Option<F::Value<i64>>,
109
110    /// Last capital gain (for funds)
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub last_cap_gain: Option<Value>,
113
114    /// Last dividend date
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub last_dividend_date: Option<F::Value<i64>>,
117
118    /// Last dividend value per share
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub last_dividend_value: Option<F::Value<f64>>,
121
122    /// Last fiscal year end date
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub last_fiscal_year_end: Option<F::Value<i64>>,
125
126    /// Last stock split date
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub last_split_date: Option<F::Value<i64>>,
129
130    /// Last stock split factor (e.g., "4:1")
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub last_split_factor: Option<String>,
133
134    /// Latest amount raised (for private companies)
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub latest_amount_raised: Option<Value>,
137
138    /// Latest funding date (for private companies)
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub latest_funding_date: Option<Value>,
141
142    /// Latest implied valuation (for private companies)
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub latest_implied_valuation: Option<Value>,
145
146    /// Latest share class
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub latest_share_class: Option<String>,
149
150    /// Lead investor (for private companies)
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub lead_investor: Option<String>,
153
154    /// Legal type
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub legal_type: Option<String>,
157
158    /// Maximum age of data in seconds
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub max_age: Option<i64>,
161
162    /// Morningstar overall rating (for funds)
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub morning_star_overall_rating: Option<Value>,
165
166    /// Morningstar risk rating (for funds)
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub morning_star_risk_rating: Option<Value>,
169
170    /// Most recent quarter end date
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub most_recent_quarter: Option<F::Value<i64>>,
173
174    /// Net income to common shareholders
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub net_income_to_common: Option<F::Value<i64>>,
177
178    /// Next fiscal year end date
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub next_fiscal_year_end: Option<F::Value<i64>>,
181
182    /// PEG ratio (Price/Earnings to Growth)
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub peg_ratio: Option<Value>,
185
186    /// Price hint (decimal places)
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub price_hint: Option<F::Value<i64>>,
189
190    /// Price to book ratio
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub price_to_book: Option<F::Value<f64>>,
193
194    /// Price to sales ratio (trailing 12 months)
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub price_to_sales_trailing12_months: Option<Value>,
197
198    /// Profit margins percentage
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub profit_margins: Option<F::Value<f64>>,
201
202    /// Quarter-to-date return (for funds)
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub qtd_return: Option<Value>,
205
206    /// Quarterly revenue growth rate
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub revenue_quarterly_growth: Option<Value>,
209
210    /// Total shares outstanding
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub shares_outstanding: Option<F::Value<i64>>,
213
214    /// Short interest as percentage of shares outstanding
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub shares_percent_shares_out: Option<F::Value<f64>>,
217
218    /// Number of shares short
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub shares_short: Option<F::Value<i64>>,
221
222    /// Previous month date for short interest
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub shares_short_previous_month_date: Option<F::Value<i64>>,
225
226    /// Shares short in prior month
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub shares_short_prior_month: Option<F::Value<i64>>,
229
230    /// Short interest as percentage of float
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub short_percent_of_float: Option<F::Value<f64>>,
233
234    /// Short ratio (days to cover)
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub short_ratio: Option<F::Value<f64>>,
237
238    /// 3-year average return (for funds)
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub three_year_average_return: Option<Value>,
241
242    /// Total assets (for funds)
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub total_assets: Option<Value>,
245
246    /// Total funding rounds (for private companies)
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub total_funding_rounds: Option<Value>,
249
250    /// Trailing earnings per share
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub trailing_eps: Option<F::Value<f64>>,
253
254    /// Yield percentage (for bonds/funds)
255    #[serde(rename = "yield", skip_serializing_if = "Option::is_none")]
256    pub yield_value: Option<Value>,
257
258    /// Year-to-date return (for funds)
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub ytd_return: Option<Value>,
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn test_deserialize_default_key_statistics() {
269        let json = r#"{
270            "52WeekChange": {"fmt": "17.38%", "raw": 0.173828},
271            "SandP52WeekChange": {"fmt": "11.35%", "raw": 0.11350584},
272            "beta": {"fmt": "1.11", "raw": 1.109},
273            "bookValue": {"fmt": "4.99", "raw": 4.991},
274            "enterpriseValue": {"fmt": "4.13T", "longFmt": "4,134,771,359,744", "raw": 4134771359744},
275            "forwardPE": {"fmt": "30.39", "raw": 30.387243},
276            "lastSplitFactor": "4:1",
277            "maxAge": 1,
278            "sharesOutstanding": {"fmt": "14.78B", "longFmt": "14,776,353,000", "raw": 14776353000},
279            "trailingEps": {"fmt": "7.45", "raw": 7.45}
280        }"#;
281
282        let stats: DefaultKeyStatistics = serde_json::from_str(json).unwrap();
283        assert_eq!(
284            stats.week_52_change.as_ref().map(|v| v.raw),
285            Some(Some(0.173828))
286        );
287        assert_eq!(stats.beta.as_ref().map(|v| v.raw), Some(Some(1.109)));
288        assert_eq!(stats.last_split_factor.as_deref(), Some("4:1"));
289        assert_eq!(stats.max_age, Some(1));
290        assert_eq!(
291            stats.shares_outstanding.as_ref().map(|v| v.raw),
292            Some(Some(14776353000))
293        );
294    }
295
296    #[test]
297    fn test_into_raw() {
298        let json = r#"{
299            "beta": {"fmt": "1.11", "raw": 1.109},
300            "trailingEps": {"fmt": "7.45", "raw": 7.45},
301            "lastSplitFactor": "4:1"
302        }"#;
303
304        let stats: DefaultKeyStatistics = serde_json::from_str(json).unwrap();
305        let raw = stats.into_raw();
306        assert_eq!(raw.beta, Some(1.109));
307        assert_eq!(raw.trailing_eps, Some(7.45));
308        assert_eq!(raw.last_split_factor.as_deref(), Some("4:1"));
309    }
310
311    #[test]
312    fn test_into_pretty() {
313        let json = r#"{
314            "beta": {"fmt": "1.11", "raw": 1.109},
315            "enterpriseValue": {"fmt": "4.13T", "longFmt": "4,134,771,359,744", "raw": 4134771359744}
316        }"#;
317
318        let stats: DefaultKeyStatistics = serde_json::from_str(json).unwrap();
319        let pretty = stats.into_pretty();
320        assert_eq!(pretty.beta.as_deref(), Some("1.11"));
321        assert_eq!(pretty.enterprise_value.as_deref(), Some("4.13T"));
322    }
323}