Skip to main content

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}