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}