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