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