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