Skip to main content

finance_query/models/chart/
events.rs

1//! Chart events module
2//!
3//! Contains dividend, split, and capital gain data structures.
4
5use crate::Provider;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::OnceLock;
9
10/// Chart events containing dividends, splits, and capital gains
11///
12/// Events are deserialized from HashMaps, then lazily converted to sorted vectors
13/// on first access and cached for subsequent calls.
14#[derive(Debug, Default, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub(crate) struct ChartEvents {
17    /// Dividend events keyed by timestamp
18    #[serde(default)]
19    pub dividends: HashMap<String, DividendEvent>,
20    /// Stock split events keyed by timestamp
21    #[serde(default)]
22    pub splits: HashMap<String, SplitEvent>,
23    /// Capital gain events keyed by timestamp
24    #[serde(default)]
25    pub capital_gains: HashMap<String, CapitalGainEvent>,
26
27    /// Cached sorted dividend vector (computed once on first access)
28    #[serde(skip)]
29    dividends_cache: OnceLock<Vec<Dividend>>,
30    /// Cached sorted splits vector (computed once on first access)
31    #[serde(skip)]
32    splits_cache: OnceLock<Vec<Split>>,
33    /// Cached sorted capital gains vector (computed once on first access)
34    #[serde(skip)]
35    capital_gains_cache: OnceLock<Vec<CapitalGain>>,
36}
37
38/// Raw dividend event from Yahoo Finance
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub(crate) struct DividendEvent {
41    /// Dividend amount per share
42    pub amount: f64,
43    /// Timestamp of the dividend
44    pub date: i64,
45}
46
47/// Raw split event from Yahoo Finance
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub(crate) struct SplitEvent {
51    /// Timestamp of the split
52    pub date: i64,
53    /// Numerator of the split ratio
54    pub numerator: f64,
55    /// Denominator of the split ratio
56    pub denominator: f64,
57    /// Split ratio as string (e.g., "2:1", "10:1")
58    pub split_ratio: String,
59}
60
61/// Raw capital gain event from Yahoo Finance
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub(crate) struct CapitalGainEvent {
64    /// Capital gain amount per share
65    pub amount: f64,
66    /// Timestamp of the capital gain distribution
67    pub date: i64,
68}
69
70/// Public dividend data
71///
72/// Note: This struct cannot be manually constructed - obtain via `Ticker::dividends()`.
73#[non_exhaustive]
74#[derive(Debug, Clone, Serialize, Deserialize)]
75#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
76pub struct Dividend {
77    /// Timestamp (Unix)
78    pub timestamp: i64,
79    /// Dividend amount per share
80    pub amount: f64,
81
82    /// Which data provider served this data (e.g., "yahoo", "polygon").
83    #[serde(skip_serializing_if = "Option::is_none", default)]
84    pub provider_id: Option<Provider>,
85}
86
87/// Public stock split data
88///
89/// Note: This struct cannot be manually constructed - obtain via `Ticker::splits()`.
90#[non_exhaustive]
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
93pub struct Split {
94    /// Timestamp (Unix)
95    pub timestamp: i64,
96    /// Numerator of the split ratio
97    pub numerator: f64,
98    /// Denominator of the split ratio
99    pub denominator: f64,
100    /// Split ratio as string (e.g., "2:1", "10:1")
101    pub ratio: String,
102
103    /// Which data provider served this data (e.g., "yahoo", "polygon").
104    #[serde(skip_serializing_if = "Option::is_none", default)]
105    pub provider_id: Option<Provider>,
106}
107
108/// Public capital gain data
109///
110/// Note: This struct cannot be manually constructed - obtain via `Ticker::capital_gains()`.
111#[non_exhaustive]
112#[derive(Debug, Clone, Serialize, Deserialize)]
113#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
114pub struct CapitalGain {
115    /// Timestamp (Unix)
116    pub timestamp: i64,
117    /// Capital gain amount per share
118    pub amount: f64,
119
120    /// Which data provider served this data (e.g., "yahoo", "polygon").
121    #[serde(skip_serializing_if = "Option::is_none", default)]
122    pub provider_id: Option<Provider>,
123}
124
125impl Clone for ChartEvents {
126    fn clone(&self) -> Self {
127        // Helper to clone OnceLock if initialized
128        fn clone_cache<T: Clone>(cache: &OnceLock<T>) -> OnceLock<T> {
129            let new_cache = OnceLock::new();
130            if let Some(value) = cache.get() {
131                let _ = new_cache.set(value.clone());
132            }
133            new_cache
134        }
135
136        Self {
137            dividends: self.dividends.clone(),
138            splits: self.splits.clone(),
139            capital_gains: self.capital_gains.clone(),
140            dividends_cache: clone_cache(&self.dividends_cache),
141            splits_cache: clone_cache(&self.splits_cache),
142            capital_gains_cache: clone_cache(&self.capital_gains_cache),
143        }
144    }
145}
146
147impl ChartEvents {
148    /// Get sorted list of dividends (cached after first call)
149    pub(crate) fn to_dividends(&self) -> Vec<Dividend> {
150        self.dividends_cache
151            .get_or_init(|| {
152                let mut dividends: Vec<Dividend> = self
153                    .dividends
154                    .values()
155                    .map(|d| Dividend {
156                        timestamp: d.date,
157                        amount: d.amount,
158                        provider_id: None,
159                    })
160                    .collect();
161                dividends.sort_by_key(|d| d.timestamp);
162                dividends
163            })
164            .clone()
165    }
166
167    /// Get sorted list of splits (cached after first call)
168    pub(crate) fn to_splits(&self) -> Vec<Split> {
169        self.splits_cache
170            .get_or_init(|| {
171                let mut splits: Vec<Split> = self
172                    .splits
173                    .values()
174                    .map(|s| Split {
175                        timestamp: s.date,
176                        numerator: s.numerator,
177                        denominator: s.denominator,
178                        ratio: s.split_ratio.clone(),
179                        provider_id: None,
180                    })
181                    .collect();
182                splits.sort_by_key(|s| s.timestamp);
183                splits
184            })
185            .clone()
186    }
187
188    /// Get sorted list of capital gains (cached after first call)
189    pub(crate) fn to_capital_gains(&self) -> Vec<CapitalGain> {
190        self.capital_gains_cache
191            .get_or_init(|| {
192                let mut gains: Vec<CapitalGain> = self
193                    .capital_gains
194                    .values()
195                    .map(|g| CapitalGain {
196                        timestamp: g.date,
197                        amount: g.amount,
198                        provider_id: None,
199                    })
200                    .collect();
201                gains.sort_by_key(|g| g.timestamp);
202                gains
203            })
204            .clone()
205    }
206}