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.timestamp_opt(div.date, 0).single().ok_or_else(|| {
90 crate::client::YahooError::ParseError(
91 "Invalid dividend timestamp".to_string(),
92 )
93 })?,
94 amount: div.amount,
95 currency: None,
96 });
97 }
98 }
99
100 if let Some(splits) = &events.splits {
102 for split in splits.values() {
103 actions.splits.push(StockSplit::new(
104 Utc.timestamp_opt(split.date, 0).single().ok_or_else(|| {
105 crate::client::YahooError::ParseError(
106 "Invalid split timestamp".to_string(),
107 )
108 })?,
109 split.numerator,
110 split.denominator,
111 ));
112 }
113 }
114
115 if let Some(gains) = &events.capital_gains {
117 for gain in gains.values() {
118 actions.capital_gains.push(CapitalGain {
119 date: Utc.timestamp_opt(gain.date, 0).single().ok_or_else(|| {
120 crate::client::YahooError::ParseError(
121 "Invalid capital gain timestamp".to_string(),
122 )
123 })?,
124 amount: gain.amount,
125 });
126 }
127 }
128 }
129 }
130
131 actions.dividends.sort_by_key(|d| d.date);
133 actions.splits.sort_by_key(|s| s.date);
134 actions.capital_gains.sort_by_key(|g| g.date);
135
136 Ok(actions)
137 }
138}
139
140#[derive(Debug, Deserialize)]
142pub(crate) struct YahooEventsResponse {
143 pub chart: ChartData,
144}
145
146#[derive(Debug, Deserialize)]
147pub(crate) struct ChartData {
148 pub result: Vec<ChartResult>,
149}
150
151#[derive(Debug, Deserialize)]
152pub(crate) struct ChartResult {
153 pub events: Option<Events>,
154}
155
156#[derive(Debug, Deserialize)]
157pub(crate) struct Events {
158 pub dividends: Option<HashMap<String, YahooDividend>>,
159 pub splits: Option<HashMap<String, YahooSplit>>,
160 #[serde(rename = "capitalGains")]
161 pub capital_gains: Option<HashMap<String, YahooCapitalGain>>,
162}
163
164#[derive(Debug, Deserialize)]
165pub(crate) struct YahooDividend {
166 pub amount: f64,
167 pub date: i64,
168}
169
170#[derive(Debug, Deserialize)]
171pub(crate) struct YahooSplit {
172 pub date: i64,
173 pub numerator: f64,
174 pub denominator: f64,
175 #[serde(rename = "splitRatio")]
176 #[allow(dead_code)]
177 pub split_ratio: String,
178}
179
180#[derive(Debug, Deserialize)]
181pub(crate) struct YahooCapitalGain {
182 pub amount: f64,
183 pub date: i64,
184}