Skip to content

Commit d785240

Browse files
committed
⚡ Optimize MACD signal path
1 parent 6828519 commit d785240

1 file changed

Lines changed: 27 additions & 12 deletions

File tree

core/src/indicators/macd.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,24 +75,39 @@ fn calc_macd_line(data: &[f64], fast_period: usize, slow_period: usize) -> Vec<O
7575
}
7676

7777
fn calc_macd_signal(macd_line: &[Option<f64>], signal_period: usize) -> Vec<Option<f64>> {
78-
let mut signal_line: Vec<Option<f64>> = vec![None; macd_line.len()];
79-
let null_count = macd_line.iter().take_while(|&&x| x.is_none()).count();
80-
let macd_values: Vec<f64> = macd_line
81-
.iter()
82-
.skip(null_count)
83-
.filter_map(|&x| x)
84-
.collect();
85-
let ema_values = ema(&macd_values, signal_period);
78+
let mut signal_line = vec![None; macd_line.len()];
79+
let Some(first_valid_idx) = macd_line.iter().position(|value| value.is_some()) else {
80+
return signal_line;
81+
};
82+
83+
let valid_len = macd_line.len() - first_valid_idx;
84+
if signal_period == 0 || valid_len < signal_period {
85+
return signal_line;
86+
}
8687

87-
for i in 0..ema_values.len() {
88-
if let Some(ema_value) = ema_values[i] {
89-
signal_line[i + null_count] = Some(ema_value);
90-
}
88+
let first_signal_idx = first_valid_idx + signal_period - 1;
89+
let mut signal = mean_macd_window(macd_line, first_valid_idx, signal_period);
90+
let alpha = 2.0 / (signal_period as f64 + 1.0);
91+
92+
signal_line[first_signal_idx] = Some(signal);
93+
94+
for (idx, value) in macd_line.iter().enumerate().skip(first_signal_idx + 1) {
95+
let macd = value.expect("macd_line becomes contiguous after the first valid value");
96+
signal = alpha * macd + (1.0 - alpha) * signal;
97+
signal_line[idx] = Some(signal);
9198
}
9299

93100
signal_line
94101
}
95102

103+
fn mean_macd_window(macd_line: &[Option<f64>], start_idx: usize, period: usize) -> f64 {
104+
macd_line[start_idx..start_idx + period]
105+
.iter()
106+
.map(|value| value.expect("initial signal window must be fully populated"))
107+
.sum::<f64>()
108+
/ period as f64
109+
}
110+
96111
#[cfg(test)]
97112
mod tests {
98113
use super::*;

0 commit comments

Comments
 (0)