Skip to main content

finance_query/models/fundamentals/
summary_detail.rs

1use crate::models::format::{Both, Format};
2use finance_query_derive::FormatConvert;
3/// Summary Detail module
4///
5/// Contains detailed trading and valuation metrics for the symbol.
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// Summary detail trading and valuation metrics
10///
11/// Contains detailed information about price, volume, market cap, and other trading data.
12///
13/// The type parameter `F` controls how numeric fields are represented:
14/// - `SummaryDetail` / `SummaryDetail<Both>` — **default**; fields hold `FormattedValue<T>`
15/// - `SummaryDetail<Raw>` — fields hold `T` directly (e.g. `Option<f64>`)
16/// - `SummaryDetail<Pretty>` — fields hold `Option<String>` (human-readable)
17///
18/// Obtain converted views via [`Quote::as_raw`](crate::Quote::as_raw) or call
19/// `.as_raw()` / `.into_raw()` on a `SummaryDetail<Both>` directly.
20#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, FormatConvert)]
21#[serde(rename_all = "camelCase", bound = "")]
22#[non_exhaustive]
23pub struct SummaryDetail<F: Format = Both> {
24    /// Algorithm (for crypto/special assets)
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub algorithm: Option<Value>,
27
28    /// All-time high price
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub all_time_high: Option<F::Value<f64>>,
31
32    /// All-time low price
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub all_time_low: Option<F::Value<f64>>,
35
36    /// Current ask price
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub ask: Option<F::Value<f64>>,
39
40    /// Ask size (shares)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub ask_size: Option<F::Value<i64>>,
43
44    /// Average daily trading volume (10 day)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub average_daily_volume10_day: Option<F::Value<i64>>,
47
48    /// Average trading volume
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub average_volume: Option<F::Value<i64>>,
51
52    /// Average trading volume (10 days)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub average_volume10days: Option<F::Value<i64>>,
55
56    /// Beta coefficient (volatility vs market)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub beta: Option<F::Value<f64>>,
59
60    /// Current bid price
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub bid: Option<F::Value<f64>>,
63
64    /// Bid size (shares)
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub bid_size: Option<F::Value<i64>>,
67
68    /// Circulating supply (for crypto)
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub circulating_supply: Option<Value>,
71
72    /// CoinMarketCap link (for crypto)
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub coin_market_cap_link: Option<Value>,
75
76    /// Currency code
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub currency: Option<String>,
79
80    /// Day's high price
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub day_high: Option<F::Value<f64>>,
83
84    /// Day's low price
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub day_low: Option<F::Value<f64>>,
87
88    /// Annual dividend rate
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub dividend_rate: Option<F::Value<f64>>,
91
92    /// Dividend yield percentage
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub dividend_yield: Option<F::Value<f64>>,
95
96    /// Ex-dividend date
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub ex_dividend_date: Option<F::Value<i64>>,
99
100    /// Expiration date (for options/futures)
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub expire_date: Option<Value>,
103
104    /// 50-day moving average
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub fifty_day_average: Option<F::Value<f64>>,
107
108    /// 52-week high price
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub fifty_two_week_high: Option<F::Value<f64>>,
111
112    /// 52-week low price
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub fifty_two_week_low: Option<F::Value<f64>>,
115
116    /// 5-year average dividend yield
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub five_year_avg_dividend_yield: Option<F::Value<f64>>,
119
120    /// Forward price-to-earnings ratio
121    #[serde(rename = "forwardPE", skip_serializing_if = "Option::is_none")]
122    pub forward_pe: Option<F::Value<f64>>,
123
124    /// From currency (for currency pairs)
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub from_currency: Option<String>,
127
128    /// Last market (for crypto)
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub last_market: Option<String>,
131
132    /// Market capitalization
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub market_cap: Option<F::Value<i64>>,
135
136    /// Maximum age of data in seconds
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub max_age: Option<i64>,
139
140    /// Maximum supply (for crypto)
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub max_supply: Option<Value>,
143
144    /// Net asset value price (for funds)
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub nav_price: Option<F::Value<f64>>,
147
148    /// Opening price
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub open: Option<F::Value<f64>>,
151
152    /// Open interest (for options/futures)
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub open_interest: Option<Value>,
155
156    /// Dividend payout ratio
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub payout_ratio: Option<F::Value<f64>>,
159
160    /// Previous closing price
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub previous_close: Option<F::Value<f64>>,
163
164    /// Price hint (decimal places)
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub price_hint: Option<F::Value<i64>>,
167
168    /// Price to sales ratio (trailing 12 months)
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub price_to_sales_trailing12_months: Option<F::Value<f64>>,
171
172    /// Quarter-to-date return
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub qtd_return: Option<Value>,
175
176    /// Regular market day high
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub regular_market_day_high: Option<F::Value<f64>>,
179
180    /// Regular market day low
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub regular_market_day_low: Option<F::Value<f64>>,
183
184    /// Regular market opening price
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub regular_market_open: Option<F::Value<f64>>,
187
188    /// Regular market previous close
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub regular_market_previous_close: Option<F::Value<f64>>,
191
192    /// Regular market trading volume
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub regular_market_volume: Option<F::Value<i64>>,
195
196    /// Start date (for funds/special assets)
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub start_date: Option<Value>,
199
200    /// Strike price (for options)
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub strike_price: Option<Value>,
203
204    /// To currency (for currency pairs)
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub to_currency: Option<String>,
207
208    /// Total assets (for funds)
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub total_assets: Option<F::Value<i64>>,
211
212    /// Whether the security is tradeable
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub tradeable: Option<bool>,
215
216    /// Trailing annual dividend rate
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub trailing_annual_dividend_rate: Option<F::Value<f64>>,
219
220    /// Trailing annual dividend yield
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub trailing_annual_dividend_yield: Option<F::Value<f64>>,
223
224    /// Trailing price-to-earnings ratio
225    #[serde(rename = "trailingPE", skip_serializing_if = "Option::is_none")]
226    pub trailing_pe: Option<F::Value<f64>>,
227
228    /// 200-day moving average
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub two_hundred_day_average: Option<F::Value<f64>>,
231
232    /// Trading volume
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub volume: Option<F::Value<i64>>,
235
236    /// 24-hour trading volume (for crypto)
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub volume24_hr: Option<Value>,
239
240    /// Volume across all currencies (for crypto)
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub volume_all_currencies: Option<Value>,
243
244    /// Yield (for bonds/funds)
245    #[serde(rename = "yield", skip_serializing_if = "Option::is_none")]
246    pub yield_value: Option<F::Value<f64>>,
247
248    /// Year-to-date return
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub ytd_return: Option<Value>,
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_deserialize_summary_detail() {
259        let json = r#"{
260            "currency": "USD",
261            "previousClose": {"fmt": "275.00", "raw": 275.0},
262            "marketCap": {"fmt": "4.09T", "longFmt": "4,090,000,000,000", "raw": 4090000000000},
263            "beta": {"fmt": "1.11", "raw": 1.109},
264            "tradeable": true
265        }"#;
266
267        let detail: SummaryDetail = serde_json::from_str(json).unwrap();
268        assert_eq!(detail.currency.as_deref(), Some("USD"));
269        assert_eq!(
270            detail.previous_close.as_ref().map(|v| v.raw),
271            Some(Some(275.0))
272        );
273        assert_eq!(
274            detail.market_cap.as_ref().map(|v| v.raw),
275            Some(Some(4090000000000))
276        );
277        assert_eq!(detail.tradeable, Some(true));
278    }
279
280    #[test]
281    fn test_into_raw() {
282        let json = r#"{
283            "currency": "USD",
284            "previousClose": {"fmt": "275.00", "raw": 275.0},
285            "beta": {"fmt": "1.11", "raw": 1.109}
286        }"#;
287
288        let detail: SummaryDetail = serde_json::from_str(json).unwrap();
289        let raw = detail.into_raw();
290        assert_eq!(raw.currency.as_deref(), Some("USD"));
291        assert_eq!(raw.previous_close, Some(275.0));
292        assert_eq!(raw.beta, Some(1.109));
293    }
294
295    #[test]
296    fn test_into_pretty() {
297        let json = r#"{
298            "previousClose": {"fmt": "275.00", "raw": 275.0},
299            "marketCap": {"fmt": "4.09T", "longFmt": "4,090,000,000,000", "raw": 4090000000000}
300        }"#;
301
302        let detail: SummaryDetail = serde_json::from_str(json).unwrap();
303        let pretty = detail.into_pretty();
304        assert_eq!(pretty.previous_close.as_deref(), Some("275.00"));
305        assert_eq!(pretty.market_cap.as_deref(), Some("4.09T"));
306    }
307}