Skip to main content

kestrel_chartkit/indicator/
coppock.rs

1use super::smoothing::Wma;
2use super::{Indicator, IndicatorAlert, IndicatorOutput};
3use crate::model::Bar;
4use std::collections::VecDeque;
5
6/// Coppock Curve Engine.
7/// Coppock Curve = WMA(10) of (ROC(14) + ROC(11))
8#[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}