Skip to main content

finance_query/models/corporate/
equity_performance.rs

1//! Equity Performance Module
2//!
3//! Contains equity performance data comparing stock returns against a benchmark
4//! over various time periods.
5
6use serde::{Deserialize, Serialize};
7
8/// Equity performance data comparing stock returns to benchmark
9#[derive(Default, Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11#[non_exhaustive]
12pub struct EquityPerformance {
13    /// Maximum age of the data in seconds
14    #[serde(default)]
15    pub max_age: Option<i64>,
16
17    /// Benchmark information
18    #[serde(default)]
19    pub benchmark: Option<Benchmark>,
20
21    /// Stock performance overview across multiple time periods
22    #[serde(default)]
23    pub performance_overview: Option<PerformanceOverview>,
24
25    /// Benchmark performance overview for comparison
26    #[serde(default)]
27    pub performance_overview_benchmark: Option<PerformanceOverview>,
28}
29
30/// Benchmark information
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct Benchmark {
34    /// Benchmark symbol (e.g., "^GSPC" for S&P 500)
35    #[serde(default)]
36    pub symbol: Option<String>,
37
38    /// Benchmark short name (e.g., "S&P 500")
39    #[serde(default)]
40    pub short_name: Option<String>,
41}
42
43/// Performance metrics across multiple time periods
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct PerformanceOverview {
47    /// Date the performance data is as of (Unix timestamp)
48    #[serde(default)]
49    pub as_of_date: Option<crate::models::quote::FormattedValue<i64>>,
50
51    /// 5-day return percentage
52    #[serde(default)]
53    pub five_days_return: Option<crate::models::quote::FormattedValue<f64>>,
54
55    /// 1-month return percentage
56    #[serde(default)]
57    pub one_month_return: Option<crate::models::quote::FormattedValue<f64>>,
58
59    /// 3-month return percentage
60    #[serde(default)]
61    pub three_month_return: Option<crate::models::quote::FormattedValue<f64>>,
62
63    /// 6-month return percentage
64    #[serde(default)]
65    pub six_month_return: Option<crate::models::quote::FormattedValue<f64>>,
66
67    /// Year-to-date return percentage
68    #[serde(default)]
69    pub ytd_return_pct: Option<crate::models::quote::FormattedValue<f64>>,
70
71    /// 1-year total return percentage
72    #[serde(default)]
73    pub one_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
74
75    /// 2-year total return percentage
76    #[serde(default)]
77    pub two_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
78
79    /// 3-year total return percentage
80    #[serde(default)]
81    pub three_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
82
83    /// 5-year total return percentage
84    #[serde(default)]
85    pub five_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
86
87    /// 10-year total return percentage
88    #[serde(default)]
89    pub ten_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
90
91    /// Maximum return percentage (all-time)
92    #[serde(default)]
93    pub max_return: Option<crate::models::quote::FormattedValue<f64>>,
94}
95
96impl EquityPerformance {
97    /// Returns the stock's year-to-date return percentage
98    pub fn ytd_return(&self) -> Option<f64> {
99        self.performance_overview
100            .as_ref()?
101            .ytd_return_pct
102            .as_ref()?
103            .raw
104    }
105
106    /// Returns the benchmark's year-to-date return percentage
107    pub fn benchmark_ytd_return(&self) -> Option<f64> {
108        self.performance_overview_benchmark
109            .as_ref()?
110            .ytd_return_pct
111            .as_ref()?
112            .raw
113    }
114
115    /// Returns the stock's outperformance vs benchmark for YTD (positive = outperforming)
116    pub fn ytd_vs_benchmark(&self) -> Option<f64> {
117        let stock_ytd = self.ytd_return()?;
118        let benchmark_ytd = self.benchmark_ytd_return()?;
119        Some(stock_ytd - benchmark_ytd)
120    }
121
122    /// Returns the stock's 1-year total return percentage
123    pub fn one_year_return(&self) -> Option<f64> {
124        self.performance_overview
125            .as_ref()?
126            .one_year_total_return
127            .as_ref()?
128            .raw
129    }
130
131    /// Returns the benchmark's 1-year total return percentage
132    pub fn benchmark_one_year_return(&self) -> Option<f64> {
133        self.performance_overview_benchmark
134            .as_ref()?
135            .one_year_total_return
136            .as_ref()?
137            .raw
138    }
139
140    /// Returns the stock's outperformance vs benchmark for 1 year (positive = outperforming)
141    pub fn one_year_vs_benchmark(&self) -> Option<f64> {
142        let stock_return = self.one_year_return()?;
143        let benchmark_return = self.benchmark_one_year_return()?;
144        Some(stock_return - benchmark_return)
145    }
146
147    /// Returns the stock's 5-year total return percentage
148    pub fn five_year_return(&self) -> Option<f64> {
149        self.performance_overview
150            .as_ref()?
151            .five_year_total_return
152            .as_ref()?
153            .raw
154    }
155
156    /// Returns the benchmark's 5-year total return percentage
157    pub fn benchmark_five_year_return(&self) -> Option<f64> {
158        self.performance_overview_benchmark
159            .as_ref()?
160            .five_year_total_return
161            .as_ref()?
162            .raw
163    }
164
165    /// Returns the stock's outperformance vs benchmark for 5 years (positive = outperforming)
166    pub fn five_year_vs_benchmark(&self) -> Option<f64> {
167        let stock_return = self.five_year_return()?;
168        let benchmark_return = self.benchmark_five_year_return()?;
169        Some(stock_return - benchmark_return)
170    }
171
172    /// Returns the benchmark name
173    pub fn benchmark_name(&self) -> Option<&str> {
174        self.benchmark.as_ref()?.short_name.as_deref()
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use serde_json::json;
182
183    #[test]
184    fn test_equity_performance_deserialize() {
185        // Test JSON uses FormattedValue format: {"raw": value}
186        let json = json!({
187            "maxAge": 1,
188            "benchmark": {
189                "symbol": "^GSPC",
190                "shortName": "S&P 500"
191            },
192            "performanceOverview": {
193                "asOfDate": {"raw": 1764892800},
194                "fiveDaysReturn": {"raw": 0.030622387},
195                "oneMonthReturn": {"raw": -0.06551842},
196                "threeMonthReturn": {"raw": 0.092266984},
197                "sixMonthReturn": {"raw": 0.30325815},
198                "ytdReturnPct": {"raw": 0.35870054},
199                "oneYearTotalReturn": {"raw": 0.2578236},
200                "twoYearTotalReturn": {"raw": 2.919418},
201                "threeYearTotalReturn": {"raw": 9.992929},
202                "fiveYearTotalReturn": {"raw": 12.491636},
203                "tenYearTotalReturn": {"raw": 220.57314},
204                "maxReturn": {"raw": 4168.3716}
205            },
206            "performanceOverviewBenchmark": {
207                "asOfDate": {"raw": 1764892800},
208                "fiveDaysReturn": {"raw": 0.0031113708},
209                "oneMonthReturn": {"raw": 0.010904458},
210                "threeMonthReturn": {"raw": 0.060001526},
211                "sixMonthReturn": {"raw": 0.15676934},
212                "ytdReturnPct": {"raw": 0.16811156},
213                "oneYearTotalReturn": {"raw": 0.13090958},
214                "twoYearTotalReturn": {"raw": 0.504298},
215                "threeYearTotalReturn": {"raw": 0.71809816},
216                "fiveYearTotalReturn": {"raw": 0.85730654},
217                "tenYearTotalReturn": {"raw": 2.2846167},
218                "maxReturn": {"raw": 388.03735}
219            }
220        });
221
222        let equity_performance: EquityPerformance = serde_json::from_value(json).unwrap();
223        assert_eq!(equity_performance.max_age, Some(1));
224        assert_eq!(equity_performance.benchmark_name(), Some("S&P 500"));
225        assert_eq!(equity_performance.ytd_return(), Some(0.35870054));
226        assert_eq!(equity_performance.benchmark_ytd_return(), Some(0.16811156));
227    }
228
229    #[test]
230    fn test_equity_performance_vs_benchmark() {
231        use crate::models::quote::FormattedValue;
232
233        let equity_performance = EquityPerformance {
234            max_age: Some(1),
235            benchmark: Some(Benchmark {
236                symbol: Some("^GSPC".to_string()),
237                short_name: Some("S&P 500".to_string()),
238            }),
239            performance_overview: Some(PerformanceOverview {
240                as_of_date: Some(FormattedValue::new(1764892800)),
241                five_days_return: Some(FormattedValue::new(0.030622387)),
242                one_month_return: Some(FormattedValue::new(-0.06551842)),
243                three_month_return: Some(FormattedValue::new(0.092266984)),
244                six_month_return: Some(FormattedValue::new(0.30325815)),
245                ytd_return_pct: Some(FormattedValue::new(0.35870054)),
246                one_year_total_return: Some(FormattedValue::new(0.2578236)),
247                two_year_total_return: Some(FormattedValue::new(2.919418)),
248                three_year_total_return: Some(FormattedValue::new(9.992929)),
249                five_year_total_return: Some(FormattedValue::new(12.491636)),
250                ten_year_total_return: Some(FormattedValue::new(220.57314)),
251                max_return: Some(FormattedValue::new(4168.3716)),
252            }),
253            performance_overview_benchmark: Some(PerformanceOverview {
254                as_of_date: Some(FormattedValue::new(1764892800)),
255                five_days_return: Some(FormattedValue::new(0.0031113708)),
256                one_month_return: Some(FormattedValue::new(0.010904458)),
257                three_month_return: Some(FormattedValue::new(0.060001526)),
258                six_month_return: Some(FormattedValue::new(0.15676934)),
259                ytd_return_pct: Some(FormattedValue::new(0.16811156)),
260                one_year_total_return: Some(FormattedValue::new(0.13090958)),
261                two_year_total_return: Some(FormattedValue::new(0.504298)),
262                three_year_total_return: Some(FormattedValue::new(0.71809816)),
263                five_year_total_return: Some(FormattedValue::new(0.85730654)),
264                ten_year_total_return: Some(FormattedValue::new(2.2846167)),
265                max_return: Some(FormattedValue::new(388.03735)),
266            }),
267        };
268
269        // Check YTD outperformance (35.87% - 16.81% H 19.06%)
270        let ytd_vs = equity_performance.ytd_vs_benchmark().unwrap();
271        assert!((ytd_vs - 0.19058898).abs() < 0.0001);
272
273        // Check 1-year outperformance (25.78% - 13.09% H 12.69%)
274        let one_year_vs = equity_performance.one_year_vs_benchmark().unwrap();
275        assert!((one_year_vs - 0.12691402).abs() < 0.0001);
276
277        // Check 5-year outperformance (1249.16% - 85.73% H 1163.43%)
278        let five_year_vs = equity_performance.five_year_vs_benchmark().unwrap();
279        assert!((five_year_vs - 11.634329).abs() < 0.001);
280    }
281}