wickra_core/indicators/
td_camouflage.rs1#![allow(clippy::doc_markdown)]
2
3use crate::ohlcv::Candle;
18use crate::traits::Indicator;
19
20#[derive(Debug, Clone, Default)]
37pub struct TdCamouflage {
38 prev: Option<Candle>,
39 last_value: Option<f64>,
40}
41
42impl TdCamouflage {
43 #[must_use]
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub const fn value(&self) -> Option<f64> {
51 self.last_value
52 }
53}
54
55impl Indicator for TdCamouflage {
56 type Input = Candle;
57 type Output = f64;
58
59 #[inline]
60 fn update(&mut self, candle: Candle) -> Option<f64> {
61 let Some(prev) = self.prev else {
62 self.prev = Some(candle);
63 self.last_value = None;
64 return None;
65 };
66 let v = if candle.close < prev.close && candle.close > candle.open && candle.low < prev.low
67 {
68 1.0
69 } else if candle.close > prev.close && candle.close < candle.open && candle.high > prev.high
70 {
71 -1.0
72 } else {
73 0.0
74 };
75 self.prev = Some(candle);
76 self.last_value = Some(v);
77 Some(v)
78 }
79
80 fn reset(&mut self) {
81 self.prev = None;
82 self.last_value = None;
83 }
84
85 #[inline]
86 fn warmup_period(&self) -> usize {
87 2
88 }
89
90 #[inline]
91 fn is_ready(&self) -> bool {
92 self.last_value.is_some()
93 }
94
95 #[inline]
96 fn name(&self) -> &'static str {
97 "TDCamouflage"
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::traits::BatchExt;
105
106 fn c(open: f64, high: f64, low: f64, close: f64) -> Candle {
107 Candle::new_unchecked(open, high, low, close, 0.0, 0)
108 }
109
110 #[test]
111 fn accessors_and_metadata() {
112 let td = TdCamouflage::new();
113 assert_eq!(td.warmup_period(), 2);
114 assert_eq!(td.name(), "TDCamouflage");
115 assert!(!td.is_ready());
116 assert_eq!(td.value(), None);
117 }
118
119 #[test]
120 fn first_bar_seeds_without_signal() {
121 let mut td = TdCamouflage::new();
122 assert_eq!(td.update(c(10.0, 11.0, 9.0, 10.0)), None);
123 assert!(td.update(c(10.0, 11.0, 8.0, 9.5)).is_some());
124 }
125
126 #[test]
127 fn bullish_camouflage_buy() {
128 let mut td = TdCamouflage::new();
131 td.update(c(10.0, 11.0, 8.0, 10.0));
132 assert_eq!(td.update(c(9.0, 10.0, 7.0, 9.5)), Some(1.0));
133 }
134
135 #[test]
136 fn bearish_camouflage_sell() {
137 let mut td = TdCamouflage::new();
140 td.update(c(10.0, 11.0, 8.0, 10.0));
141 assert_eq!(td.update(c(11.0, 12.0, 10.0, 10.5)), Some(-1.0));
142 }
143
144 #[test]
145 fn no_pattern_is_zero() {
146 let mut td = TdCamouflage::new();
147 td.update(c(10.0, 11.0, 9.0, 10.0));
148 assert_eq!(td.update(c(10.0, 11.5, 9.5, 11.0)), Some(0.0));
149 }
150
151 #[test]
152 fn reset_clears_state() {
153 let mut td = TdCamouflage::new();
154 td.update(c(10.0, 11.0, 9.0, 10.0));
155 td.update(c(9.0, 10.0, 7.0, 9.5));
156 assert!(td.is_ready());
157 td.reset();
158 assert!(!td.is_ready());
159 assert_eq!(td.update(c(10.0, 11.0, 9.0, 10.0)), None);
160 }
161
162 #[test]
163 fn batch_equals_streaming() {
164 let candles: Vec<Candle> = (0..40)
165 .map(|i| {
166 let b = 100.0 + (f64::from(i) * 0.4).sin() * 5.0;
167 c(b, b + 1.0, b - 1.0, b + 0.2)
168 })
169 .collect();
170 let batch = TdCamouflage::new().batch(&candles);
171 let mut b = TdCamouflage::new();
172 let streamed: Vec<_> = candles.iter().map(|x| b.update(*x)).collect();
173 assert_eq!(batch, streamed);
174 }
175}