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