use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EquityPerformance {
#[serde(default)]
pub max_age: Option<i64>,
#[serde(default)]
pub benchmark: Option<Benchmark>,
#[serde(default)]
pub performance_overview: Option<PerformanceOverview>,
#[serde(default)]
pub performance_overview_benchmark: Option<PerformanceOverview>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Benchmark {
#[serde(default)]
pub symbol: Option<String>,
#[serde(default)]
pub short_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerformanceOverview {
#[serde(default)]
pub as_of_date: Option<crate::models::quote::FormattedValue<i64>>,
#[serde(default)]
pub five_days_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub one_month_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub three_month_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub six_month_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub ytd_return_pct: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub one_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub two_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub three_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub five_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub ten_year_total_return: Option<crate::models::quote::FormattedValue<f64>>,
#[serde(default)]
pub max_return: Option<crate::models::quote::FormattedValue<f64>>,
}
impl EquityPerformance {
pub fn ytd_return(&self) -> Option<f64> {
self.performance_overview
.as_ref()?
.ytd_return_pct
.as_ref()?
.raw
}
pub fn benchmark_ytd_return(&self) -> Option<f64> {
self.performance_overview_benchmark
.as_ref()?
.ytd_return_pct
.as_ref()?
.raw
}
pub fn ytd_vs_benchmark(&self) -> Option<f64> {
let stock_ytd = self.ytd_return()?;
let benchmark_ytd = self.benchmark_ytd_return()?;
Some(stock_ytd - benchmark_ytd)
}
pub fn one_year_return(&self) -> Option<f64> {
self.performance_overview
.as_ref()?
.one_year_total_return
.as_ref()?
.raw
}
pub fn benchmark_one_year_return(&self) -> Option<f64> {
self.performance_overview_benchmark
.as_ref()?
.one_year_total_return
.as_ref()?
.raw
}
pub fn one_year_vs_benchmark(&self) -> Option<f64> {
let stock_return = self.one_year_return()?;
let benchmark_return = self.benchmark_one_year_return()?;
Some(stock_return - benchmark_return)
}
pub fn five_year_return(&self) -> Option<f64> {
self.performance_overview
.as_ref()?
.five_year_total_return
.as_ref()?
.raw
}
pub fn benchmark_five_year_return(&self) -> Option<f64> {
self.performance_overview_benchmark
.as_ref()?
.five_year_total_return
.as_ref()?
.raw
}
pub fn five_year_vs_benchmark(&self) -> Option<f64> {
let stock_return = self.five_year_return()?;
let benchmark_return = self.benchmark_five_year_return()?;
Some(stock_return - benchmark_return)
}
pub fn benchmark_name(&self) -> Option<&str> {
self.benchmark.as_ref()?.short_name.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_equity_performance_deserialize() {
let json = json!({
"maxAge": 1,
"benchmark": {
"symbol": "^GSPC",
"shortName": "S&P 500"
},
"performanceOverview": {
"asOfDate": {"raw": 1764892800},
"fiveDaysReturn": {"raw": 0.030622387},
"oneMonthReturn": {"raw": -0.06551842},
"threeMonthReturn": {"raw": 0.092266984},
"sixMonthReturn": {"raw": 0.30325815},
"ytdReturnPct": {"raw": 0.35870054},
"oneYearTotalReturn": {"raw": 0.2578236},
"twoYearTotalReturn": {"raw": 2.919418},
"threeYearTotalReturn": {"raw": 9.992929},
"fiveYearTotalReturn": {"raw": 12.491636},
"tenYearTotalReturn": {"raw": 220.57314},
"maxReturn": {"raw": 4168.3716}
},
"performanceOverviewBenchmark": {
"asOfDate": {"raw": 1764892800},
"fiveDaysReturn": {"raw": 0.0031113708},
"oneMonthReturn": {"raw": 0.010904458},
"threeMonthReturn": {"raw": 0.060001526},
"sixMonthReturn": {"raw": 0.15676934},
"ytdReturnPct": {"raw": 0.16811156},
"oneYearTotalReturn": {"raw": 0.13090958},
"twoYearTotalReturn": {"raw": 0.504298},
"threeYearTotalReturn": {"raw": 0.71809816},
"fiveYearTotalReturn": {"raw": 0.85730654},
"tenYearTotalReturn": {"raw": 2.2846167},
"maxReturn": {"raw": 388.03735}
}
});
let equity_performance: EquityPerformance = serde_json::from_value(json).unwrap();
assert_eq!(equity_performance.max_age, Some(1));
assert_eq!(equity_performance.benchmark_name(), Some("S&P 500"));
assert_eq!(equity_performance.ytd_return(), Some(0.35870054));
assert_eq!(equity_performance.benchmark_ytd_return(), Some(0.16811156));
}
#[test]
fn test_equity_performance_vs_benchmark() {
use crate::models::quote::FormattedValue;
let equity_performance = EquityPerformance {
max_age: Some(1),
benchmark: Some(Benchmark {
symbol: Some("^GSPC".to_string()),
short_name: Some("S&P 500".to_string()),
}),
performance_overview: Some(PerformanceOverview {
as_of_date: Some(FormattedValue::new(1764892800)),
five_days_return: Some(FormattedValue::new(0.030622387)),
one_month_return: Some(FormattedValue::new(-0.06551842)),
three_month_return: Some(FormattedValue::new(0.092266984)),
six_month_return: Some(FormattedValue::new(0.30325815)),
ytd_return_pct: Some(FormattedValue::new(0.35870054)),
one_year_total_return: Some(FormattedValue::new(0.2578236)),
two_year_total_return: Some(FormattedValue::new(2.919418)),
three_year_total_return: Some(FormattedValue::new(9.992929)),
five_year_total_return: Some(FormattedValue::new(12.491636)),
ten_year_total_return: Some(FormattedValue::new(220.57314)),
max_return: Some(FormattedValue::new(4168.3716)),
}),
performance_overview_benchmark: Some(PerformanceOverview {
as_of_date: Some(FormattedValue::new(1764892800)),
five_days_return: Some(FormattedValue::new(0.0031113708)),
one_month_return: Some(FormattedValue::new(0.010904458)),
three_month_return: Some(FormattedValue::new(0.060001526)),
six_month_return: Some(FormattedValue::new(0.15676934)),
ytd_return_pct: Some(FormattedValue::new(0.16811156)),
one_year_total_return: Some(FormattedValue::new(0.13090958)),
two_year_total_return: Some(FormattedValue::new(0.504298)),
three_year_total_return: Some(FormattedValue::new(0.71809816)),
five_year_total_return: Some(FormattedValue::new(0.85730654)),
ten_year_total_return: Some(FormattedValue::new(2.2846167)),
max_return: Some(FormattedValue::new(388.03735)),
}),
};
let ytd_vs = equity_performance.ytd_vs_benchmark().unwrap();
assert!((ytd_vs - 0.19058898).abs() < 0.0001);
let one_year_vs = equity_performance.one_year_vs_benchmark().unwrap();
assert!((one_year_vs - 0.12691402).abs() < 0.0001);
let five_year_vs = equity_performance.five_year_vs_benchmark().unwrap();
assert!((five_year_vs - 11.634329).abs() < 0.001);
}
}