quant_indicators/
detrended.rs1use quant_primitives::Candle;
14use rust_decimal::Decimal;
15
16use crate::atr::Atr;
17use crate::error::IndicatorError;
18use crate::hull::HullMa;
19use crate::indicator::Indicator;
20use crate::series::Series;
21
22#[derive(Debug, Clone)]
26pub struct DetrendedOscillator {
27 hma: HullMa,
28 atr: Atr,
29 name: String,
30}
31
32impl DetrendedOscillator {
33 pub fn new(hma_period: usize, atr_period: usize) -> Result<Self, IndicatorError> {
43 let hma = HullMa::new(hma_period)?;
44 let atr = Atr::new(atr_period)?;
45 Ok(Self {
46 name: format!("DetrendedOsc(HMA={},ATR={})", hma_period, atr_period),
47 hma,
48 atr,
49 })
50 }
51}
52
53impl Indicator for DetrendedOscillator {
54 fn name(&self) -> &str {
55 &self.name
56 }
57
58 fn warmup_period(&self) -> usize {
59 self.hma.warmup_period().max(self.atr.warmup_period())
63 }
64
65 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
66 let required = self.warmup_period();
67 if candles.len() < required {
68 return Err(IndicatorError::InsufficientData {
69 required,
70 actual: candles.len(),
71 });
72 }
73
74 let hma_series = self.hma.compute(candles)?;
75 let atr_series = self.atr.compute(candles)?;
76
77 let hma_vals = hma_series.values();
80 let atr_vals = atr_series.values();
81
82 let len = hma_vals.len().min(atr_vals.len());
84
85 let hma_tail = &hma_vals[hma_vals.len() - len..];
87 let atr_tail = &atr_vals[atr_vals.len() - len..];
88
89 let candle_tail = &candles[candles.len() - len..];
91
92 let mut values = Vec::with_capacity(len);
93 for i in 0..len {
94 let close = candle_tail[i].close();
95 let hma = hma_tail[i].1;
96 let atr = atr_tail[i].1;
97 let ts = candle_tail[i].timestamp();
98
99 if atr == Decimal::ZERO {
100 values.push((ts, Decimal::ZERO));
102 } else {
103 values.push((ts, (close - hma) / atr));
104 }
105 }
106
107 Ok(Series::new(values))
108 }
109}
110
111#[cfg(test)]
112#[path = "detrended_tests.rs"]
113mod tests;