wickra_core/indicators/
wad.rs1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
44pub struct Wad {
45 prev_close: Option<f64>,
46 line: f64,
47 last: Option<f64>,
48}
49
50impl Wad {
51 #[must_use]
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub const fn value(&self) -> Option<f64> {
59 self.last
60 }
61}
62
63impl Indicator for Wad {
64 type Input = Candle;
65 type Output = f64;
66
67 #[inline]
68 fn update(&mut self, candle: Candle) -> Option<f64> {
69 let Some(prev_close) = self.prev_close else {
70 self.prev_close = Some(candle.close);
71 return None;
72 };
73 let ad = if candle.close > prev_close {
74 candle.close - candle.low.min(prev_close)
75 } else if candle.close < prev_close {
76 candle.close - candle.high.max(prev_close)
77 } else {
78 0.0
79 };
80 self.line += ad;
81 self.prev_close = Some(candle.close);
82 self.last = Some(self.line);
83 Some(self.line)
84 }
85
86 fn reset(&mut self) {
87 self.prev_close = None;
88 self.line = 0.0;
89 self.last = None;
90 }
91
92 #[inline]
93 fn warmup_period(&self) -> usize {
94 2
97 }
98
99 #[inline]
100 fn is_ready(&self) -> bool {
101 self.last.is_some()
102 }
103
104 #[inline]
105 fn name(&self) -> &'static str {
106 "Wad"
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::traits::BatchExt;
114 use approx::assert_relative_eq;
115
116 fn candle(high: f64, low: f64, close: f64) -> Candle {
117 Candle::new_unchecked(low, high, low, close, 1_000.0, 0)
118 }
119
120 #[test]
121 fn accessors_and_metadata() {
122 let wad = Wad::new();
123 assert_eq!(wad.warmup_period(), 2);
124 assert_eq!(wad.name(), "Wad");
125 assert!(!wad.is_ready());
126 assert_eq!(wad.value(), None);
127 }
128
129 #[test]
130 fn first_bar_seeds_without_output() {
131 let mut wad = Wad::new();
132 assert_eq!(wad.update(candle(101.0, 99.0, 100.0)), None);
133 assert!(wad.update(candle(102.0, 100.0, 101.0)).is_some());
134 }
135
136 #[test]
137 fn up_close_accumulates() {
138 let mut wad = Wad::new();
141 wad.update(candle(101.0, 99.0, 100.0));
142 let v = wad.update(candle(102.0, 100.0, 101.0)).unwrap();
143 assert_relative_eq!(v, 1.0, epsilon = 1e-9);
144 }
145
146 #[test]
147 fn down_close_distributes() {
148 let mut wad = Wad::new();
151 wad.update(candle(102.0, 100.0, 100.0));
152 let v = wad.update(candle(101.0, 98.0, 99.0)).unwrap();
153 assert_relative_eq!(v, -2.0, epsilon = 1e-9);
154 }
155
156 #[test]
157 fn unchanged_close_adds_nothing() {
158 let mut wad = Wad::new();
159 wad.update(candle(101.0, 99.0, 100.0));
160 let v = wad.update(candle(105.0, 95.0, 100.0)).unwrap();
161 assert_relative_eq!(v, 0.0, epsilon = 1e-12);
162 }
163
164 #[test]
165 fn pure_uptrend_is_monotone() {
166 let mut wad = Wad::new();
167 let candles: Vec<Candle> = (0..30)
168 .map(|i| {
169 let base = 100.0 + f64::from(i);
170 candle(base + 1.0, base - 1.0, base)
171 })
172 .collect();
173 let mut prev = f64::NEG_INFINITY;
174 for v in wad.batch(&candles).into_iter().flatten() {
175 assert!(v >= prev, "WAD must rise in an uptrend");
176 prev = v;
177 }
178 }
179
180 #[test]
181 fn reset_clears_state() {
182 let mut wad = Wad::new();
183 let candles: Vec<Candle> = (0..10)
184 .map(|i| {
185 let base = 100.0 + f64::from(i);
186 candle(base + 1.0, base - 1.0, base)
187 })
188 .collect();
189 wad.batch(&candles);
190 assert!(wad.is_ready());
191 wad.reset();
192 assert!(!wad.is_ready());
193 assert_eq!(wad.value(), None);
194 assert_eq!(wad.update(candle(101.0, 99.0, 100.0)), None);
195 }
196
197 #[test]
198 fn batch_equals_streaming() {
199 let candles: Vec<Candle> = (0..80)
200 .map(|i| {
201 let base = 100.0 + (f64::from(i) * 0.3).sin() * 8.0;
202 candle(base + 2.0, base - 2.0, base + 0.5)
203 })
204 .collect();
205 let batch = Wad::new().batch(&candles);
206 let mut b = Wad::new();
207 let streamed: Vec<_> = candles.iter().map(|c| b.update(*c)).collect();
208 assert_eq!(batch, streamed);
209 }
210}