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