1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
43pub struct AdOscillator {
44 prev_close: Option<f64>,
45 total: f64,
46 has_emitted: bool,
47}
48
49impl AdOscillator {
50 pub const fn new() -> Self {
52 Self {
53 prev_close: None,
54 total: 0.0,
55 has_emitted: false,
56 }
57 }
58
59 pub const fn value(&self) -> Option<f64> {
61 if self.has_emitted {
62 Some(self.total)
63 } else {
64 None
65 }
66 }
67}
68
69impl Indicator for AdOscillator {
70 type Input = Candle;
71 type Output = f64;
72
73 fn update(&mut self, candle: Candle) -> Option<f64> {
74 let Some(prev) = self.prev_close else {
75 self.prev_close = Some(candle.close);
77 return None;
78 };
79 let delta = if candle.close > prev {
80 let tr_l = prev.min(candle.low);
82 candle.close - tr_l
83 } else if candle.close < prev {
84 let tr_h = prev.max(candle.high);
86 candle.close - tr_h
87 } else {
88 0.0
90 };
91 self.total += delta;
92 self.prev_close = Some(candle.close);
93 self.has_emitted = true;
94 Some(self.total)
95 }
96
97 fn reset(&mut self) {
98 self.prev_close = None;
99 self.total = 0.0;
100 self.has_emitted = false;
101 }
102
103 fn warmup_period(&self) -> usize {
104 2
106 }
107
108 fn is_ready(&self) -> bool {
109 self.has_emitted
110 }
111
112 fn name(&self) -> &'static str {
113 "WilliamsAD"
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::traits::BatchExt;
121 use approx::assert_relative_eq;
122
123 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
124 Candle::new(open, high, low, close, 100.0, ts).unwrap()
125 }
126
127 #[test]
128 fn accessors_and_metadata() {
129 let ad = AdOscillator::new();
130 assert_eq!(ad.name(), "WilliamsAD");
131 assert_eq!(ad.warmup_period(), 2);
132 assert_eq!(ad.value(), None);
133 }
134
135 #[test]
136 fn value_returns_total_after_first_emission() {
137 let mut ad = AdOscillator::new();
138 ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
139 let v = ad.update(c(11.0, 13.0, 8.0, 12.0, 1)).unwrap();
140 assert_relative_eq!(ad.value().unwrap(), v, epsilon = 1e-12);
141 }
142
143 #[test]
144 fn first_bar_only_seeds() {
145 let mut ad = AdOscillator::new();
146 assert_eq!(ad.update(c(10.0, 11.0, 9.0, 10.0, 0)), None);
147 assert!(!ad.is_ready());
148 }
149
150 #[test]
151 fn accumulation_adds_distance_from_true_low() {
152 let mut ad = AdOscillator::new();
155 ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
156 let v = ad.update(c(11.0, 13.0, 8.0, 12.0, 1)).unwrap();
157 assert_relative_eq!(v, 4.0, epsilon = 1e-12);
158 }
159
160 #[test]
161 fn distribution_adds_distance_from_true_high() {
162 let mut ad = AdOscillator::new();
165 ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
166 let v = ad.update(c(10.0, 11.0, 7.0, 7.0, 1)).unwrap();
167 assert_relative_eq!(v, -4.0, epsilon = 1e-12);
168 }
169
170 #[test]
171 fn unchanged_close_keeps_total() {
172 let mut ad = AdOscillator::new();
174 ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
175 let v = ad.update(c(10.0, 12.0, 8.0, 10.0, 1)).unwrap();
176 assert_relative_eq!(v, 0.0, epsilon = 1e-12);
177 }
178
179 #[test]
180 fn constant_series_yields_zero() {
181 let candles: Vec<Candle> = (0..40).map(|i| c(10.0, 11.0, 9.0, 10.0, i)).collect();
183 let mut ad = AdOscillator::new();
184 for v in ad.batch(&candles).into_iter().flatten() {
185 assert_relative_eq!(v, 0.0, epsilon = 1e-12);
186 }
187 }
188
189 #[test]
190 fn batch_equals_streaming() {
191 let candles: Vec<Candle> = (0..80i64)
192 .map(|i| {
193 let f = i as f64;
194 let mid = 100.0 + (f * 0.3).sin() * 5.0;
195 c(mid, mid + 2.0, mid - 2.0, mid + 0.5, i)
196 })
197 .collect();
198 let mut a = AdOscillator::new();
199 let mut b = AdOscillator::new();
200 assert_eq!(
201 a.batch(&candles),
202 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
203 );
204 }
205
206 #[test]
207 fn reset_clears_state() {
208 let mut ad = AdOscillator::new();
209 ad.batch(&[
210 c(10.0, 11.0, 9.0, 10.0, 0),
211 c(10.0, 12.0, 9.0, 11.0, 1),
212 c(11.0, 13.0, 10.0, 12.0, 2),
213 ]);
214 assert!(ad.is_ready());
215 ad.reset();
216 assert!(!ad.is_ready());
217 assert_eq!(ad.value(), None);
218 assert_eq!(ad.update(c(10.0, 11.0, 9.0, 10.0, 3)), None);
219 }
220}