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::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}