kestrel_chartkit/indicator/
coppock.rs1use super::smoothing::Wma;
2use super::{Indicator, IndicatorAlert, IndicatorOutput};
3use crate::model::Bar;
4use std::collections::VecDeque;
5
6#[derive(Debug, Clone)]
9pub struct CoppockCurveEngine {
10 wma10: Wma,
11 closes: VecDeque<f64>,
12}
13
14impl CoppockCurveEngine {
15 pub fn new() -> Self {
16 Self {
17 wma10: Wma::new(10),
18 closes: VecDeque::with_capacity(15),
19 }
20 }
21}
22
23impl Default for CoppockCurveEngine {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl Indicator for CoppockCurveEngine {
30 fn name(&self) -> &str {
31 "coppock"
32 }
33
34 fn warmup_period(&self) -> usize {
35 25
36 }
37
38 fn reset(&mut self) {
39 self.wma10.reset();
40 self.closes.clear();
41 }
42
43 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
44 self.closes.push_back(bar.close);
45 if self.closes.len() > 15 {
46 self.closes.pop_front();
47 }
48
49 if self.closes.len() < 15 {
50 return None;
51 }
52
53 let c_curr = bar.close;
54 let c_14 = self.closes[self.closes.len() - 15];
55 let c_11 = self.closes[self.closes.len() - 12];
56
57 let roc14 = if c_14 > 0.0 {
58 ((c_curr - c_14) / c_14) * 100.0
59 } else {
60 0.0
61 };
62 let roc11 = if c_11 > 0.0 {
63 ((c_curr - c_11) / c_11) * 100.0
64 } else {
65 0.0
66 };
67
68 let raw = roc14 + roc11;
69 let coppock_val = self.wma10.update(raw)?;
70
71 Some(IndicatorOutput::new(coppock_val))
72 }
73
74 fn alerts(&self) -> Vec<IndicatorAlert> {
75 Vec::new()
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_coppock_curve() {
85 let mut cc = CoppockCurveEngine::new();
86 let mut out = None;
87 for i in 0..30 {
88 let b = Bar::new(i, 100.0, 105.0, 95.0, 100.0 + i as f64, 1000.0);
89 out = cc.on_bar(&b);
90 }
91 assert!(out.is_some());
92 }
93}