traquer/
volatility.rs

1//! Volatility Indicators
2//!
3//! Indicators that measure the price movement, regardless of direction. In essence, it is
4//! signaling whether there is a trend or not generally based on the delta between the
5//! highest and lowest prices in period. It may also be represented as channels for which
6//! it expects prices to fall within.
7
8use std::iter;
9
10use itertools::izip;
11use num_traits::cast::ToPrimitive;
12
13use crate::smooth;
14use crate::statistic::distribution::{_std_dev as unpadded_std_dev, std_dev as padded_std_dev};
15
16/// Mass Index
17///
18/// Measures the volatility of stock prices by calculating a ratio of two exponential
19/// moving averages of the high-low differential.
20///
21/// ## Usage
22///
23/// A high value suggests a potential reversal in trend.
24///
25/// ## Sources
26///
27/// [[1]](https://www.investopedia.com/terms/m/mass-index.asp)
28///
29/// # Examples
30///
31/// ```
32/// use traquer::volatility;
33///
34/// volatility::mass(
35///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
36///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
37///     3, 6).collect::<Vec<f64>>();
38///
39/// ```
40pub fn mass<'a, T: ToPrimitive>(
41    high: &'a [T],
42    low: &'a [T],
43    short: usize,
44    long: usize,
45) -> impl Iterator<Item = f64> + 'a {
46    let ma1 = smooth::ewma(
47        &high
48            .iter()
49            .zip(low)
50            .map(|(h, l)| h.to_f64().unwrap() - l.to_f64().unwrap())
51            .collect::<Vec<f64>>(),
52        short,
53    )
54    .collect::<Vec<f64>>();
55    let ma2 = smooth::ewma(&ma1[short - 1..], short);
56    iter::repeat(f64::NAN).take((long - 1) + (short - 1)).chain(
57        ma1.iter()
58            .skip(short - 1)
59            .zip(ma2)
60            .map(|(num, denom)| num / denom)
61            .collect::<Vec<f64>>()
62            .windows(long)
63            .map(|w| w.iter().sum::<f64>())
64            .collect::<Vec<f64>>(),
65    )
66}
67
68/// Keltner Channel
69///
70/// Upper and lower bands are defined by True Range from moving average.
71///
72/// ## Sources
73///
74/// [[1]](https://www.investopedia.com/terms/k/keltnerchannel.asp)
75///
76/// # Examples
77///
78/// ```
79/// use traquer::volatility;
80///
81/// volatility::keltner(
82///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
83///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
84///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
85///     6).collect::<Vec<(f64,f64,f64)>>();
86///
87/// ```
88pub fn keltner<'a, T: ToPrimitive>(
89    high: &'a [T],
90    low: &'a [T],
91    close: &'a [T],
92    window: usize,
93) -> impl Iterator<Item = (f64, f64, f64)> + 'a {
94    smooth::ewma(close, window)
95        .zip(atr(high, low, close, window))
96        .map(|(middle, atr)| (middle, middle + 2.0 * atr, middle - 2.0 * atr))
97}
98
99/// Gopalakrishnan Range Index
100///
101/// Calculates the range of a security's price action over a specified period,
102/// providing insights into the volatility
103///
104/// ## Usage
105///
106/// A high value suggests a strong trend while a low value suggests sideways movement.
107///
108/// ## Sources
109///
110/// [[1]](https://superalgos.org/Docs/Foundations/Topic/gapo.shtml)
111/// [[2]](https://library.tradingtechnologies.com/trade/chrt-ti-gopalakrishnan-range-index.html)
112///
113/// # Examples
114///
115/// ```
116/// use traquer::volatility;
117///
118/// volatility::gri(
119///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
120///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
121///     6).collect::<Vec<f64>>();
122///
123/// ```
124pub fn gri<'a, T: ToPrimitive>(
125    high: &'a [T],
126    low: &'a [T],
127    window: usize,
128) -> impl Iterator<Item = f64> + 'a {
129    iter::repeat(f64::NAN).take(window - 1).chain(
130        high.windows(window)
131            .zip(low.windows(window))
132            .map(move |(h, l)| {
133                let hh = h
134                    .iter()
135                    .fold(f64::NAN, |state, x| state.max(x.to_f64().unwrap()));
136                let ll = l
137                    .iter()
138                    .fold(f64::NAN, |state, x| state.min(x.to_f64().unwrap()));
139                f64::ln(hh - ll) / f64::ln(window as f64)
140            }),
141    )
142}
143
144pub(crate) fn _true_range<'a, T: ToPrimitive>(
145    high: &'a [T],
146    low: &'a [T],
147    close: &'a [T],
148) -> impl Iterator<Item = f64> + 'a {
149    izip!(&high[1..], &low[1..], &close[..close.len() - 1]).map(|(h, l, prevc)| {
150        let (h, l, prevc) = (
151            h.to_f64().unwrap(),
152            l.to_f64().unwrap(),
153            prevc.to_f64().unwrap(),
154        );
155        (h - l).max(f64::abs(h - prevc)).max(f64::abs(l - prevc))
156    })
157}
158
159/// True Range
160///
161/// Measures market volatility by computing the greatest of the following: current high less
162/// the current low; the absolute value of the current high less the previous close;
163/// and the absolute value of the current low less the previous close.
164///
165/// ## Sources
166///
167/// [[1]](https://www.investopedia.com/terms/a/atr.asp)
168///
169/// # Examples
170///
171/// ```
172/// use traquer::volatility;
173///
174/// volatility::tr(
175///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
176///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
177///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
178///     ).collect::<Vec<f64>>();
179///
180/// ```
181pub fn tr<'a, T: ToPrimitive>(
182    high: &'a [T],
183    low: &'a [T],
184    close: &'a [T],
185) -> impl Iterator<Item = f64> + 'a {
186    iter::once(f64::NAN).chain(_true_range(high, low, close))
187}
188
189/// Average True Range
190///
191/// Moving average of the True Range series
192///
193/// ## Sources
194///
195/// [[1]](https://www.investopedia.com/terms/a/atr.asp)
196///
197/// # Examples
198///
199/// ```
200/// use traquer::volatility;
201///
202/// volatility::atr(
203///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
204///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
205///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
206///     6).collect::<Vec<f64>>();
207///
208/// ```
209pub fn atr<'a, T: ToPrimitive>(
210    high: &'a [T],
211    low: &'a [T],
212    close: &'a [T],
213    window: usize,
214) -> impl Iterator<Item = f64> + 'a {
215    iter::once(f64::NAN).chain(
216        smooth::wilder(&_true_range(high, low, close).collect::<Vec<f64>>(), window)
217            .collect::<Vec<f64>>(),
218    )
219}
220
221/// Typical Price
222///
223/// Average of a given day's high, low, and close price.
224///
225/// ## Sources
226///
227/// [[1]](https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/typical-price)
228///
229/// # Examples
230///
231/// ```
232/// use traquer::volatility;
233///
234/// volatility::typical(
235///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
236///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
237///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
238///     6).collect::<Vec<f64>>();
239///
240/// ```
241pub fn typical<'a, T: ToPrimitive>(
242    high: &'a [T],
243    low: &'a [T],
244    close: &'a [T],
245    window: usize,
246) -> impl Iterator<Item = f64> + 'a {
247    smooth::sma(
248        &izip!(high, low, close)
249            .map(|(h, l, c)| {
250                (h.to_f64().unwrap() + l.to_f64().unwrap() + c.to_f64().unwrap()) / 3.0
251            })
252            .collect::<Vec<f64>>(),
253        window,
254    )
255    .collect::<Vec<f64>>()
256    .into_iter()
257}
258
259/// Standard Deviation
260///
261/// Measures market volatility. Standard deviation of price over a period.
262///
263/// # Examples
264///
265/// ```
266/// use traquer::volatility;
267///
268/// volatility::std_dev(
269///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
270///     6, Some(1.0)).collect::<Vec<f64>>();
271///
272/// ```
273pub fn std_dev<T: ToPrimitive>(
274    data: &[T],
275    window: usize,
276    deviations: Option<f64>,
277) -> impl Iterator<Item = f64> + '_ {
278    let devs = deviations.unwrap_or(2.0);
279    padded_std_dev(data, window).map(move |x| x * devs)
280}
281
282/// Bollinger Bands
283///
284/// Channels defined by standard deviations away from a moving average.
285///
286/// ## Sources
287///
288/// [[1]](https://www.investopedia.com/terms/b/bollingerbands.asp)
289///
290/// # Examples
291///
292/// ```
293/// use traquer::{volatility, smooth};
294///
295/// volatility::bbands(
296///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
297///     6, Some(1.0), Some(smooth::MaMode::SMA)).collect::<Vec<(f64,f64,f64)>>();
298///
299/// ```
300pub fn bbands<T: ToPrimitive>(
301    data: &[T],
302    window: usize,
303    deviations: Option<f64>,
304    mamode: Option<smooth::MaMode>,
305) -> impl Iterator<Item = (f64, f64, f64)> + '_ {
306    smooth::ma(data, window, mamode.unwrap_or(smooth::MaMode::EWMA))
307        .zip(std_dev(data, window, deviations))
308        .map(|(ma, stdev)| (ma + stdev, ma, ma - stdev))
309}
310
311/// Donchian Channels
312///
313/// Channels defined by highest high and lowest low
314///
315/// ## Sources
316///
317/// [[1]](https://www.tradingview.com/support/solutions/43000502253-donchian-channels-dc/)
318///
319/// # Examples
320///
321/// ```
322/// use traquer::volatility;
323///
324/// volatility::donchian(
325///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
326///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
327///     6).collect::<Vec<(f64,f64,f64)>>();
328///
329/// ```
330pub fn donchian<'a, T: ToPrimitive>(
331    high: &'a [T],
332    low: &'a [T],
333    window: usize,
334) -> impl Iterator<Item = (f64, f64, f64)> + 'a {
335    iter::repeat((f64::NAN, f64::NAN, f64::NAN))
336        .take(window - 1)
337        .chain(high.windows(window).zip(low.windows(window)).map(|(h, l)| {
338            let hh = h
339                .iter()
340                .fold(f64::NAN, |state, x| state.max(x.to_f64().unwrap()));
341            let ll = l
342                .iter()
343                .fold(f64::NAN, |state, x| state.min(x.to_f64().unwrap()));
344            (hh, (hh + ll) / 2.0, ll)
345        }))
346}
347
348/// Fractal Chaos Bands
349///
350/// Channels defined by peaks or valleys in prior period.
351///
352/// ## Sources
353///
354/// [[1]](https://www.tradingview.com/script/Yy2ASjTq-Fractal-Chaos-Bands/)
355///
356/// # Examples
357///
358/// ```
359/// use traquer::volatility;
360///
361/// volatility::fbands(
362///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
363///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
364///     ).collect::<Vec<(f64,f64)>>();
365///
366/// ```
367pub fn fbands<'a, T: ToPrimitive>(
368    high: &'a [T],
369    low: &'a [T],
370) -> impl Iterator<Item = (f64, f64)> + 'a {
371    let win = 5;
372    iter::repeat((f64::NAN, f64::NAN)).take(win - 1).chain(
373        high.windows(win)
374            .zip(low.windows(win))
375            .scan((0.0, 0.0), |state, (h, l)| {
376                let (hh, _) =
377                    h.iter()
378                        .enumerate()
379                        .fold((0, h[0].to_f64().unwrap()), |state, (idx, x)| {
380                            let x = x.to_f64().unwrap();
381                            if x > state.1 {
382                                (idx, x)
383                            } else {
384                                state
385                            }
386                        });
387                let upper = if hh == 2 {
388                    h[2].to_f64().unwrap()
389                } else {
390                    state.0
391                };
392
393                let (ll, _) =
394                    l.iter()
395                        .enumerate()
396                        .fold((0, l[0].to_f64().unwrap()), |state, (idx, x)| {
397                            let x = x.to_f64().unwrap();
398                            if x < state.1 {
399                                (idx, x)
400                            } else {
401                                state
402                            }
403                        });
404                let lower = if ll == 2 {
405                    l[2].to_f64().unwrap()
406                } else {
407                    state.1
408                };
409                *state = (upper, lower);
410                Some(*state)
411            })
412            .collect::<Vec<(f64, f64)>>(),
413    )
414}
415
416/// Historical Volatility
417///
418/// Measures the standard deviation of returns, annualised.
419///
420/// ## Usage
421///
422/// A higher value suggests higher risk.
423///
424/// ## Sources
425///
426/// [[1]](https://www.macroption.com/historical-volatility-calculation/)
427///
428/// # Examples
429///
430/// ```
431/// use traquer::volatility;
432///
433/// volatility::hv(
434///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
435///     3, Some(1.0)).collect::<Vec<f64>>();
436///
437/// ```
438pub fn hv<T: ToPrimitive>(
439    data: &[T],
440    window: usize,
441    deviations: Option<f64>,
442) -> impl Iterator<Item = f64> + '_ {
443    let annualize = 252.0 * deviations.unwrap_or(1.0);
444    iter::repeat(f64::NAN).take(window).chain(
445        data.windows(2)
446            .map(|pair| f64::ln(pair[1].to_f64().unwrap() / pair[0].to_f64().unwrap()))
447            .collect::<Vec<f64>>()
448            .windows(window)
449            .map(move |w| {
450                let mean = w.iter().sum::<f64>() / window as f64;
451                (w.iter().map(|x| (x - mean).powi(2)).sum::<f64>() * annualize / window as f64)
452                    .sqrt()
453            })
454            .collect::<Vec<f64>>(),
455    )
456}
457
458/// Stoller Average Range Channel (STARC)
459///
460/// Upper and lower bands are defined by True Range from moving average with a multiplier.
461/// Similar to Keltner Channels.
462///
463/// [[1]](https://www.investopedia.com/terms/s/starc.asp)
464///
465/// # Examples
466///
467/// ```
468/// use traquer::volatility;
469///
470/// volatility::starc(
471///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
472///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
473///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
474///     6, 2, Some(1.3)).collect::<Vec<(f64,f64)>>();
475///
476/// ```
477pub fn starc<'a, T: ToPrimitive>(
478    high: &'a [T],
479    low: &'a [T],
480    close: &'a [T],
481    window: usize,
482    ma_window: usize,
483    multiplier: Option<f64>,
484) -> impl Iterator<Item = (f64, f64)> + 'a {
485    let multiplier = multiplier.unwrap_or(1.2);
486    atr(high, low, close, window)
487        .zip(smooth::sma(close, ma_window))
488        .map(move |(atr, ma)| (ma + multiplier * atr, ma - multiplier * atr))
489}
490
491/// Chaikin volatility
492///
493/// Measures the volatility of a security's price action by comparing the spread between
494/// the high and low prices over a specified period.
495///
496/// ## Usage
497///
498/// A high value suggests high volatility.
499///
500/// ## Sources
501///
502/// [[1]](https://www.tradingview.com/chart/AUDUSD/gjfxqWqW-What-Is-a-Chaikin-Volatility-Indicator-in-Trading/)
503/// [[2]](https://theforexgeek.com/chaikins-volatility-indicator/)
504///
505/// # Examples
506///
507/// ```
508/// use traquer::volatility;
509///
510/// volatility::cvi(
511///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
512///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
513///     6, 3).collect::<Vec<f64>>();
514///
515/// ```
516pub fn cvi<'a, T: ToPrimitive>(
517    high: &'a [T],
518    low: &'a [T],
519    window: usize,
520    rate_of_change: usize,
521) -> impl Iterator<Item = f64> + 'a {
522    iter::repeat(f64::NAN).take(rate_of_change).chain(
523        smooth::ewma(
524            &high
525                .iter()
526                .zip(low)
527                .map(|(h, l)| h.to_f64().unwrap() - l.to_f64().unwrap())
528                .collect::<Vec<f64>>(),
529            window,
530        )
531        .collect::<Vec<f64>>()
532        .windows(rate_of_change + 1)
533        .map(|w| 100.0 * (w.last().unwrap() / w.first().unwrap() - 1.0))
534        .collect::<Vec<f64>>(),
535    )
536}
537
538/// Relative Volatility
539///
540/// Measures the direction and magnitude of volatility in an asset’s price. Unlike the
541/// Relative Strength Index (RSI), which uses absolute prices, the RVI uses standard deviation.
542///
543/// ## Usage
544///
545/// A high value suggests higher volatility.
546///
547/// ## Sources
548///
549/// [[1]](https://www.tradingview.com/support/solutions/43000594684-relative-volatility-index/)
550///
551/// # Examples
552///
553/// ```
554/// use traquer::volatility;
555///
556/// volatility::relative_vol(
557///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
558///     6, 2).collect::<Vec<f64>>();
559///
560/// ```
561pub fn relative_vol<T: ToPrimitive>(
562    close: &[T],
563    window: usize,
564    smoothing: usize,
565) -> impl Iterator<Item = f64> + '_ {
566    let (gain, loss): (Vec<f64>, Vec<f64>) = izip!(
567        unpadded_std_dev(close, window),
568        &close[window - 1..],
569        &close[window - 2..close.len() - 1]
570    )
571    .map(|(std, curr, prev)| {
572        let (curr, prev) = (curr.to_f64().unwrap(), prev.to_f64().unwrap());
573        (
574            f64::max(0.0, (curr - prev).signum()) * std,
575            f64::max(0.0, (prev - curr).signum()) * std,
576        )
577    })
578    .unzip();
579    iter::repeat(f64::NAN).take(window - 1).chain(
580        smooth::wilder(&gain, smoothing)
581            .zip(smooth::wilder(&loss, smoothing))
582            .map(|(g, l)| 100.0 * g / (g + l))
583            .collect::<Vec<f64>>(),
584    )
585}
586
587/// Inertia
588///
589/// An extension of Donald Dorsey’s Relative Volatility Index (RVI). The name “Inertia”
590/// reflects the concept that trends require more force to reverse than to continue
591/// in the same direction.
592///
593/// ## Usage
594///
595/// A high value suggests higher volatility.
596///
597/// ## Sources
598///
599/// [[1]](https://www.tradingview.com/script/ODEBlQkx-Inertia-Indicator/)
600/// [[2]](https://theforexgeek.com/inertia-indicator/)
601///
602/// # Examples
603///
604/// ```
605/// use traquer::volatility;
606///
607/// volatility::inertia(
608///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
609///     3, 2).collect::<Vec<f64>>();
610///
611/// ```
612pub fn inertia<T: ToPrimitive>(
613    close: &[T],
614    window: usize,
615    smoothing: usize,
616) -> impl Iterator<Item = f64> + '_ {
617    smooth::lrf(
618        &relative_vol(close, window, window).collect::<Vec<f64>>(),
619        smoothing,
620    )
621    .collect::<Vec<f64>>()
622    .into_iter()
623}
624
625/// Choppiness Index
626///
627/// Aims to capture the true volatility and directionality of the market by taking
628/// into account the range between the highest high and the lowest low prices over
629/// a specified period.
630///
631/// ## Usage
632///
633/// A high value suggests a strong trend while a low value suggests sideways movement.
634///
635/// ## Sources
636///
637/// [[1]](https://www.tradingview.com/support/solutions/43000501980-choppiness-index-chop/)
638///
639/// # Examples
640///
641/// ```
642/// use traquer::volatility;
643///
644/// volatility::chop(
645///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
646///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
647///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
648///     6).collect::<Vec<f64>>();
649///
650/// ```
651pub fn chop<'a, T: ToPrimitive>(
652    high: &'a [T],
653    low: &'a [T],
654    close: &'a [T],
655    window: usize,
656) -> impl Iterator<Item = f64> + 'a {
657    iter::repeat(f64::NAN).take(window).chain(
658        izip!(
659            high[1..].windows(window),
660            low[1..].windows(window),
661            _true_range(high, low, close)
662                .collect::<Vec<f64>>()
663                .windows(window)
664        )
665        .map(|(h, l, tr)| {
666            let hh = h
667                .iter()
668                .fold(f64::NAN, |state, x| state.max(x.to_f64().unwrap()));
669            let ll = l
670                .iter()
671                .fold(f64::NAN, |state, x| state.min(x.to_f64().unwrap()));
672            let tr_sum = tr.iter().sum::<f64>();
673            100.0 * f64::ln(tr_sum / (hh - ll)) / f64::ln(window as f64)
674        })
675        .collect::<Vec<f64>>(),
676    )
677}
678
679/// Vertical Horizontal Filter
680///
681/// Measures the level of trend activity in a financial market by comparing the max price
682/// range over a specific period to the cumulative price movement within that period.
683///
684/// ## Usage
685///
686/// A higher VHF value indicates a trending market, while lower VHF suggests consolidation.
687///
688/// ## Sources
689///
690/// [[1]](https://www.upcomingtrader.com/blog/the-vertical-horizontal-filter-a-traders-guide-to-market-phases/)
691///
692/// # Examples
693///
694/// ```
695/// use traquer::volatility;
696///
697/// volatility::vhf(
698///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
699///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
700///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
701///     6).collect::<Vec<f64>>();
702///
703/// ```
704pub fn vhf<'a, T: ToPrimitive>(
705    high: &'a [T],
706    low: &'a [T],
707    close: &'a [T],
708    window: usize,
709) -> impl Iterator<Item = f64> + 'a {
710    let diffs = close
711        .windows(2)
712        .map(|pair| {
713            let (prev, curr) = (pair[0].to_f64().unwrap(), pair[1].to_f64().unwrap());
714            (curr - prev).abs()
715        })
716        .collect::<Vec<f64>>();
717    iter::repeat(f64::NAN).take(window).chain(
718        izip!(
719            diffs.windows(window),
720            high.windows(window).skip(1),
721            low.windows(window).skip(1)
722        )
723        .map(|(diff, h, l)| {
724            (h.iter()
725                .fold(f64::NAN, |state, x| state.max(x.to_f64().unwrap()))
726                - l.iter()
727                    .fold(f64::NAN, |state, x| state.min(x.to_f64().unwrap())))
728                / diff.iter().sum::<f64>()
729        })
730        .collect::<Vec<f64>>(),
731    )
732}
733
734/// Heikin-Ashi Candlestick
735///
736/// Uses a modified formula based on two-period averages to derive OHLC candlesticks
737///
738/// ## Usage
739///
740/// Like normal chandlestick chart.
741///
742/// ## Sources
743///
744/// [[1]](https://www.marketvolume.com/technicalanalysis/heikin_ashi_candlesticks.asp)
745/// [[2]](https://www.investopedia.com/terms/h/heikinashi.asp)
746///
747/// # Examples
748///
749/// ```
750/// use traquer::volatility;
751///
752/// volatility::heikin_ashi(
753///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
754///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
755///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
756///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
757///     ).collect::<Vec<(f64,f64,f64,f64)>>();
758///
759/// ```
760pub fn heikin_ashi<'a, T: ToPrimitive>(
761    open: &'a [T],
762    high: &'a [T],
763    low: &'a [T],
764    close: &'a [T],
765) -> impl Iterator<Item = (f64, f64, f64, f64)> + 'a {
766    let c_0 = (open[0].to_f64().unwrap()
767        + high[0].to_f64().unwrap()
768        + low[0].to_f64().unwrap()
769        + close[0].to_f64().unwrap())
770        / 4.0;
771    let o_0 = (open[0].to_f64().unwrap() + close[0].to_f64().unwrap()) / 2.0;
772    iter::once((
773        o_0,
774        high[0].to_f64().unwrap(),
775        low[0].to_f64().unwrap(),
776        c_0,
777    ))
778    .chain(
779        izip!(
780            open[1..].iter(),
781            high[1..].iter(),
782            low[1..].iter(),
783            close[1..].iter()
784        )
785        .scan((o_0, c_0), |(prevo, prevc), (o, h, l, c)| {
786            let (o, h, l, c) = (
787                o.to_f64().unwrap(),
788                h.to_f64().unwrap(),
789                l.to_f64().unwrap(),
790                c.to_f64().unwrap(),
791            );
792            let c_i = (o + h + l + c) / 4.0;
793            let o_i = (*prevo + *prevc) / 2.0;
794            let h_i = h.max(o_i).max(c_i);
795            let l_i = l.min(o_i).min(c_i);
796            *prevo = o_i;
797            *prevc = c_i;
798            Some((o_i, h_i, l_i, c_i))
799        }),
800    )
801}
802
803/// Weighted Closing Price
804///
805/// Doubles the closing price.
806///
807/// ## Usage
808///
809/// Similar to ATR.
810///
811/// # Examples
812///
813/// ```
814/// use traquer::volatility;
815///
816/// volatility::wcp(
817///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
818///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
819///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
820///     ).collect::<Vec<f64>>();
821///
822/// ```
823pub fn wcp<'a, T: ToPrimitive>(
824    high: &'a [T],
825    low: &'a [T],
826    close: &'a [T],
827) -> impl Iterator<Item = f64> + 'a {
828    izip!(high, low, close).map(|(h, l, c)| {
829        (h.to_f64().unwrap() + l.to_f64().unwrap() + 2.0 * c.to_f64().unwrap()) / 4.0
830    })
831}
832
833/// Close-to-Close Historical Volatility
834///
835/// The simplest volatility estimator. Calculated using only stock's closing prices. Value is annualised.
836///
837/// ## Usage
838///
839/// Higher value implies increase volatility.
840///
841/// ## Sources
842///
843/// [[1]](https://harbourfronts.com/close-close-historical-volatility-calculation-volatility-analysis-python/)
844///
845/// # Examples
846///
847/// ```
848/// use traquer::volatility;
849///
850/// volatility::cc_hv(
851///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
852///     3).collect::<Vec<f64>>();
853///
854/// ```
855pub fn cc_hv<T: ToPrimitive>(data: &[T], window: usize) -> impl Iterator<Item = f64> + '_ {
856    iter::repeat(f64::NAN).take(window).chain(
857        data.windows(2)
858            .map(|w| {
859                (w[1].to_f64().unwrap() / w[0].to_f64().unwrap())
860                    .ln()
861                    .powi(2)
862            })
863            .collect::<Vec<f64>>()
864            .windows(window)
865            .map(|w| (w.iter().sum::<f64>() * 252.0 / window as f64).sqrt())
866            .collect::<Vec<f64>>(),
867    )
868}
869
870/// Garman-Klass-Yang-Zhang Historical Volatility
871///
872/// Extends Garman-Klass volatility estimator by taking into consideration overnight jumps. Value
873/// is annualised.
874///
875/// ## Usage
876///
877/// Higher value implies increase volatility.
878///
879/// ## Sources
880///
881/// [[1]](https://harbourfronts.com/garman-klass-yang-zhang-historical-volatility-calculation-volatility-analysis-python/)
882///
883/// # Examples
884///
885/// ```
886/// use traquer::volatility;
887///
888/// volatility::gkyz_hv(
889///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
890///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
891///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
892///     &[1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
893///     3).collect::<Vec<f64>>();
894///
895/// ```
896pub fn gkyz_hv<'a, T: ToPrimitive>(
897    open: &'a [T],
898    high: &'a [T],
899    low: &'a [T],
900    close: &'a [T],
901    window: usize,
902) -> impl Iterator<Item = f64> + 'a {
903    iter::repeat(f64::NAN).take(window).chain(
904        izip!(&open[1..], &high[1..], &low[1..], &close[1..], close)
905            .map(|(o, h, l, c, c0)| {
906                let (o, h, l, c, c0) = (
907                    o.to_f64().unwrap(),
908                    h.to_f64().unwrap(),
909                    l.to_f64().unwrap(),
910                    c.to_f64().unwrap(),
911                    c0.to_f64().unwrap(),
912                );
913                (o / c0).ln().powi(2) + 0.5 * (h / l).ln().powi(2)
914                    - (2.0 * 2_f64.ln() - 1.0) * (c / o).ln().powi(2)
915            })
916            .collect::<Vec<f64>>()
917            .windows(window)
918            .map(|w| (w.iter().sum::<f64>() * 252.0 / window as f64).sqrt())
919            .collect::<Vec<f64>>(),
920    )
921}