1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::indicators::atr::Atr;
7use crate::ohlcv::Candle;
8use crate::traits::Indicator;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct ChandeKrollStopOutput {
13 pub stop_long: f64,
15 pub stop_short: f64,
17}
18
19#[derive(Debug, Clone)]
54pub struct ChandeKrollStop {
55 atr_period: usize,
56 atr_multiplier: f64,
57 stop_period: usize,
58 atr: Atr,
59 highs: VecDeque<f64>,
60 lows: VecDeque<f64>,
61 high_stops: VecDeque<f64>,
62 low_stops: VecDeque<f64>,
63}
64
65impl ChandeKrollStop {
66 pub fn new(atr_period: usize, atr_multiplier: f64, stop_period: usize) -> Result<Self> {
73 if !atr_multiplier.is_finite() || atr_multiplier <= 0.0 {
74 return Err(Error::NonPositiveMultiplier);
75 }
76 if stop_period == 0 {
77 return Err(Error::PeriodZero);
78 }
79 Ok(Self {
80 atr_period,
81 atr_multiplier,
82 stop_period,
83 atr: Atr::new(atr_period)?,
84 highs: VecDeque::with_capacity(atr_period),
85 lows: VecDeque::with_capacity(atr_period),
86 high_stops: VecDeque::with_capacity(stop_period),
87 low_stops: VecDeque::with_capacity(stop_period),
88 })
89 }
90
91 pub fn classic() -> Self {
93 Self::new(10, 1.0, 9).expect("classic Chande Kroll Stop params are valid")
94 }
95
96 pub const fn params(&self) -> (usize, f64, usize) {
98 (self.atr_period, self.atr_multiplier, self.stop_period)
99 }
100}
101
102impl Indicator for ChandeKrollStop {
103 type Input = Candle;
104 type Output = ChandeKrollStopOutput;
105
106 fn update(&mut self, candle: Candle) -> Option<ChandeKrollStopOutput> {
107 let atr = self.atr.update(candle);
108 if self.highs.len() == self.atr_period {
109 self.highs.pop_front();
110 self.lows.pop_front();
111 }
112 self.highs.push_back(candle.high);
113 self.lows.push_back(candle.low);
114 if self.highs.len() < self.atr_period {
115 return None;
116 }
117 let atr = atr?;
120 let highest = self.highs.iter().copied().fold(f64::NEG_INFINITY, f64::max);
121 let lowest = self.lows.iter().copied().fold(f64::INFINITY, f64::min);
122 let high_stop = highest - self.atr_multiplier * atr;
123 let low_stop = lowest + self.atr_multiplier * atr;
124
125 if self.high_stops.len() == self.stop_period {
126 self.high_stops.pop_front();
127 self.low_stops.pop_front();
128 }
129 self.high_stops.push_back(high_stop);
130 self.low_stops.push_back(low_stop);
131 if self.high_stops.len() < self.stop_period {
132 return None;
133 }
134 let stop_short = self
135 .high_stops
136 .iter()
137 .copied()
138 .fold(f64::NEG_INFINITY, f64::max);
139 let stop_long = self.low_stops.iter().copied().fold(f64::INFINITY, f64::min);
140 Some(ChandeKrollStopOutput {
141 stop_long,
142 stop_short,
143 })
144 }
145
146 fn reset(&mut self) {
147 self.atr.reset();
148 self.highs.clear();
149 self.lows.clear();
150 self.high_stops.clear();
151 self.low_stops.clear();
152 }
153
154 fn warmup_period(&self) -> usize {
155 self.atr_period + self.stop_period - 1
158 }
159
160 fn is_ready(&self) -> bool {
161 self.high_stops.len() == self.stop_period
162 }
163
164 fn name(&self) -> &'static str {
165 "ChandeKrollStop"
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::traits::BatchExt;
173 use approx::assert_relative_eq;
174
175 fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
176 Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
177 }
178
179 #[test]
180 fn reference_values_flat_market() {
181 let candles: Vec<Candle> = (0..20).map(|i| c(11.0, 9.0, 10.0, i)).collect();
185 let mut cks = ChandeKrollStop::new(5, 1.0, 3).unwrap();
186 let last = cks.batch(&candles).into_iter().flatten().last().unwrap();
187 assert_relative_eq!(last.stop_short, 9.0, epsilon = 1e-12);
188 assert_relative_eq!(last.stop_long, 11.0, epsilon = 1e-12);
189 }
190
191 #[test]
192 fn first_emission_matches_warmup_period() {
193 let candles: Vec<Candle> = (0..16)
194 .map(|i| {
195 let base = 100.0 + i as f64;
196 c(base + 1.0, base - 1.0, base, i)
197 })
198 .collect();
199 let mut cks = ChandeKrollStop::new(4, 1.0, 3).unwrap();
200 let out = cks.batch(&candles);
201 assert_eq!(cks.warmup_period(), 6);
202 for (i, v) in out.iter().enumerate().take(5) {
203 assert!(v.is_none(), "index {i} must be None during warmup");
204 }
205 assert!(out[5].is_some(), "first value lands at warmup_period - 1");
206 }
207
208 #[test]
209 fn rejects_invalid_params() {
210 assert!(ChandeKrollStop::new(0, 1.0, 9).is_err());
211 assert!(ChandeKrollStop::new(10, 1.0, 0).is_err());
212 assert!(ChandeKrollStop::new(10, 0.0, 9).is_err());
213 assert!(ChandeKrollStop::new(10, -1.0, 9).is_err());
214 assert!(ChandeKrollStop::new(10, f64::NAN, 9).is_err());
215 }
216
217 #[test]
220 fn accessors_and_metadata() {
221 let s = ChandeKrollStop::new(10, 1.0, 9).unwrap();
222 let (p, m, q) = s.params();
223 assert_eq!(p, 10);
224 assert!((m - 1.0).abs() < 1e-12);
225 assert_eq!(q, 9);
226 assert_eq!(s.name(), "ChandeKrollStop");
227 }
228
229 #[test]
230 fn reset_clears_state() {
231 let candles: Vec<Candle> = (0..40)
232 .map(|i| {
233 let base = 100.0 + i as f64;
234 c(base + 1.0, base - 1.0, base, i)
235 })
236 .collect();
237 let mut cks = ChandeKrollStop::classic();
238 cks.batch(&candles);
239 assert!(cks.is_ready());
240 cks.reset();
241 assert!(!cks.is_ready());
242 assert_eq!(cks.update(candles[0]), None);
243 }
244
245 #[test]
246 fn batch_equals_streaming() {
247 let candles: Vec<Candle> = (0..80)
248 .map(|i| {
249 let mid = 100.0 + (i as f64 * 0.3).sin() * 8.0;
250 c(mid + 1.5, mid - 1.5, mid + 0.5, i)
251 })
252 .collect();
253 let mut a = ChandeKrollStop::classic();
254 let mut b = ChandeKrollStop::classic();
255 assert_eq!(
256 a.batch(&candles),
257 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
258 );
259 }
260}