stat/analysis/
trend.rs

1//! Trend contains technical analysis indicators that try to predict the
2//! direction in which values are moving towards.
3use analysis::{AnalysisError, Result};
4
5/// Exponential moving average (EMA) is a filter that applies weighting factors
6/// which decrease exponentially. The weighting for each older datum decreases
7/// exponentially, never reaching zero.
8///
9/// The number of periods depends on the analytical objectives. Typical number
10/// of periods for short, medium and long term trends are 5-20, 20-60 and
11/// 100-200 respectively.
12///
13/// Function calculates the weighting factor from the slice length and
14/// calculates the exponential moving average using that factor. If EMA is
15/// calculated for the first time previous argument needs to be 0. When previous
16/// is 0 function returns simple moving average of the datum points.
17///
18/// # Arguments
19///
20/// * `slice` - array of values
21/// * `old` - previous EMA if any
22///
23/// # Example
24///
25/// ```
26/// use stat::analysis::trend;
27///
28/// let array = [3.5, 3.4, 3.3, 3.6, 3.7];
29/// let value = trend::exponential_moving_average(&array, Some(3.4));
30/// assert_eq!(value.ok(), Some(3.5));
31/// ```
32pub fn exponential_moving_average(slice: &[f64], old: Option<f64>) -> Result<f64> {
33	let length = slice.len();
34	if length == 0 {
35		return Err(AnalysisError::SliceIsEmpty);
36	}
37	Ok(match old {
38		Some(ema) => (slice[length-1] - ema) * 2. / (1. + length as f64) + ema,
39		None => try!(simple_moving_average(slice)),
40	})
41}
42
43/// Simple moving average (SMA) is the unweighted mean of the datum points.
44///
45/// The number of periods depends on the analytical objectives. Typical number
46/// of periods for short, medium and long term trends are 5-20, 20-60 and
47/// 100-200 respectively.
48///
49/// # Arguments
50///
51/// * `slice` - array of values
52///
53/// # Example
54///
55/// ```
56/// use stat::analysis::trend;
57///
58/// let array = [3.5, 3.4, 3.3, 3.6, 3.7];
59/// let value = trend::simple_moving_average(&array);
60/// assert_eq!(value.ok(), Some(3.5));
61/// ```
62pub fn simple_moving_average(slice: &[f64]) -> Result<f64> {
63	let length = slice.len();
64	if length == 0 {
65		return Err(AnalysisError::SliceIsEmpty);
66	}
67	Ok(slice.iter().fold(0., |sum, x| sum + x) / length as f64)
68}
69
70#[cfg(test)]
71mod tests {
72	extern crate math;
73	use analysis::{AnalysisError, Result};
74	use self::math::round::half_to_even;
75	use std::error::Error;
76
77	#[test]
78	fn exponential_moving_average() {
79		let values: [f64; 30] = [
80			22.27, 22.19, 22.08, 22.17, 22.18, 22.13, 22.23, 22.43, 22.24,
81			22.29, 22.15, 22.39, 22.38, 22.61, 23.36, 24.05, 23.75, 23.83,
82			23.95, 23.63, 23.82, 23.87, 23.65, 23.19, 23.10, 23.33, 22.68,
83			23.10, 22.40, 22.17,
84		];
85		let results: [f64; 21] = [
86			22.221000, 22.208091, 22.241165, 22.266408, 22.328879, 22.516356,
87			22.795200, 22.968800, 23.125382, 23.275312, 23.339801, 23.427110,
88			23.507635, 23.533520, 23.471062, 23.403596, 23.390215, 23.261085,
89			23.231797, 23.080561, 22.915004,
90		];
91		let tests: [(&[f64], Option<f64>, Result<f64>); 22] = [
92			(&values[0..0], None, Err(AnalysisError::SliceIsEmpty)),
93			(&values[0..10], None, Ok(results[0])),
94			(&values[1..11], Some(results[0]), Ok(results[1])),
95			(&values[2..12], Some(results[1]), Ok(results[2])),
96			(&values[3..13], Some(results[2]), Ok(results[3])),
97			(&values[4..14], Some(results[3]), Ok(results[4])),
98			(&values[5..15], Some(results[4]), Ok(results[5])),
99			(&values[6..16], Some(results[5]), Ok(results[6])),
100			(&values[7..17], Some(results[6]), Ok(results[7])),
101			(&values[8..18], Some(results[7]), Ok(results[8])),
102			(&values[9..19], Some(results[8]), Ok(results[9])),
103			(&values[10..20], Some(results[9]), Ok(results[10])),
104			(&values[11..21], Some(results[10]), Ok(results[11])),
105			(&values[12..22], Some(results[11]), Ok(results[12])),
106			(&values[13..23], Some(results[12]), Ok(results[13])),
107			(&values[14..24], Some(results[13]), Ok(results[14])),
108			(&values[15..25], Some(results[14]), Ok(results[15])),
109			(&values[16..26], Some(results[15]), Ok(results[16])),
110			(&values[17..27], Some(results[16]), Ok(results[17])),
111			(&values[18..28], Some(results[17]), Ok(results[18])),
112			(&values[19..29], Some(results[18]), Ok(results[19])),
113			(&values[20..30], Some(results[19]), Ok(results[20])),
114		];
115
116		for test in &tests {
117			let result = super::exponential_moving_average(test.0, test.1);
118			match (result, test.2.as_ref()) {
119				(Ok(val), Ok(exp))
120					=> assert_eq!(half_to_even(val, 6), *exp),
121				(Err(err), Err(exp))
122					=> assert_eq!(err.description(), exp.description()),
123				_ => panic!("return type mismatch"),
124			}
125		}
126	}
127
128	#[test]
129	fn simple_moving_average() {
130		let values: [f64; 30] = [
131			22.27, 22.19, 22.08, 22.17, 22.18, 22.13, 22.23, 22.43, 22.24,
132			22.29, 22.15, 22.39, 22.38, 22.61, 23.36, 24.05, 23.75, 23.83,
133			23.95, 23.63, 23.82, 23.87, 23.65, 23.19, 23.10, 23.33, 22.68,
134			23.10, 22.40, 22.17,
135		];
136		let results: [f64; 21] = [
137			22.221, 22.209, 22.229, 22.259, 22.303, 22.421, 22.613, 22.765,
138			22.905, 23.076, 23.210, 23.377, 23.525, 23.652, 23.710, 23.684,
139			23.612, 23.505, 23.432, 23.277, 23.131,
140		];
141		let tests: [(&[f64], Result<f64>); 22] = [
142			(&values[0..0], Err(AnalysisError::SliceIsEmpty)),
143			(&values[0..10], Ok(results[0])),
144			(&values[1..11], Ok(results[1])),
145			(&values[2..12], Ok(results[2])),
146			(&values[3..13], Ok(results[3])),
147			(&values[4..14], Ok(results[4])),
148			(&values[5..15], Ok(results[5])),
149			(&values[6..16], Ok(results[6])),
150			(&values[7..17], Ok(results[7])),
151			(&values[8..18], Ok(results[8])),
152			(&values[9..19], Ok(results[9])),
153			(&values[10..20], Ok(results[10])),
154			(&values[11..21], Ok(results[11])),
155			(&values[12..22], Ok(results[12])),
156			(&values[13..23], Ok(results[13])),
157			(&values[14..24], Ok(results[14])),
158			(&values[15..25], Ok(results[15])),
159			(&values[16..26], Ok(results[16])),
160			(&values[17..27], Ok(results[17])),
161			(&values[18..28], Ok(results[18])),
162			(&values[19..29], Ok(results[19])),
163			(&values[20..30], Ok(results[20])),
164		];
165
166		for test in &tests {
167			let result = super::simple_moving_average(test.0);
168			match (result, test.1.as_ref()) {
169				(Ok(val), Ok(exp))
170					=> assert_eq!(half_to_even(val, 3), *exp),
171				(Err(err), Err(exp))
172					=> assert_eq!(err.description(), exp.description()),
173				_ => panic!("return type mismatch"),
174			}
175		}
176	}
177}