finance_query/models/chart/data.rs
1/// Chart aggregate module
2///
3/// Contains the fully typed Chart structure for historical data.
4use super::{Candle, ChartMeta};
5use crate::Provider;
6use crate::constants::{Interval, TimeRange};
7use serde::{Deserialize, Serialize};
8
9/// Fully typed chart data
10///
11/// Aggregates chart metadata and candles into a single convenient structure.
12/// This is the recommended type for serialization and API responses.
13/// Used for both single symbol and batch historical data requests.
14///
15/// Note: This struct cannot be manually constructed - use `Ticker::chart()` to obtain chart data.
16#[non_exhaustive]
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Chart {
19 /// Stock symbol
20 pub symbol: String,
21
22 /// Chart metadata (exchange, currency, 52-week range, etc.)
23 pub meta: ChartMeta,
24
25 /// OHLCV candles/bars
26 pub candles: Vec<Candle>,
27
28 /// Time interval used (e.g., `Interval::OneDay`)
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub interval: Option<Interval>,
31
32 /// Time range used (e.g., `TimeRange::OneYear`)
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub range: Option<TimeRange>,
35
36 /// Which data provider served this data (e.g., "yahoo", "polygon").
37 #[serde(skip_serializing_if = "Option::is_none", default)]
38 pub provider_id: Option<Provider>,
39}
40
41#[cfg(feature = "dataframe")]
42impl Chart {
43 /// Converts the candles to a polars DataFrame.
44 ///
45 /// Each candle becomes a row with columns for timestamp, open, high, low, close, volume.
46 pub fn to_dataframe(&self) -> ::polars::prelude::PolarsResult<::polars::prelude::DataFrame> {
47 Candle::vec_to_dataframe(&self.candles)
48 }
49}
50
51#[cfg(feature = "indicators")]
52impl Chart {
53 /// Extracts close prices from candles as a `Vec<f64>`.
54 ///
55 /// This is a convenience method for passing price data to indicator functions.
56 pub fn close_prices(&self) -> Vec<f64> {
57 self.candles.iter().map(|c| c.close).collect()
58 }
59
60 /// Extracts high prices from candles as a `Vec<f64>`.
61 pub fn high_prices(&self) -> Vec<f64> {
62 self.candles.iter().map(|c| c.high).collect()
63 }
64
65 /// Extracts low prices from candles as a `Vec<f64>`.
66 pub fn low_prices(&self) -> Vec<f64> {
67 self.candles.iter().map(|c| c.low).collect()
68 }
69
70 /// Extracts open prices from candles as a `Vec<f64>`.
71 pub fn open_prices(&self) -> Vec<f64> {
72 self.candles.iter().map(|c| c.open).collect()
73 }
74
75 /// Extracts volumes from candles as a `Vec<f64>`.
76 pub fn volumes(&self) -> Vec<f64> {
77 self.candles.iter().map(|c| c.volume as f64).collect()
78 }
79
80 /// Calculate Simple Moving Average (SMA) on close prices.
81 ///
82 /// # Example
83 ///
84 /// ```no_run
85 /// use finance_query::{Ticker, Interval, TimeRange};
86 ///
87 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
88 /// let ticker = Ticker::new("AAPL").await?;
89 /// let chart = ticker.chart(Interval::OneDay, TimeRange::OneMonth).await?;
90 ///
91 /// let sma_20 = chart.sma(20);
92 /// # Ok(())
93 /// # }
94 /// ```
95 pub fn sma(&self, period: usize) -> Vec<Option<f64>> {
96 crate::indicators::sma(&self.close_prices(), period)
97 }
98
99 /// Calculate Exponential Moving Average (EMA) on close prices.
100 ///
101 /// # Example
102 ///
103 /// ```no_run
104 /// use finance_query::{Ticker, Interval, TimeRange};
105 ///
106 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
107 /// let ticker = Ticker::new("AAPL").await?;
108 /// let chart = ticker.chart(Interval::OneDay, TimeRange::OneMonth).await?;
109 ///
110 /// let ema_12 = chart.ema(12);
111 /// # Ok(())
112 /// # }
113 /// ```
114 pub fn ema(&self, period: usize) -> Vec<Option<f64>> {
115 crate::indicators::ema(&self.close_prices(), period)
116 }
117
118 /// Calculate Relative Strength Index (RSI) on close prices.
119 ///
120 /// Returns values between 0 and 100. Readings above 70 indicate overbought,
121 /// below 30 indicate oversold conditions.
122 ///
123 /// # Example
124 ///
125 /// ```no_run
126 /// use finance_query::{Ticker, Interval, TimeRange};
127 ///
128 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
129 /// let ticker = Ticker::new("AAPL").await?;
130 /// let chart = ticker.chart(Interval::OneDay, TimeRange::OneMonth).await?;
131 ///
132 /// let rsi = chart.rsi(14)?;
133 /// # Ok(())
134 /// # }
135 /// ```
136 pub fn rsi(&self, period: usize) -> crate::indicators::Result<Vec<Option<f64>>> {
137 crate::indicators::rsi(&self.close_prices(), period)
138 }
139
140 /// Calculate Moving Average Convergence Divergence (MACD).
141 ///
142 /// Standard parameters are (12, 26, 9).
143 ///
144 /// # Example
145 ///
146 /// ```no_run
147 /// use finance_query::{Ticker, Interval, TimeRange};
148 ///
149 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
150 /// let ticker = Ticker::new("AAPL").await?;
151 /// let chart = ticker.chart(Interval::OneDay, TimeRange::OneMonth).await?;
152 ///
153 /// let macd_result = chart.macd(12, 26, 9)?;
154 /// println!("MACD Line: {:?}", macd_result.macd_line);
155 /// println!("Signal Line: {:?}", macd_result.signal_line);
156 /// println!("Histogram: {:?}", macd_result.histogram);
157 /// # Ok(())
158 /// # }
159 /// ```
160 pub fn macd(
161 &self,
162 fast_period: usize,
163 slow_period: usize,
164 signal_period: usize,
165 ) -> crate::indicators::Result<crate::indicators::MacdResult> {
166 crate::indicators::macd(
167 &self.close_prices(),
168 fast_period,
169 slow_period,
170 signal_period,
171 )
172 }
173
174 /// Calculate Bollinger Bands.
175 ///
176 /// Standard parameters are (20, 2.0) for period and std_dev_multiplier.
177 ///
178 /// # Example
179 ///
180 /// ```no_run
181 /// use finance_query::{Ticker, Interval, TimeRange};
182 ///
183 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
184 /// let ticker = Ticker::new("AAPL").await?;
185 /// let chart = ticker.chart(Interval::OneDay, TimeRange::OneMonth).await?;
186 ///
187 /// let bb = chart.bollinger_bands(20, 2.0)?;
188 /// println!("Upper: {:?}", bb.upper);
189 /// println!("Middle: {:?}", bb.middle);
190 /// println!("Lower: {:?}", bb.lower);
191 /// # Ok(())
192 /// # }
193 /// ```
194 pub fn bollinger_bands(
195 &self,
196 period: usize,
197 std_dev_multiplier: f64,
198 ) -> crate::indicators::Result<crate::indicators::BollingerBands> {
199 crate::indicators::bollinger_bands(&self.close_prices(), period, std_dev_multiplier)
200 }
201
202 /// Calculate Average True Range (ATR).
203 ///
204 /// ATR measures market volatility. Standard period is 14.
205 ///
206 /// # Example
207 ///
208 /// ```no_run
209 /// use finance_query::{Ticker, Interval, TimeRange};
210 ///
211 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
212 /// let ticker = Ticker::new("AAPL").await?;
213 /// let chart = ticker.chart(Interval::OneDay, TimeRange::OneMonth).await?;
214 ///
215 /// let atr = chart.atr(14)?;
216 /// # Ok(())
217 /// # }
218 /// ```
219 pub fn atr(&self, period: usize) -> crate::indicators::Result<Vec<Option<f64>>> {
220 crate::indicators::atr(
221 &self.high_prices(),
222 &self.low_prices(),
223 &self.close_prices(),
224 period,
225 )
226 }
227
228 /// Detect candlestick patterns across all bars.
229 ///
230 /// Returns a `Vec<Option<CandlePattern>>` of the same length as `candles`.
231 /// `Some(pattern)` means a pattern was detected on that bar; `None` means
232 /// no pattern matched. Three-bar patterns take precedence over two-bar,
233 /// which take precedence over one-bar.
234 ///
235 /// # Example
236 ///
237 /// ```no_run
238 /// use finance_query::{Ticker, Interval, TimeRange};
239 /// use finance_query::indicators::{CandlePattern, PatternSentiment};
240 ///
241 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
242 /// let ticker = Ticker::new("AAPL").await?;
243 /// let chart = ticker.chart(Interval::OneDay, TimeRange::SixMonths).await?;
244 ///
245 /// let signals = chart.patterns();
246 /// let bullish_count = signals
247 /// .iter()
248 /// .filter(|s| s.map(|p| p.sentiment() == PatternSentiment::Bullish).unwrap_or(false))
249 /// .count();
250 /// println!("{bullish_count} bullish patterns in the last 6 months");
251 /// # Ok(())
252 /// # }
253 /// ```
254 pub fn patterns(&self) -> Vec<Option<crate::indicators::CandlePattern>> {
255 crate::indicators::patterns(&self.candles)
256 }
257}