finance_query/indicators/
keltner_channels.rs1use super::{IndicatorError, Result, atr::atr, ema::ema};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct KeltnerChannelsResult {
9 pub upper: Vec<Option<f64>>,
11 pub middle: Vec<Option<f64>>,
13 pub lower: Vec<Option<f64>>,
15}
16
17pub 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}