Skip to main content

finance_query/indicators/
donchian_channels.rs

1//! Donchian Channels indicator.
2
3use super::{IndicatorError, Result};
4use serde::{Deserialize, Serialize};
5
6/// Result of Donchian Channels calculation
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct DonchianChannelsResult {
9    /// Upper channel
10    pub upper: Vec<Option<f64>>,
11    /// Middle channel
12    pub middle: Vec<Option<f64>>,
13    /// Lower channel
14    pub lower: Vec<Option<f64>>,
15}
16
17/// Calculate Donchian Channels.
18///
19/// Upper Channel = Highest high over period
20/// Lower Channel = Lowest low over period
21/// Middle Channel = (Upper + Lower) / 2
22///
23/// # Arguments
24///
25/// * `highs` - High prices
26/// * `lows` - Low prices
27/// * `period` - Number of periods
28///
29/// # Example
30///
31/// ```
32/// use finance_query::indicators::donchian_channels;
33///
34/// let highs = vec![10.0, 11.0, 12.0, 11.0, 10.0];
35/// let lows = vec![8.0, 9.0, 10.0, 9.0, 8.0];
36/// let result = donchian_channels(&highs, &lows, 3).unwrap();
37/// ```
38pub 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}