finance_query_core/models/
actions.rs1use chrono::{DateTime, TimeZone, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct Dividend {
9 pub date: DateTime<Utc>,
10 pub amount: f64,
11 #[serde(skip_serializing_if = "Option::is_none")]
12 pub currency: Option<String>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct StockSplit {
19 pub date: DateTime<Utc>,
20 pub numerator: f64,
21 pub denominator: f64,
22 pub split_ratio: String,
24}
25
26impl StockSplit {
27 pub fn new(date: DateTime<Utc>, numerator: f64, denominator: f64) -> Self {
28 let split_ratio = format!("{}:{}", numerator, denominator);
29 Self {
30 date,
31 numerator,
32 denominator,
33 split_ratio,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct CapitalGain {
42 pub date: DateTime<Utc>,
43 pub amount: f64,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct ActionsResponse {
50 pub symbol: String,
51 pub dividends: Vec<Dividend>,
52 pub splits: Vec<StockSplit>,
53 pub capital_gains: Vec<CapitalGain>,
54}
55
56impl ActionsResponse {
57 pub fn new(symbol: String) -> Self {
58 Self {
59 symbol,
60 dividends: Vec::new(),
61 splits: Vec::new(),
62 capital_gains: Vec::new(),
63 }
64 }
65
66 pub fn is_empty(&self) -> bool {
68 self.dividends.is_empty() && self.splits.is_empty() && self.capital_gains.is_empty()
69 }
70
71 pub fn total_dividends(&self) -> f64 {
73 self.dividends.iter().map(|d| d.amount).sum()
74 }
75
76 pub(crate) fn from_yahoo_response(
78 symbol: String,
79 response: YahooEventsResponse,
80 ) -> Result<Self, crate::client::YahooError> {
81 let mut actions = Self::new(symbol);
82
83 if let Some(result) = response.chart.result.first() {
84 if let Some(events) = &result.events {
85 if let Some(divs) = &events.dividends {
87 for div in divs.values() {
88 actions.dividends.push(Dividend {
89 date: Utc
90 .timestamp_opt(div.date, 0)
91 .single()
92 .ok_or_else(|| {
93 crate::client::YahooError::ParseError(
94 "Invalid dividend timestamp".to_string(),
95 )
96 })?,
97 amount: div.amount,
98 currency: None,
99 });
100 }
101 }
102
103 if let Some(splits) = &events.splits {
105 for split in splits.values() {
106 actions.splits.push(StockSplit::new(
107 Utc.timestamp_opt(split.date, 0)
108 .single()
109 .ok_or_else(|| {
110 crate::client::YahooError::ParseError(
111 "Invalid split timestamp".to_string(),
112 )
113 })?,
114 split.numerator,
115 split.denominator,
116 ));
117 }
118 }
119
120 if let Some(gains) = &events.capital_gains {
122 for gain in gains.values() {
123 actions.capital_gains.push(CapitalGain {
124 date: Utc
125 .timestamp_opt(gain.date, 0)
126 .single()
127 .ok_or_else(|| {
128 crate::client::YahooError::ParseError(
129 "Invalid capital gain timestamp".to_string(),
130 )
131 })?,
132 amount: gain.amount,
133 });
134 }
135 }
136 }
137 }
138
139 actions.dividends.sort_by_key(|d| d.date);
141 actions.splits.sort_by_key(|s| s.date);
142 actions.capital_gains.sort_by_key(|g| g.date);
143
144 Ok(actions)
145 }
146}
147
148#[derive(Debug, Deserialize)]
150pub(crate) struct YahooEventsResponse {
151 pub chart: ChartData,
152}
153
154#[derive(Debug, Deserialize)]
155pub(crate) struct ChartData {
156 pub result: Vec<ChartResult>,
157}
158
159#[derive(Debug, Deserialize)]
160pub(crate) struct ChartResult {
161 pub events: Option<Events>,
162}
163
164#[derive(Debug, Deserialize)]
165pub(crate) struct Events {
166 pub dividends: Option<HashMap<String, YahooDividend>>,
167 pub splits: Option<HashMap<String, YahooSplit>>,
168 #[serde(rename = "capitalGains")]
169 pub capital_gains: Option<HashMap<String, YahooCapitalGain>>,
170}
171
172#[derive(Debug, Deserialize)]
173pub(crate) struct YahooDividend {
174 pub amount: f64,
175 pub date: i64,
176}
177
178#[derive(Debug, Deserialize)]
179pub(crate) struct YahooSplit {
180 pub date: i64,
181 pub numerator: f64,
182 pub denominator: f64,
183 #[serde(rename = "splitRatio")]
184 #[allow(dead_code)]
185 pub split_ratio: String,
186}
187
188#[derive(Debug, Deserialize)]
189pub(crate) struct YahooCapitalGain {
190 pub amount: f64,
191 pub date: i64,
192}