wickra_core/indicators/
dragonfly_doji.rs1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
41pub struct DragonflyDoji {
42 has_emitted: bool,
43}
44
45impl DragonflyDoji {
46 pub const fn new() -> Self {
48 Self { has_emitted: false }
49 }
50}
51
52impl Indicator for DragonflyDoji {
53 type Input = Candle;
54 type Output = f64;
55
56 fn update(&mut self, candle: Candle) -> Option<f64> {
57 self.has_emitted = true;
58 let range = candle.high - candle.low;
59 if range <= 0.0 {
60 return Some(0.0);
61 }
62 if (candle.close - candle.open).abs() > 0.1 * range {
63 return Some(0.0);
64 }
65 let upper = candle.high - candle.open.max(candle.close);
66 let lower = candle.open.min(candle.close) - candle.low;
67 if upper <= 0.1 * range && lower >= 0.5 * range {
68 return Some(1.0);
69 }
70 Some(0.0)
71 }
72
73 fn reset(&mut self) {
74 self.has_emitted = false;
75 }
76
77 fn warmup_period(&self) -> usize {
78 1
79 }
80
81 fn is_ready(&self) -> bool {
82 self.has_emitted
83 }
84
85 fn name(&self) -> &'static str {
86 "DragonflyDoji"
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::traits::BatchExt;
94
95 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
96 Candle::new(open, high, low, close, 1.0, ts).unwrap()
97 }
98
99 #[test]
100 fn accessors_and_metadata() {
101 let t = DragonflyDoji::new();
102 assert_eq!(t.name(), "DragonflyDoji");
103 assert_eq!(t.warmup_period(), 1);
104 assert!(!t.is_ready());
105 }
106
107 #[test]
108 fn dragonfly_is_plus_one() {
109 let mut t = DragonflyDoji::new();
110 assert_eq!(t.update(c(10.0, 10.05, 6.0, 10.0, 0)), Some(1.0));
111 }
112
113 #[test]
114 fn upper_shadow_yields_zero() {
115 let mut t = DragonflyDoji::new();
116 assert_eq!(t.update(c(10.0, 14.0, 9.95, 10.0, 0)), Some(0.0));
118 }
119
120 #[test]
121 fn short_lower_shadow_yields_zero() {
122 let mut t = DragonflyDoji::new();
123 assert_eq!(t.update(c(10.0, 10.05, 9.6, 10.0, 0)), Some(0.0));
125 }
126
127 #[test]
128 fn non_doji_yields_zero() {
129 let mut t = DragonflyDoji::new();
130 assert_eq!(t.update(c(10.0, 12.0, 6.0, 11.5, 0)), Some(0.0));
131 }
132
133 #[test]
134 fn zero_range_yields_zero() {
135 let mut t = DragonflyDoji::new();
136 assert_eq!(t.update(c(10.0, 10.0, 10.0, 10.0, 0)), Some(0.0));
137 }
138
139 #[test]
140 fn batch_equals_streaming() {
141 let candles: Vec<Candle> = (0..40)
142 .map(|i| {
143 let base = 100.0 + i as f64;
144 c(base, base + 0.05, base - 4.0, base, i)
145 })
146 .collect();
147 let mut a = DragonflyDoji::new();
148 let mut b = DragonflyDoji::new();
149 assert_eq!(
150 a.batch(&candles),
151 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
152 );
153 }
154
155 #[test]
156 fn reset_clears_state() {
157 let mut t = DragonflyDoji::new();
158 t.update(c(10.0, 10.05, 6.0, 10.0, 0));
159 assert!(t.is_ready());
160 t.reset();
161 assert!(!t.is_ready());
162 }
163}