wickra_core/indicators/
demark_pivots.rs1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct DemarkPivotsOutput {
9 pub pp: f64,
11 pub r1: f64,
13 pub s1: f64,
15}
16
17#[derive(Debug, Clone, Default)]
46pub struct DemarkPivots {
47 ready: bool,
48}
49
50impl DemarkPivots {
51 pub const fn new() -> Self {
53 Self { ready: false }
54 }
55}
56
57impl Indicator for DemarkPivots {
58 type Input = Candle;
59 type Output = DemarkPivotsOutput;
60
61 #[inline]
62 fn update(&mut self, candle: Candle) -> Option<DemarkPivotsOutput> {
63 let open = candle.open;
64 let high = candle.high;
65 let low = candle.low;
66 let close = candle.close;
67 let x = if close < open {
68 high + 2.0 * low + close
69 } else if close > open {
70 2.0 * high + low + close
71 } else {
72 high + low + 2.0 * close
73 };
74 let pp = x / 4.0;
75 let half = x / 2.0;
76 let out = DemarkPivotsOutput {
77 pp,
78 r1: half - low,
79 s1: half - high,
80 };
81 self.ready = true;
82 Some(out)
83 }
84
85 fn reset(&mut self) {
86 self.ready = false;
87 }
88
89 #[inline]
90 fn warmup_period(&self) -> usize {
91 1
92 }
93
94 #[inline]
95 fn is_ready(&self) -> bool {
96 self.ready
97 }
98
99 #[inline]
100 fn name(&self) -> &'static str {
101 "DemarkPivots"
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::traits::BatchExt;
109
110 #[test]
111 fn down_bar_uses_h_plus_2l_plus_c() {
112 let cd = Candle::new(110.0, 120.0, 80.0, 100.0, 1.0, 0).unwrap();
114 let lv = DemarkPivots::new().update(cd).unwrap();
115 assert!((lv.pp - 95.0).abs() < 1e-12);
116 assert!((lv.r1 - (190.0 - 80.0)).abs() < 1e-12);
117 assert!((lv.s1 - (190.0 - 120.0)).abs() < 1e-12);
118 }
119
120 #[test]
121 fn up_bar_uses_2h_plus_l_plus_c() {
122 let cd = Candle::new(100.0, 120.0, 80.0, 110.0, 1.0, 0).unwrap();
124 let lv = DemarkPivots::new().update(cd).unwrap();
125 assert!((lv.pp - 107.5).abs() < 1e-12);
126 assert!((lv.r1 - (215.0 - 80.0)).abs() < 1e-12);
127 assert!((lv.s1 - (215.0 - 120.0)).abs() < 1e-12);
128 }
129
130 #[test]
131 fn doji_uses_h_plus_l_plus_2c() {
132 let cd = Candle::new(100.0, 120.0, 80.0, 100.0, 1.0, 0).unwrap();
134 let lv = DemarkPivots::new().update(cd).unwrap();
135 assert!((lv.pp - 100.0).abs() < 1e-12);
136 }
137
138 #[test]
139 fn ordering_resistance_above_pivot_above_support() {
140 let cd = Candle::new(100.0, 120.0, 80.0, 110.0, 1.0, 0).unwrap();
141 let lv = DemarkPivots::new().update(cd).unwrap();
142 assert!(lv.r1 >= lv.pp);
143 assert!(lv.pp >= lv.s1);
144 }
145
146 #[test]
147 fn constant_series_collapses_levels() {
148 let cd = Candle::new(50.0, 50.0, 50.0, 50.0, 1.0, 0).unwrap();
149 let lv = DemarkPivots::new().update(cd).unwrap();
150 assert_eq!(lv.pp, 50.0);
151 assert_eq!(lv.r1, 50.0);
152 assert_eq!(lv.s1, 50.0);
153 }
154
155 #[test]
156 fn warmup_and_ready() {
157 let mut p = DemarkPivots::new();
158 assert!(!p.is_ready());
159 assert_eq!(p.warmup_period(), 1);
160 let cd = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
161 p.update(cd);
162 assert!(p.is_ready());
163 }
164
165 #[test]
166 fn reset_clears_state() {
167 let mut p = DemarkPivots::new();
168 let cd = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
169 p.update(cd);
170 p.reset();
171 assert!(!p.is_ready());
172 }
173
174 #[test]
175 fn batch_equals_streaming() {
176 let candles: Vec<Candle> = (0..40)
177 .map(|i| {
178 let base = f64::from(i);
179 Candle::new(base, base + 2.0, base - 0.5, base + 1.0, 1.0, i64::from(i)).unwrap()
180 })
181 .collect();
182 let mut a = DemarkPivots::new();
183 let mut b = DemarkPivots::new();
184 assert_eq!(
185 a.batch(&candles),
186 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
187 );
188 }
189
190 #[test]
191 fn accessors_and_metadata() {
192 let p = DemarkPivots::new();
193 assert_eq!(p.warmup_period(), 1);
194 assert_eq!(p.name(), "DemarkPivots");
195 }
196}