wickra_core/indicators/
kase_permission_stochastic.rs1use std::collections::VecDeque;
5
6use crate::error::{Error, Result};
7use crate::indicators::ema::Ema;
8use crate::ohlcv::Candle;
9use crate::traits::Indicator;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct KasePermissionStochasticOutput {
14 pub fast: f64,
16 pub slow: f64,
18}
19
20#[derive(Debug, Clone)]
55pub struct KasePermissionStochastic {
56 length: usize,
57 smooth: usize,
58 window: VecDeque<(f64, f64)>,
59 fast_ema: Ema,
60 slow_ema: Ema,
61}
62
63impl KasePermissionStochastic {
64 pub fn new(length: usize, smooth: usize) -> Result<Self> {
71 if length == 0 {
72 return Err(Error::PeriodZero);
73 }
74 Ok(Self {
75 length,
76 smooth,
77 window: VecDeque::with_capacity(length),
78 fast_ema: Ema::new(smooth)?,
79 slow_ema: Ema::new(smooth)?,
80 })
81 }
82
83 pub fn classic() -> Self {
85 Self::new(9, 3).expect("classic Kase Permission Stochastic parameters are valid")
86 }
87
88 pub const fn periods(&self) -> (usize, usize) {
90 (self.length, self.smooth)
91 }
92}
93
94impl Indicator for KasePermissionStochastic {
95 type Input = Candle;
96 type Output = KasePermissionStochasticOutput;
97
98 fn update(&mut self, candle: Candle) -> Option<KasePermissionStochasticOutput> {
99 self.window.push_back((candle.high, candle.low));
100 if self.window.len() > self.length {
101 self.window.pop_front();
102 }
103 if self.window.len() < self.length {
104 return None;
105 }
106
107 let highest = self.window.iter().map(|w| w.0).fold(f64::MIN, f64::max);
108 let lowest = self.window.iter().map(|w| w.1).fold(f64::MAX, f64::min);
109 let raw_k = if highest > lowest {
110 100.0 * (candle.close - lowest) / (highest - lowest)
111 } else {
112 50.0
113 };
114
115 let fast = self.fast_ema.update(raw_k)?;
116 let slow = self.slow_ema.update(fast)?;
117 Some(KasePermissionStochasticOutput { fast, slow })
118 }
119
120 fn reset(&mut self) {
121 self.window.clear();
122 self.fast_ema.reset();
123 self.slow_ema.reset();
124 }
125
126 fn warmup_period(&self) -> usize {
127 self.length + 2 * self.smooth - 2
129 }
130
131 fn is_ready(&self) -> bool {
132 self.slow_ema.is_ready()
133 }
134
135 fn name(&self) -> &'static str {
136 "KasePermissionStochastic"
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::traits::BatchExt;
144 use approx::assert_relative_eq;
145
146 fn candle(high: f64, low: f64, close: f64, ts: i64) -> Candle {
147 Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
148 }
149
150 #[test]
151 fn rejects_zero_period() {
152 assert!(matches!(
153 KasePermissionStochastic::new(0, 3),
154 Err(Error::PeriodZero)
155 ));
156 assert!(matches!(
157 KasePermissionStochastic::new(9, 0),
158 Err(Error::PeriodZero)
159 ));
160 }
161
162 #[test]
163 fn accessors_and_metadata() {
164 let k = KasePermissionStochastic::classic();
165 assert_eq!(k.periods(), (9, 3));
166 assert_eq!(k.warmup_period(), 13);
168 assert_eq!(k.name(), "KasePermissionStochastic");
169 assert!(!k.is_ready());
170 }
171
172 #[test]
173 fn warmup_emits_at_expected_bar() {
174 let mut k = KasePermissionStochastic::new(3, 2).unwrap();
175 let candles: Vec<Candle> = (0..8).map(|i| candle(11.0, 9.0, 10.5, i)).collect();
177 let out = k.batch(&candles);
178 assert!(out[3].is_none());
179 assert!(out[4].is_some());
180 }
181
182 #[test]
183 fn top_of_range_is_high() {
184 let mut k = KasePermissionStochastic::new(5, 3).unwrap();
187 let candles: Vec<Candle> = (0_i64..40)
188 .map(|i| {
189 let base = 100.0 + i as f64;
190 candle(base + 2.0, base - 2.0, base + 2.0, i)
191 })
192 .collect();
193 let last = k.batch(&candles).last().unwrap().unwrap();
194 assert!(last.fast > 80.0, "fast {} should be high", last.fast);
195 assert!(last.slow > 80.0, "slow {} should be high", last.slow);
196 }
197
198 #[test]
199 fn flat_window_defaults_to_neutral() {
200 let mut k = KasePermissionStochastic::new(4, 2).unwrap();
203 let candles: Vec<Candle> = (0..20).map(|i| candle(10.0, 10.0, 10.0, i)).collect();
204 let last = k.batch(&candles).last().unwrap().unwrap();
205 assert_relative_eq!(last.fast, 50.0, epsilon = 1e-9);
206 assert_relative_eq!(last.slow, 50.0, epsilon = 1e-9);
207 }
208
209 #[test]
210 fn reset_clears_state() {
211 let mut k = KasePermissionStochastic::classic();
212 let candles: Vec<Candle> = (0..40).map(|i| candle(11.0, 9.0, 10.5, i)).collect();
213 k.batch(&candles);
214 assert!(k.is_ready());
215 k.reset();
216 assert!(!k.is_ready());
217 }
218
219 #[test]
220 fn batch_equals_streaming() {
221 let candles: Vec<Candle> = (0..80_i64)
222 .map(|i| {
223 let base = 100.0 + (i as f64 * 0.2).sin() * 5.0;
224 candle(base + 2.0, base - 2.0, base + (i as f64 * 0.3).cos(), i)
225 })
226 .collect();
227 let mut a = KasePermissionStochastic::classic();
228 let mut b = KasePermissionStochastic::classic();
229 assert_eq!(
230 a.batch(&candles),
231 candles.iter().map(|c| b.update(*c)).collect::<Vec<_>>()
232 );
233 }
234}