wickra_core/indicators/
classic_pivots.rs1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct ClassicPivotsOutput {
9 pub pp: f64,
11 pub r1: f64,
13 pub r2: f64,
15 pub r3: f64,
17 pub s1: f64,
19 pub s2: f64,
21 pub s3: f64,
23}
24
25#[derive(Debug, Clone, Default)]
55pub struct ClassicPivots {
56 ready: bool,
57}
58
59impl ClassicPivots {
60 pub const fn new() -> Self {
63 Self { ready: false }
64 }
65}
66
67impl Indicator for ClassicPivots {
68 type Input = Candle;
69 type Output = ClassicPivotsOutput;
70
71 fn update(&mut self, candle: Candle) -> Option<ClassicPivotsOutput> {
72 let (h, l, c) = (candle.high, candle.low, candle.close);
73 let pp = (h + l + c) / 3.0;
74 let range = h - l;
75 let out = ClassicPivotsOutput {
76 pp,
77 r1: 2.0 * pp - l,
78 r2: pp + range,
79 r3: h + 2.0 * (pp - l),
80 s1: 2.0 * pp - h,
81 s2: pp - range,
82 s3: l - 2.0 * (h - pp),
83 };
84 self.ready = true;
85 Some(out)
86 }
87
88 fn reset(&mut self) {
89 self.ready = false;
90 }
91
92 fn warmup_period(&self) -> usize {
93 1
94 }
95
96 fn is_ready(&self) -> bool {
97 self.ready
98 }
99
100 fn name(&self) -> &'static str {
101 "ClassicPivots"
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::traits::BatchExt;
109
110 fn c(h: f64, l: f64, close: f64, ts: i64) -> Candle {
111 Candle::new(close, h, l, close, 1.0, ts).unwrap()
112 }
113
114 #[test]
115 fn formula_reference_values() {
116 let levels = ClassicPivots::new()
118 .update(c(110.0, 90.0, 105.0, 0))
119 .unwrap();
120 let pp = 305.0 / 3.0;
121 let range = 20.0;
122 assert!((levels.pp - pp).abs() < 1e-12);
123 assert!((levels.r1 - (2.0 * pp - 90.0)).abs() < 1e-12);
124 assert!((levels.s1 - (2.0 * pp - 110.0)).abs() < 1e-12);
125 assert!((levels.r2 - (pp + range)).abs() < 1e-12);
126 assert!((levels.s2 - (pp - range)).abs() < 1e-12);
127 assert!((levels.r3 - (110.0 + 2.0 * (pp - 90.0))).abs() < 1e-12);
128 assert!((levels.s3 - (90.0 - 2.0 * (110.0 - pp))).abs() < 1e-12);
129 }
130
131 #[test]
132 fn ordering_resistance_above_pivot_above_support() {
133 let levels = ClassicPivots::new()
135 .update(c(200.0, 100.0, 150.0, 0))
136 .unwrap();
137 assert!(levels.r3 >= levels.r2);
138 assert!(levels.r2 >= levels.r1);
139 assert!(levels.r1 >= levels.pp);
140 assert!(levels.pp >= levels.s1);
141 assert!(levels.s1 >= levels.s2);
142 assert!(levels.s2 >= levels.s3);
143 }
144
145 #[test]
146 fn constant_series_collapses_levels() {
147 let levels = ClassicPivots::new().update(c(50.0, 50.0, 50.0, 0)).unwrap();
149 assert_eq!(levels.pp, 50.0);
150 assert_eq!(levels.r1, 50.0);
151 assert_eq!(levels.s1, 50.0);
152 assert_eq!(levels.r2, 50.0);
153 assert_eq!(levels.s2, 50.0);
154 assert_eq!(levels.r3, 50.0);
155 assert_eq!(levels.s3, 50.0);
156 }
157
158 #[test]
159 fn ready_after_first_update_warmup_is_one() {
160 let mut pp = ClassicPivots::new();
161 assert!(!pp.is_ready());
162 assert_eq!(pp.warmup_period(), 1);
163 pp.update(c(11.0, 9.0, 10.0, 0));
164 assert!(pp.is_ready());
165 }
166
167 #[test]
168 fn reset_clears_state() {
169 let mut pp = ClassicPivots::new();
170 pp.update(c(11.0, 9.0, 10.0, 0));
171 assert!(pp.is_ready());
172 pp.reset();
173 assert!(!pp.is_ready());
174 }
175
176 #[test]
177 fn batch_equals_streaming() {
178 let candles: Vec<Candle> = (0_i32..40)
179 .map(|i| {
180 c(
181 f64::from(i) + 2.0,
182 f64::from(i),
183 f64::from(i) + 1.0,
184 i.into(),
185 )
186 })
187 .collect();
188 let mut a = ClassicPivots::new();
189 let mut b = ClassicPivots::new();
190 assert_eq!(
191 a.batch(&candles),
192 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
193 );
194 }
195
196 #[test]
197 fn accessors_and_metadata() {
198 let pp = ClassicPivots::new();
199 assert_eq!(pp.warmup_period(), 1);
200 assert_eq!(pp.name(), "ClassicPivots");
201 }
202}