wickra_core/indicators/
hammer.rs1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
44pub struct Hammer {
45 has_emitted: bool,
46}
47
48impl Hammer {
49 pub const fn new() -> Self {
51 Self { has_emitted: false }
52 }
53}
54
55impl Indicator for Hammer {
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 lower >= 2.0 * body && upper <= 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 "Hammer"
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 = Hammer::new();
111 assert_eq!(h.name(), "Hammer");
112 assert_eq!(h.warmup_period(), 1);
113 assert!(!h.is_ready());
114 }
115
116 #[test]
117 fn clean_hammer_is_one() {
118 let mut h = Hammer::new();
119 assert_eq!(h.update(c(10.0, 10.6, 5.0, 10.5, 0)), Some(1.0));
121 }
122
123 #[test]
124 fn marubozu_is_not_hammer() {
125 let mut h = Hammer::new();
126 assert_eq!(h.update(c(10.0, 12.0, 10.0, 12.0, 0)), Some(0.0));
127 }
128
129 #[test]
130 fn shooting_star_shape_is_not_hammer() {
131 let mut h = Hammer::new();
133 assert_eq!(h.update(c(10.5, 15.0, 10.0, 10.0, 0)), Some(0.0));
134 }
135
136 #[test]
137 fn doji_is_not_hammer() {
138 let mut h = Hammer::new();
139 assert_eq!(h.update(c(10.0, 11.0, 9.0, 10.0, 0)), Some(0.0));
140 }
141
142 #[test]
143 fn zero_range_yields_zero() {
144 let mut h = Hammer::new();
145 assert_eq!(h.update(c(10.0, 10.0, 10.0, 10.0, 0)), Some(0.0));
146 }
147
148 #[test]
149 fn batch_equals_streaming() {
150 let candles: Vec<Candle> = (0..40)
151 .map(|i| {
152 let base = 100.0 + i as f64;
153 c(base, base + 2.0, base - 4.0, base + 0.5, i)
154 })
155 .collect();
156 let mut a = Hammer::new();
157 let mut b = Hammer::new();
158 assert_eq!(
159 a.batch(&candles),
160 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
161 );
162 }
163
164 #[test]
165 fn reset_clears_state() {
166 let mut h = Hammer::new();
167 h.update(c(10.0, 10.6, 5.0, 10.5, 0));
168 assert!(h.is_ready());
169 h.reset();
170 assert!(!h.is_ready());
171 }
172}