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)]
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}