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