finance_query/indicators/
keltner_channels.rs1use super::{IndicatorError, Result, atr::atr_raw, ema::ema_raw};
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 atr_dense = atr_raw(highs, lows, closes, atr_period)?;
69 keltner_with_atr_dense(closes, period, &atr_dense, atr_period, multiplier)
70}
71
72pub(crate) fn keltner_with_atr_dense(
76 closes: &[f64],
77 period: usize,
78 atr_dense: &[f64],
79 atr_period: usize,
80 multiplier: f64,
81) -> Result<KeltnerChannelsResult> {
82 if period == 0 || atr_period == 0 {
83 return Err(IndicatorError::InvalidPeriod(
84 "Periods must be greater than 0".to_string(),
85 ));
86 }
87 let len = closes.len();
88 if len < period {
89 return Err(IndicatorError::InsufficientData {
90 need: period,
91 got: len,
92 });
93 }
94 let ema_vals = ema_raw(closes, period);
95 let ema_off = period - 1;
96 let atr_off = atr_period - 1;
97 let mut upper = vec![None; len];
98 let mut middle = vec![None; len];
99 let mut lower = vec![None; len];
100 for (k, &ev) in ema_vals.iter().enumerate() {
101 let i = k + ema_off;
102 middle[i] = Some(ev);
103 if i >= atr_off {
104 let av = atr_dense[i - atr_off];
105 upper[i] = Some(ev + multiplier * av);
106 lower[i] = Some(ev - multiplier * av);
107 }
108 }
109 Ok(KeltnerChannelsResult {
110 upper,
111 middle,
112 lower,
113 })
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_keltner_channels() {
122 let highs = vec![10.0; 20];
123 let lows = vec![8.0; 20];
124 let closes = vec![9.0; 20];
125 let result = keltner_channels(&highs, &lows, &closes, 10, 10, 2.0).unwrap();
126
127 assert_eq!(result.upper.len(), 20);
128 assert!(result.upper[8].is_none());
129 assert!(result.upper[9].is_some());
130 }
131}