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 fn update(&mut self, candle: Candle) -> Option<DemarkPivotsOutput> {
62 let open = candle.open;
63 let high = candle.high;
64 let low = candle.low;
65 let close = candle.close;
66 let x = if close < open {
67 2.0 * high + low + close
68 } else if close > open {
69 high + 2.0 * low + close
70 } else {
71 high + low + 2.0 * close
72 };
73 let pp = x / 4.0;
74 let half = x / 2.0;
75 let out = DemarkPivotsOutput {
76 pp,
77 r1: half - low,
78 s1: half - high,
79 };
80 self.ready = true;
81 Some(out)
82 }
83
84 fn reset(&mut self) {
85 self.ready = false;
86 }
87
88 fn warmup_period(&self) -> usize {
89 1
90 }
91
92 fn is_ready(&self) -> bool {
93 self.ready
94 }
95
96 fn name(&self) -> &'static str {
97 "DemarkPivots"
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::traits::BatchExt;
105
106 #[test]
107 fn down_bar_uses_2h_plus_l_plus_c() {
108 let cd = Candle::new(110.0, 120.0, 80.0, 100.0, 1.0, 0).unwrap();
110 let lv = DemarkPivots::new().update(cd).unwrap();
111 assert!((lv.pp - 105.0).abs() < 1e-12);
112 assert!((lv.r1 - (210.0 - 80.0)).abs() < 1e-12);
113 assert!((lv.s1 - (210.0 - 120.0)).abs() < 1e-12);
114 }
115
116 #[test]
117 fn up_bar_uses_h_plus_2l_plus_c() {
118 let cd = Candle::new(100.0, 120.0, 80.0, 110.0, 1.0, 0).unwrap();
120 let lv = DemarkPivots::new().update(cd).unwrap();
121 assert!((lv.pp - 97.5).abs() < 1e-12);
122 assert!((lv.r1 - (195.0 - 80.0)).abs() < 1e-12);
123 assert!((lv.s1 - (195.0 - 120.0)).abs() < 1e-12);
124 }
125
126 #[test]
127 fn doji_uses_h_plus_l_plus_2c() {
128 let cd = Candle::new(100.0, 120.0, 80.0, 100.0, 1.0, 0).unwrap();
130 let lv = DemarkPivots::new().update(cd).unwrap();
131 assert!((lv.pp - 100.0).abs() < 1e-12);
132 }
133
134 #[test]
135 fn ordering_resistance_above_pivot_above_support() {
136 let cd = Candle::new(100.0, 120.0, 80.0, 110.0, 1.0, 0).unwrap();
137 let lv = DemarkPivots::new().update(cd).unwrap();
138 assert!(lv.r1 >= lv.pp);
139 assert!(lv.pp >= lv.s1);
140 }
141
142 #[test]
143 fn constant_series_collapses_levels() {
144 let cd = Candle::new(50.0, 50.0, 50.0, 50.0, 1.0, 0).unwrap();
145 let lv = DemarkPivots::new().update(cd).unwrap();
146 assert_eq!(lv.pp, 50.0);
147 assert_eq!(lv.r1, 50.0);
148 assert_eq!(lv.s1, 50.0);
149 }
150
151 #[test]
152 fn warmup_and_ready() {
153 let mut p = DemarkPivots::new();
154 assert!(!p.is_ready());
155 assert_eq!(p.warmup_period(), 1);
156 let cd = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
157 p.update(cd);
158 assert!(p.is_ready());
159 }
160
161 #[test]
162 fn reset_clears_state() {
163 let mut p = DemarkPivots::new();
164 let cd = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
165 p.update(cd);
166 p.reset();
167 assert!(!p.is_ready());
168 }
169
170 #[test]
171 fn batch_equals_streaming() {
172 let candles: Vec<Candle> = (0..40)
173 .map(|i| {
174 let base = f64::from(i);
175 Candle::new(base, base + 2.0, base - 0.5, base + 1.0, 1.0, i64::from(i)).unwrap()
176 })
177 .collect();
178 let mut a = DemarkPivots::new();
179 let mut b = DemarkPivots::new();
180 assert_eq!(
181 a.batch(&candles),
182 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
183 );
184 }
185
186 #[test]
187 fn accessors_and_metadata() {
188 let p = DemarkPivots::new();
189 assert_eq!(p.warmup_period(), 1);
190 assert_eq!(p.name(), "DemarkPivots");
191 }
192}