yata/methods/
adi.rs

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/// [Accumulation Distribution Index](https://en.wikipedia.org/wiki/Accumulation/distribution_index) of specified `length` for timeseries of [`OHLCV`]
9///
10/// [`CLV`] ranges from `-1.0` when the close is the low of the day, to `+1.0` when it's the high.
11/// For instance if the close is `3/4` the way up the range then [`CLV`] is `+0.5`.
12/// The accumulation/distribution index adds up volume multiplied by the [`CLV`] factor, i.e.
13///
14/// ADI = `ADI_prev` + [`CLV`] * [`volume`]
15///
16/// The name accumulation/distribution comes from the idea that during accumulation buyers are in control
17/// and the price will be bid up through the day, or will make a recovery if sold down, in either case
18/// more often finishing near the day's high than the low. The opposite applies during distribution.
19///
20/// The accumulation/distribution index is similar to on balance volume, but acc/dist is based on the close
21/// within the day's range, instead of the close-to-close up or down that the latter uses.
22///
23/// Can be used by a shortcut [`ADI`]
24///
25/// Used in indicators: [`Chaikin Money Flow`](crate::indicators::ChaikinMoneyFlow), [`Chaikin Oscillator`](crate::indicators::ChaikinOscillator)
26///
27/// # Parameters
28///
29/// Has a single parameter `length`: [`PeriodType`]
30///
31/// When `length == 0`, `ADI` becomes windowless. That means full `ADI` value accumulation over time.
32///
33/// When `length > 0`, `ADI` will be calculated over the last `length` values.
34///
35/// # Input type
36/// Input type is reference to [`OHLCV`]
37///
38/// # Output type
39/// Output type is [`ValueType`]
40///
41/// # Examples
42///
43/// ```
44/// use yata::prelude::*;
45/// use yata::methods::ADI;
46/// use yata::helpers::RandomCandles;
47///
48/// let mut candles = RandomCandles::default();
49/// let mut windowless = ADI::new(0, &candles.first()).unwrap();
50/// let mut windowed = ADI::new(3, &candles.first()).unwrap(); // <----- Window size 3
51///
52/// let candle = candles.next().unwrap();
53/// assert_ne!(windowless.next(&candle), windowed.next(&candle));
54///
55/// let candle = candles.next().unwrap();
56/// assert_ne!(windowless.next(&candle), windowed.next(&candle));
57///
58/// let candle = candles.next().unwrap();
59/// assert!((windowless.next(&candle)-windowed.next(&candle)).abs() < 1e-5); // Must be equal here
60/// ```
61///
62/// # Performance
63///
64/// O(1)
65///
66/// # See also
67///
68/// [ADI]
69///
70/// [`OHLCV`]: crate::core::OHLCV
71/// [`volume`]: crate::core::OHLCV::volume
72/// [`ValueType`]: crate::core::ValueType
73/// [`PeriodType`]: crate::core::PeriodType
74/// [`CLV`]: crate::core::OHLCV::clv
75#[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	/// Returns last calculated value
84	#[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}