Skip to main content

finance_query/indicators/
keltner_channels.rs

1//! Keltner Channels indicator.
2
3use super::{IndicatorError, Result, atr::atr, ema::ema};
4use serde::{Deserialize, Serialize};
5
6/// Result of Keltner Channels calculation
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct KeltnerChannelsResult {
9    /// Upper channel
10    pub upper: Vec<Option<f64>>,
11    /// Middle channel (EMA)
12    pub middle: Vec<Option<f64>>,
13    /// Lower channel
14    pub lower: Vec<Option<f64>>,
15}
16
17/// Calculate Keltner Channels.
18///
19/// Middle Line = EMA(period)
20/// Upper Channel = EMA + (multiplier * ATR)
21/// Lower Channel = EMA - (multiplier * ATR)
22///
23/// # Arguments
24///
25/// * `highs` - High prices
26/// * `lows` - Low prices
27/// * `closes` - Close prices
28/// * `period` - EMA period
29/// * `atr_period` - ATR period
30/// * `multiplier` - ATR multiplier
31///
32/// # Example
33///
34/// ```
35/// use finance_query::indicators::keltner_channels;
36///
37/// let highs = vec![10.0; 20];
38/// let lows = vec![8.0; 20];
39/// let closes = vec![9.0; 20];
40/// let result = keltner_channels(&highs, &lows, &closes, 10, 10, 2.0).unwrap();
41/// ```
42pub fn keltner_channels(
43    highs: &[f64],
44    lows: &[f64],
45    closes: &[f64],
46    period: usize,
47    atr_period: usize,
48    multiplier: f64,
49) -> Result<KeltnerChannelsResult> {
50    if period == 0 || atr_period == 0 {
51        return Err(IndicatorError::InvalidPeriod(
52            "Periods must be greater than 0".to_string(),
53        ));
54    }
55    let len = highs.len();
56    if lows.len() != len || closes.len() != len {
57        return Err(IndicatorError::InvalidPeriod(
58            "Data lengths must match".to_string(),
59        ));
60    }
61    if len < period {
62        return Err(IndicatorError::InsufficientData {
63            need: period,
64            got: len,
65        });
66    }
67
68    let ema_values = ema(closes, period);
69    let atr_values = atr(highs, lows, closes, atr_period)?;
70
71    let mut upper = vec![None; len];
72    let mut middle = vec![None; len];
73    let mut lower = vec![None; len];
74
75    for i in 0..len {
76        if let (Some(ema_val), Some(atr_val)) = (ema_values[i], atr_values[i]) {
77            middle[i] = Some(ema_val);
78            upper[i] = Some(ema_val + (multiplier * atr_val));
79            lower[i] = Some(ema_val - (multiplier * atr_val));
80        } else if let Some(ema_val) = ema_values[i] {
81            middle[i] = Some(ema_val);
82        }
83    }
84
85    Ok(KeltnerChannelsResult {
86        upper,
87        middle,
88        lower,
89    })
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_keltner_channels() {
98        let highs = vec![10.0; 20];
99        let lows = vec![8.0; 20];
100        let closes = vec![9.0; 20];
101        let result = keltner_channels(&highs, &lows, &closes, 10, 10, 2.0).unwrap();
102
103        assert_eq!(result.upper.len(), 20);
104        assert!(result.upper[8].is_none());
105        assert!(result.upper[9].is_some());
106    }
107}