finance_query/indicators/
donchian_channels.rs1use super::{IndicatorError, Result};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct DonchianChannelsResult {
9 pub upper: Vec<Option<f64>>,
11 pub middle: Vec<Option<f64>>,
13 pub lower: Vec<Option<f64>>,
15}
16
17pub fn donchian_channels(
39 highs: &[f64],
40 lows: &[f64],
41 period: usize,
42) -> Result<DonchianChannelsResult> {
43 if period == 0 {
44 return Err(IndicatorError::InvalidPeriod(
45 "Period must be greater than 0".to_string(),
46 ));
47 }
48 let len = highs.len();
49 if lows.len() != len {
50 return Err(IndicatorError::InvalidPeriod(
51 "Data lengths must match".to_string(),
52 ));
53 }
54 if len < period {
55 return Err(IndicatorError::InsufficientData {
56 need: period,
57 got: len,
58 });
59 }
60
61 let mut upper = vec![None; len];
62 let mut middle = vec![None; len];
63 let mut lower = vec![None; len];
64
65 for i in (period - 1)..len {
66 let start_idx = i + 1 - period;
67 let slice_highs = &highs[start_idx..=i];
68 let slice_lows = &lows[start_idx..=i];
69
70 let highest = slice_highs.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
71 let lowest = slice_lows.iter().fold(f64::INFINITY, |a, &b| a.min(b));
72 let mid = (highest + lowest) / 2.0;
73
74 upper[i] = Some(highest);
75 lower[i] = Some(lowest);
76 middle[i] = Some(mid);
77 }
78
79 Ok(DonchianChannelsResult {
80 upper,
81 middle,
82 lower,
83 })
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_donchian_channels() {
92 let highs = vec![10.0, 11.0, 12.0, 11.0, 10.0];
93 let lows = vec![8.0, 9.0, 10.0, 9.0, 8.0];
94 let result = donchian_channels(&highs, &lows, 3).unwrap();
95
96 assert_eq!(result.upper.len(), 5);
97 assert!(result.upper[0].is_none());
98 assert!(result.upper[1].is_none());
99 assert!(result.upper[2].is_some());
100 }
101}