1use crate::core::Method;
2use crate::core::{Error, PeriodType, ValueType, Window, OHLCV};
3use crate::helpers::Peekable;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone)]
76#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77pub struct ADI {
78 cmf_sum: ValueType,
79 window: Window<ValueType>,
80}
81
82impl ADI {
83 #[must_use]
85 #[deprecated(since = "0.6.1", note = "Use `Peekable::peek` instead")]
86 pub const fn get_value(&self) -> ValueType {
87 self.cmf_sum
88 }
89}
90
91impl Method for ADI {
92 type Params = PeriodType;
93 type Input = dyn OHLCV;
94 type Output = ValueType;
95
96 fn new(length: Self::Params, candle: &Self::Input) -> Result<Self, Error> {
97 let mut cmf_sum = 0.0;
98 let window = if length > 0 {
99 let clvv = candle.clv() * candle.volume();
100 cmf_sum = clvv * length as ValueType;
101 Window::new(length, clvv)
102 } else {
103 Window::empty()
104 };
105
106 Ok(Self { cmf_sum, window })
107 }
108
109 #[inline]
110 fn next(&mut self, candle: &Self::Input) -> Self::Output {
111 let clvv = candle.clv() * candle.volume();
112 self.cmf_sum += clvv;
113
114 if !self.window.is_empty() {
115 self.cmf_sum -= self.window.push(clvv);
116 }
117
118 self.peek()
119 }
120}
121
122impl Peekable<<Self as Method>::Output> for ADI {
123 fn peek(&self) -> <Self as Method>::Output {
124 self.cmf_sum
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::suboptimal_flops)]
130mod tests {
131 use super::ADI;
132 use crate::core::OHLCV;
133 use crate::core::{Candle, Method};
134 use crate::helpers::RandomCandles;
135 use crate::helpers::{assert_eq_float, assert_neq_float};
136 use crate::methods::tests::test_const;
137
138 #[test]
139 fn test_adi_const() {
140 let candle = Candle {
141 open: 121.0,
142 high: 133.0,
143 low: 49.0,
144 close: 70.0,
145 volume: 531.0,
146 };
147
148 for i in 1..30 {
149 let mut adi = ADI::new(i, &candle).unwrap();
150 let output = adi.next(&candle);
151
152 test_const(&mut adi, &candle, &output);
153 }
154 }
155
156 #[test]
157 #[should_panic(expected = "assertion")]
158 fn test_adi_windowless_const() {
159 let candle = Candle {
160 open: 121.0,
161 high: 133.0,
162 low: 49.0,
163 close: 70.0,
164 volume: 531.0,
165 };
166
167 let mut adi = ADI::new(0, &candle).unwrap();
168 let output = adi.next(&candle);
169
170 test_const(&mut adi, &candle, &output);
171 }
172
173 #[test]
174 fn test_adi() {
175 let mut candles = RandomCandles::default();
176 let first_candle = candles.first();
177 let mut adi = ADI::new(0, &first_candle).unwrap();
178
179 candles.take(100).fold(0., |s, candle| {
180 assert_eq_float(adi.next(&candle), s + candle.clv() * candle.volume());
181 s + candle.clv() * candle.volume()
182 });
183 }
184
185 #[test]
186 fn test_adi_windowed() {
187 let mut candles = RandomCandles::default();
188 let first = candles.first();
189 let mut adi = ADI::new(0, &first).unwrap();
190 let mut adiw = [
191 ADI::new(1, &first).unwrap(),
192 ADI::new(2, &first).unwrap(),
193 ADI::new(3, &first).unwrap(),
194 ADI::new(4, &first).unwrap(),
195 ADI::new(5, &first).unwrap(),
196 ];
197
198 candles
199 .take(adiw.len())
200 .enumerate()
201 .for_each(|(i, candle)| {
202 let v1 = adi.next(&candle);
203
204 adiw.iter_mut().enumerate().for_each(|(j, adiw)| {
205 let v2 = adiw.next(&candle);
206 if i == j {
207 assert_eq_float(v1, v2);
208 } else {
209 assert_neq_float(v1, v2);
210 }
211 });
212 });
213 }
214}