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