use std::vec;
use crate::indicator_error::IndicatorError;
use tracing::instrument;
#[derive(Debug, Clone)]
pub struct WFData {
pub fractal_high: bool,
pub fractal_low: bool,
}
pub struct SettingWf {
pub factor: usize,
}
#[instrument(level = "trace", skip_all, ret)]
pub fn calculate_wf(
candle_high: &[f64],
candle_low: &[f64],
setting: &SettingWf,
) -> Result<Vec<WFData>, IndicatorError> {
if candle_high.is_empty() || candle_low.is_empty() {
return Err(IndicatorError::EmptyData);
}
if candle_high.len() != candle_low.len() {
return Err(IndicatorError::DifferentDataLength);
}
if candle_high.len() < ((setting.factor * 2) + 1) {
return Err(IndicatorError::ImproperDataLength);
}
let mut wf: Vec<WFData> = vec![
WFData {
fractal_high: false,
fractal_low: false
};
candle_high.len()
];
for i in setting.factor..(candle_high.len() - setting.factor) {
let center_high = candle_high[i];
let center_low = candle_low[i];
let mut is_fractal_high = true;
let mut is_fractal_low = true;
for j in 1..=setting.factor {
if center_high <= candle_high[i - j] || center_high <= candle_high[i + j] {
is_fractal_high = false;
}
if center_low >= candle_low[i - j] || center_low >= candle_low[i + j] {
is_fractal_low = false;
}
if !is_fractal_high && !is_fractal_low {
break;
}
}
wf[i] = WFData {
fractal_high: is_fractal_high,
fractal_low: is_fractal_low,
};
}
Ok(wf)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_wf_high_fractal() {
let high = vec![10.0, 15.0, 20.0, 15.0, 10.0];
let low = vec![5.0, 8.0, 10.0, 8.0, 5.0];
let setting = SettingWf { factor: 2 };
let result = calculate_wf(&high, &low, &setting).unwrap();
assert_eq!(result.len(), 5);
assert!(result[2].fractal_high);
assert!(!result[2].fractal_low);
}
#[test]
fn test_calculate_wf_low_fractal() {
let high = vec![20.0, 15.0, 10.0, 15.0, 20.0];
let low = vec![10.0, 8.0, 5.0, 8.0, 10.0];
let setting = SettingWf { factor: 2 };
let result = calculate_wf(&high, &low, &setting).unwrap();
assert_eq!(result.len(), 5);
assert!(result[2].fractal_low);
assert!(!result[2].fractal_high);
}
#[test]
fn test_calculate_wf_both_fractals() {
let high = vec![5.0, 8.0, 15.0, 8.0, 5.0];
let low = vec![3.0, 4.0, 2.0, 4.0, 3.0];
let setting = SettingWf { factor: 2 };
let result = calculate_wf(&high, &low, &setting).unwrap();
assert!(result[2].fractal_high);
assert!(result[2].fractal_low);
}
#[test]
fn test_calculate_wf_no_fractal() {
let high = vec![10.0, 12.0, 11.0, 13.0, 10.0];
let low = vec![8.0, 9.0, 8.5, 10.0, 7.0];
let setting = SettingWf { factor: 2 };
let result = calculate_wf(&high, &low, &setting).unwrap();
assert!(!result[2].fractal_high);
}
#[test]
fn test_calculate_wf_boundary_elements() {
let high = vec![1.0, 2.0, 3.0, 2.0, 1.0];
let low = vec![1.0, 1.0, 1.0, 1.0, 1.0];
let setting = SettingWf { factor: 2 };
let result = calculate_wf(&high, &low, &setting).unwrap();
assert!(!result[0].fractal_high);
assert!(!result[1].fractal_high);
assert!(!result[3].fractal_high);
assert!(!result[4].fractal_high);
}
#[test]
fn test_calculate_wf_empty() {
assert!(matches!(
calculate_wf(&[], &[1.0], &SettingWf { factor: 2 }).unwrap_err(),
IndicatorError::EmptyData
));
}
#[test]
fn test_calculate_wf_length_mismatch() {
assert!(matches!(
calculate_wf(&[1.0, 2.0], &[1.0], &SettingWf { factor: 2 }).unwrap_err(),
IndicatorError::DifferentDataLength
));
}
#[test]
fn test_calculate_wf_insufficient_data() {
assert!(matches!(
calculate_wf(&[1.0, 2.0, 3.0], &[1.0, 2.0, 3.0], &SettingWf { factor: 2 }).unwrap_err(),
IndicatorError::ImproperDataLength
));
}
#[test]
fn test_calculate_wf_minimum_valid() {
let high = vec![1.0, 3.0, 2.0];
let low = vec![1.0, 1.0, 1.0];
let setting = SettingWf { factor: 1 };
let result = calculate_wf(&high, &low, &setting).unwrap();
assert_eq!(result.len(), 3);
assert!(result[1].fractal_high);
}
}