1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
41pub struct ThreeInside {
42 prev: Option<Candle>,
43 prev_prev: Option<Candle>,
44 has_emitted: bool,
45}
46
47impl ThreeInside {
48 pub const fn new() -> Self {
50 Self {
51 prev: None,
52 prev_prev: None,
53 has_emitted: false,
54 }
55 }
56}
57
58impl Indicator for ThreeInside {
59 type Input = Candle;
60 type Output = f64;
61
62 fn update(&mut self, candle: Candle) -> Option<f64> {
63 self.has_emitted = true;
64 let pp = self.prev_prev;
65 let p = self.prev;
66 self.prev_prev = self.prev;
67 self.prev = Some(candle);
68 let (Some(b1), Some(b2)) = (pp, p) else {
69 return Some(0.0);
70 };
71 let body1 = (b1.close - b1.open).abs();
72 let body2 = (b2.close - b2.open).abs();
73 if body1 <= 0.0 || body2 <= 0.0 || body2 >= body1 {
74 return Some(0.0);
75 }
76 let b1_red = b1.close < b1.open;
77 let b1_green = b1.close > b1.open;
78 let b2_green = b2.close > b2.open;
79 let b2_red = b2.close < b2.open;
80 let b3_green = candle.close > candle.open;
81 let b3_red = candle.close < candle.open;
82 if b1_red
84 && b2_green
85 && b2.open >= b1.close
86 && b2.close <= b1.open
87 && b3_green
88 && candle.close > b1.open
89 {
90 return Some(1.0);
91 }
92 if b1_green
94 && b2_red
95 && b2.open <= b1.close
96 && b2.close >= b1.open
97 && b3_red
98 && candle.close < b1.open
99 {
100 return Some(-1.0);
101 }
102 Some(0.0)
103 }
104
105 fn reset(&mut self) {
106 self.prev = None;
107 self.prev_prev = None;
108 self.has_emitted = false;
109 }
110
111 fn warmup_period(&self) -> usize {
112 3
113 }
114
115 fn is_ready(&self) -> bool {
116 self.has_emitted
117 }
118
119 fn name(&self) -> &'static str {
120 "ThreeInside"
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::traits::BatchExt;
128
129 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
130 Candle::new(open, high, low, close, 1.0, ts).unwrap()
131 }
132
133 #[test]
134 fn accessors_and_metadata() {
135 let t = ThreeInside::new();
136 assert_eq!(t.name(), "ThreeInside");
137 assert_eq!(t.warmup_period(), 3);
138 assert!(!t.is_ready());
139 }
140
141 #[test]
142 fn three_inside_up_is_plus_one() {
143 let mut t = ThreeInside::new();
144 assert_eq!(t.update(c(12.0, 12.5, 9.5, 10.0, 0)), Some(0.0));
145 assert_eq!(t.update(c(10.5, 11.5, 10.4, 11.0, 1)), Some(0.0));
146 assert_eq!(t.update(c(11.0, 13.0, 10.9, 12.5, 2)), Some(1.0));
148 }
149
150 #[test]
151 fn three_inside_down_is_minus_one() {
152 let mut t = ThreeInside::new();
153 assert_eq!(t.update(c(10.0, 12.5, 9.5, 12.0, 0)), Some(0.0));
154 assert_eq!(t.update(c(11.5, 11.6, 10.9, 11.0, 1)), Some(0.0));
155 assert_eq!(t.update(c(11.0, 11.1, 9.3, 9.5, 2)), Some(-1.0));
157 }
158
159 #[test]
160 fn unconfirmed_third_bar_yields_zero() {
161 let mut t = ThreeInside::new();
162 t.update(c(12.0, 12.5, 9.5, 10.0, 0));
163 t.update(c(10.5, 11.5, 10.4, 11.0, 1));
164 assert_eq!(t.update(c(11.0, 11.8, 10.9, 11.5, 2)), Some(0.0));
166 }
167
168 #[test]
169 fn first_two_bars_return_zero() {
170 let mut t = ThreeInside::new();
171 assert_eq!(t.update(c(12.0, 12.5, 9.5, 10.0, 0)), Some(0.0));
172 assert_eq!(t.update(c(10.5, 11.5, 10.4, 11.0, 1)), Some(0.0));
173 }
174
175 #[test]
176 fn batch_equals_streaming() {
177 let candles: Vec<Candle> = (0..40)
178 .map(|i| {
179 let base = 100.0 + i as f64;
180 c(base, base + 1.0, base - 0.5, base + 0.5, i)
181 })
182 .collect();
183 let mut a = ThreeInside::new();
184 let mut b = ThreeInside::new();
185 assert_eq!(
186 a.batch(&candles),
187 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
188 );
189 }
190
191 #[test]
192 fn reset_clears_state() {
193 let mut t = ThreeInside::new();
194 t.update(c(12.0, 12.5, 9.5, 10.0, 0));
195 t.update(c(10.5, 11.5, 10.4, 11.0, 1));
196 t.update(c(11.0, 13.0, 10.9, 12.5, 2));
197 assert!(t.is_ready());
198 t.reset();
199 assert!(!t.is_ready());
200 assert_eq!(t.update(c(12.0, 12.5, 9.5, 10.0, 0)), Some(0.0));
201 }
202}