Skip to main content

indicators/volatility/
elder_ray_index.rs

1//! Elder Ray Index.
2//!
3//! Python source: `indicators/other/elder_ray_index.py :: class ElderRayIndexIndicator`
4//!
5//! # Python algorithm (to port)
6//! ```python
7//! ema        = data["Close"].ewm(span=self.fast_period, adjust=False).mean()
8//! bull_power = data["High"] - ema
9//! bear_power = data["Low"]  - ema
10//! ```
11//!
12//! Output columns: `"ElderRay_bull"`, `"ElderRay_bear"`.
13
14use std::collections::HashMap;
15
16use crate::error::IndicatorError;
17use crate::functions::{self};
18use crate::indicator::{Indicator, IndicatorOutput};
19use crate::registry::param_usize;
20use crate::types::Candle;
21
22#[derive(Debug, Clone)]
23pub struct ElderRayParams {
24    /// EMA period for the base line.  Python default: 14.
25    pub fast_period: usize,
26}
27impl Default for ElderRayParams {
28    fn default() -> Self {
29        Self { fast_period: 14 }
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct ElderRayIndex {
35    pub params: ElderRayParams,
36}
37
38impl ElderRayIndex {
39    pub fn new(params: ElderRayParams) -> Self {
40        Self { params }
41    }
42    pub fn with_period(period: usize) -> Self {
43        Self::new(ElderRayParams {
44            fast_period: period,
45        })
46    }
47}
48
49impl Indicator for ElderRayIndex {
50    fn name(&self) -> &'static str {
51        "ElderRayIndex"
52    }
53    fn required_len(&self) -> usize {
54        self.params.fast_period
55    }
56    fn required_columns(&self) -> &[&'static str] {
57        &["high", "low", "close"]
58    }
59
60    /// TODO: port Python EMA-based bull/bear power.
61    fn calculate(&self, candles: &[Candle]) -> Result<IndicatorOutput, IndicatorError> {
62        self.check_len(candles)?;
63
64        let close: Vec<f64> = candles.iter().map(|c| c.close).collect();
65        let high: Vec<f64> = candles.iter().map(|c| c.high).collect();
66        let low: Vec<f64> = candles.iter().map(|c| c.low).collect();
67
68        let ema = functions::ema(&close, self.params.fast_period)?;
69
70        let bull: Vec<f64> = high.iter().zip(&ema).map(|(&h, &e)| h - e).collect();
71        let bear: Vec<f64> = low.iter().zip(&ema).map(|(&l, &e)| l - e).collect();
72
73        Ok(IndicatorOutput::from_pairs([
74            ("ElderRay_bull".to_string(), bull),
75            ("ElderRay_bear".to_string(), bear),
76        ]))
77    }
78}
79
80pub fn factory<S: ::std::hash::BuildHasher>(params: &HashMap<String, String, S>) -> Result<Box<dyn Indicator>, IndicatorError> {
81    Ok(Box::new(ElderRayIndex::new(ElderRayParams {
82        fast_period: param_usize(params, "fast_period", 14)?,
83    })))
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    fn candles(n: usize) -> Vec<Candle> {
91        (0..n)
92            .map(|i| Candle {
93                time: i64::try_from(i).expect("time index fits i64"),
94                open: 10.0,
95                high: 12.0,
96                low: 8.0,
97                close: 10.0 + i as f64 * 0.1,
98                volume: 100.0,
99            })
100            .collect()
101    }
102
103    #[test]
104    fn elder_ray_two_columns() {
105        let out = ElderRayIndex::with_period(14)
106            .calculate(&candles(20))
107            .unwrap();
108        assert!(out.get("ElderRay_bull").is_some());
109        assert!(out.get("ElderRay_bear").is_some());
110    }
111
112    #[test]
113    fn bull_power_is_high_minus_ema() {
114        // Bull power must always be >= bear power (high >= low).
115        let out = ElderRayIndex::with_period(5)
116            .calculate(&candles(20))
117            .unwrap();
118        let bull = out.get("ElderRay_bull").unwrap();
119        let bear = out.get("ElderRay_bear").unwrap();
120        for i in 5..20 {
121            if !bull[i].is_nan() {
122                assert!(bull[i] >= bear[i], "bull < bear at {i}");
123            }
124        }
125    }
126
127    #[test]
128    fn factory_creates_elder_ray() {
129        assert_eq!(factory(&HashMap::new()).unwrap().name(), "ElderRayIndex");
130    }
131}