use crate::statistics::Metrics;
use crate::utils::ACF;
use super::sma::sma;
pub fn smooth(data: &[f64], resolution: usize) -> Vec<f64> {
if data.is_empty() {
return Vec::new();
}
let mut data = data.to_vec();
if data.last().map_or(false, |&x| x.is_nan()) {
data.pop();
}
if data.is_empty() {
return Vec::new();
}
if data.len() <= resolution * 2 {
return data;
}
let target_min_points = resolution.max(10);
if data.len() > target_min_points * 10 {
let window = (data.len() / (target_min_points * 2)).max(1);
let slide = window; data = sma(&data, window, slide);
}
if data.len() <= 2 {
return data;
}
let metrics = Metrics::new(data.clone());
let original_kurt = metrics.kurtosis();
let original_roughness = metrics.roughness();
let is_zigzag_pattern = detect_zigzag_pattern(&data, original_roughness);
let kurtosis_threshold = if is_zigzag_pattern {
original_kurt * 0.6
} else {
original_kurt * 0.9
};
if data.len() < 20 {
let candidates = [2, 3, 5, 7];
let mut best_window = 1;
let mut min_roughness = original_roughness;
for &w in &candidates {
if w >= data.len() {
continue;
}
let smoothed = sma(&data, w, 1);
if smoothed.is_empty() {
continue;
}
let metrics = Metrics::new(smoothed);
let roughness = metrics.roughness();
if metrics.kurtosis() >= kurtosis_threshold && roughness < min_roughness {
min_roughness = roughness;
best_window = w;
}
}
if best_window > 1 {
return sma(&data, best_window, 1);
}
if is_zigzag_pattern {
return sma(&data, 2, 1);
}
return data;
}
let max_lag = (data.len() / 10).max(1).min(50); let mut acf = ACF::new(data.clone(), max_lag);
let peaks = acf.find_peaks();
if peaks.is_empty() {
let window_size = (data.len() / 10).max(2).min(data.len() / 2);
let smoothed = sma(&data, window_size, 1);
let metrics = Metrics::new(smoothed.clone());
if metrics.kurtosis() >= kurtosis_threshold && metrics.roughness() < original_roughness {
return smoothed;
}
if is_zigzag_pattern {
let smoothed = sma(&data, 2, 1);
let metrics = Metrics::new(smoothed.clone());
if metrics.roughness() < original_roughness {
return smoothed;
}
}
return data;
}
let mut min_obj = original_roughness;
let mut window_size = 1; let mut lb = 1;
let mut largest_feasible = usize::MAX;
let mut tail = (data.len() / 10).min(peaks[peaks.len() - 1]);
for (i, &w) in peaks.iter().rev().enumerate() {
if w > data.len() / 2 {
continue;
}
if w < lb || w == 1 {
break;
} else if (1.0 - acf.correlations[w]).sqrt() * window_size as f64 >
(1.0 - acf.correlations[window_size]).sqrt() * w as f64 {
continue;
}
let smoothed = sma(&data, w, 1);
if smoothed.len() < 2 { continue;
}
let metrics = Metrics::new(smoothed);
let roughness = metrics.roughness();
if metrics.kurtosis() >= kurtosis_threshold {
if roughness < min_obj {
min_obj = roughness;
window_size = w;
}
let acf_ratio = ((acf.max_acf - 1.0) / (acf.correlations[w] - 1.0)).sqrt();
if acf_ratio.is_finite() && !acf_ratio.is_nan() {
lb = ((w as f64 * acf_ratio).round() as usize).max(lb);
}
if largest_feasible == usize::MAX {
largest_feasible = i;
}
}
}
if window_size == 1 && is_zigzag_pattern {
window_size = 2;
}
if window_size > 1 {
if largest_feasible != usize::MAX && largest_feasible < peaks.len().saturating_sub(2) {
tail = peaks[largest_feasible + 1];
}
lb = lb.max(if largest_feasible != usize::MAX { peaks[largest_feasible] + 1 } else { 1 });
if lb < tail && lb < data.len() / 2 {
window_size = binary_search(lb, tail, &data, min_obj, kurtosis_threshold, window_size, is_zigzag_pattern);
}
} else {
let default_window = (data.len() / 20).max(2).min(data.len() / 3);
let smoothed = sma(&data, default_window, 1);
if !smoothed.is_empty() {
let metrics = Metrics::new(smoothed.clone());
if metrics.kurtosis() >= kurtosis_threshold && metrics.roughness() < original_roughness {
return smoothed;
}
}
if is_zigzag_pattern {
let smoothed = sma(&data, 2, 1);
if !smoothed.is_empty() {
let metrics = Metrics::new(smoothed.clone());
if metrics.roughness() < original_roughness {
return smoothed;
}
}
}
return data;
}
window_size = window_size.max(2).min(data.len() / 3);
let smoothed = sma(&data, window_size, 1);
let original_roughness = Metrics::new(data.clone()).roughness();
let smoothed_roughness = Metrics::new(smoothed.clone()).roughness();
if smoothed.len() < 2 || smoothed_roughness >= original_roughness * 1.1 {
return data;
}
smoothed
}
fn detect_zigzag_pattern(data: &[f64], roughness: f64) -> bool {
if data.len() < 4 {
return false;
}
let max_val = data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_val = data.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let data_range = max_val - min_val;
if data_range < 1e-10 {
return false;
}
let mut alternating_count = 0;
for i in 1..data.len()-1 {
let prev_diff = data[i] - data[i-1];
let next_diff = data[i+1] - data[i];
if prev_diff * next_diff < 0.0 { alternating_count += 1;
}
}
let alternating_ratio = alternating_count as f64 / (data.len() - 2) as f64;
roughness > data_range * 0.5 && alternating_ratio > 0.7
}
fn binary_search(mut head: usize, mut tail: usize, data: &[f64], mut min_obj: f64, kurtosis_threshold: f64, mut window_size: usize, is_zigzag: bool) -> usize {
if data.is_empty() || head > data.len() || tail > data.len() {
return window_size;
}
head = head.min(data.len() / 2); tail = tail.min(data.len() / 2);
if head > tail {
return window_size;
}
if is_zigzag && head <= 2 && 2 <= tail {
let smoothed = sma(data, 2, 1);
if !smoothed.is_empty() {
let metrics = Metrics::new(smoothed);
if metrics.roughness() < min_obj {
return 2;
}
}
}
while head <= tail {
let w = (head + tail) / 2;
let smoothed = sma(data, w, 1);
if smoothed.len() < 2 {
break;
}
let metrics = Metrics::new(smoothed);
if metrics.kurtosis() >= kurtosis_threshold {
let roughness = metrics.roughness();
if roughness < min_obj {
window_size = w;
min_obj = roughness;
}
head = w + 1;
} else {
tail = w.saturating_sub(1);
}
}
window_size
}