diff --git a/ta_lib/momentum/src/lib.rs b/ta_lib/momentum/src/lib.rs index 09eb9054..8524b55a 100644 --- a/ta_lib/momentum/src/lib.rs +++ b/ta_lib/momentum/src/lib.rs @@ -1 +1,2 @@ +pub mod macd; pub mod rsi; diff --git a/ta_lib/momentum/src/macd.rs b/ta_lib/momentum/src/macd.rs new file mode 100644 index 00000000..3c38fe3f --- /dev/null +++ b/ta_lib/momentum/src/macd.rs @@ -0,0 +1,128 @@ +use overlap::ema::ema; + +pub fn macd( + data: &[f64], + fast_period: usize, + slow_period: usize, + signal_period: usize, +) -> (Vec>, Vec>, Vec>) { + let ema_fast = ema(data, fast_period); + let ema_slow = ema(data, slow_period); + + let macd_line = ema_fast + .iter() + .zip(&ema_slow) + .map(|(fast, slow)| match (fast, slow) { + (Some(fast), Some(slow)) => Some(fast - slow), + _ => None, + }) + .collect::>(); + + let signal_line_values = ema( + &macd_line.iter().filter_map(|&x| x).collect::>(), + signal_period, + ); + + let mut signal_line = vec![None; macd_line.len() - signal_line_values.len()]; + signal_line.extend(signal_line_values); + + let histogram = macd_line + .iter() + .zip(&signal_line) + .map(|(macd, signal)| match (macd, signal) { + (Some(macd), Some(signal)) => Some(macd - signal), + _ => None, + }) + .collect::>(); + + (macd_line, signal_line, histogram) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_macd() { + let data = vec![2.0, 4.0, 6.0, 8.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0]; + let fast_period = 3; + let slow_period = 5; + let signal_period = 4; + let epsilon = 0.001; + + let (macd_line, signal_line, histogram) = + macd(&data, fast_period, slow_period, signal_period); + + let expected_macd_line = vec![ + None, + None, + None, + None, + Some(1.334877), + Some(1.035751), + Some(0.596751), + Some(0.184292), + Some(-0.150576), + Some(-0.403769), + ]; + let expected_signal_line = vec![ + None, + None, + None, + None, + None, + None, + None, + Some(0.654418), + Some(0.332421), + Some(0.037945), + ]; + let expected_histogram = vec![ + None, + None, + None, + None, + None, + None, + None, + Some(-0.470126), + Some(-0.482997), + Some(-0.441714), + ]; + + for i in 0..data.len() { + match (macd_line[i], expected_macd_line[i]) { + (Some(a), Some(b)) => { + assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) + } + (None, None) => {} + _ => panic!( + "at position {}: {:?} != {:?}", + i, macd_line[i], expected_macd_line[i] + ), + } + + match (signal_line[i], expected_signal_line[i]) { + (Some(a), Some(b)) => { + assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) + } + (None, None) => {} + _ => panic!( + "at position {}: {:?} != {:?}", + i, signal_line[i], expected_signal_line[i] + ), + } + + match (histogram[i], expected_histogram[i]) { + (Some(a), Some(b)) => { + assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) + } + (None, None) => {} + _ => panic!( + "at position {}: {:?} != {:?}", + i, histogram[i], expected_histogram[i] + ), + } + } + } +}