indicators/volatility/
elder_ray_index.rs1use std::collections::HashMap;
15
16use crate::functions::{self};
17use crate::error::IndicatorError;
18use crate::indicator::{Indicator, IndicatorOutput};
19use crate::registry::param_usize;
20use crate::types::Candle;
21
22#[derive(Debug, Clone)]
23pub struct ElderRayParams {
24 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) -> &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 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(params: &HashMap<String, String>) -> 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).map(|i| Candle {
92 time: i as i64, open: 10.0, high: 12.0, low: 8.0,
93 close: 10.0 + i as f64 * 0.1, volume: 100.0,
94 }).collect()
95 }
96
97 #[test]
98 fn elder_ray_two_columns() {
99 let out = ElderRayIndex::with_period(14).calculate(&candles(20)).unwrap();
100 assert!(out.get("ElderRay_bull").is_some());
101 assert!(out.get("ElderRay_bear").is_some());
102 }
103
104 #[test]
105 fn bull_power_is_high_minus_ema() {
106 let out = ElderRayIndex::with_period(5).calculate(&candles(20)).unwrap();
108 let bull = out.get("ElderRay_bull").unwrap();
109 let bear = out.get("ElderRay_bear").unwrap();
110 for i in 5..20 {
111 if !bull[i].is_nan() {
112 assert!(bull[i] >= bear[i], "bull < bear at {i}");
113 }
114 }
115 }
116
117 #[test]
118 fn factory_creates_elder_ray() {
119 assert_eq!(factory(&HashMap::new()).unwrap().name(), "ElderRayIndex");
120 }
121}