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 let pp = self.prev_prev;
64 let p = self.prev;
65 self.prev_prev = self.prev;
66 self.prev = Some(candle);
67 let (Some(b1), Some(b2)) = (pp, p) else {
68 return None;
69 };
70 self.has_emitted = true;
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 #[inline]
112 fn warmup_period(&self) -> usize {
113 3
114 }
115
116 #[inline]
117 fn is_ready(&self) -> bool {
118 self.has_emitted
119 }
120
121 #[inline]
122 fn name(&self) -> &'static str {
123 "ThreeInside"
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::traits::BatchExt;
131
132 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
133 Candle::new(open, high, low, close, 1.0, ts).unwrap()
134 }
135
136 #[test]
137 fn accessors_and_metadata() {
138 let t = ThreeInside::new();
139 assert_eq!(t.name(), "ThreeInside");
140 assert_eq!(t.warmup_period(), 3);
141 assert!(!t.is_ready());
142 }
143
144 #[test]
145 fn three_inside_up_is_plus_one() {
146 let mut t = ThreeInside::new();
147 assert_eq!(t.update(c(12.0, 12.5, 9.5, 10.0, 0)), None);
148 assert_eq!(t.update(c(10.5, 11.5, 10.4, 11.0, 1)), None);
149 assert_eq!(t.update(c(11.0, 13.0, 10.9, 12.5, 2)), Some(1.0));
151 }
152
153 #[test]
154 fn three_inside_down_is_minus_one() {
155 let mut t = ThreeInside::new();
156 assert_eq!(t.update(c(10.0, 12.5, 9.5, 12.0, 0)), None);
157 assert_eq!(t.update(c(11.5, 11.6, 10.9, 11.0, 1)), None);
158 assert_eq!(t.update(c(11.0, 11.1, 9.3, 9.5, 2)), Some(-1.0));
160 }
161
162 #[test]
163 fn unconfirmed_third_bar_yields_zero() {
164 let mut t = ThreeInside::new();
165 t.update(c(12.0, 12.5, 9.5, 10.0, 0));
166 t.update(c(10.5, 11.5, 10.4, 11.0, 1));
167 assert_eq!(t.update(c(11.0, 11.8, 10.9, 11.5, 2)), Some(0.0));
169 }
170
171 #[test]
172 fn first_two_bars_return_zero() {
173 let mut t = ThreeInside::new();
174 assert_eq!(t.update(c(12.0, 12.5, 9.5, 10.0, 0)), None);
175 assert_eq!(t.update(c(10.5, 11.5, 10.4, 11.0, 1)), None);
176 }
177
178 #[test]
179 fn batch_equals_streaming() {
180 let candles: Vec<Candle> = (0..40)
181 .map(|i| {
182 let base = 100.0 + i as f64;
183 c(base, base + 1.0, base - 0.5, base + 0.5, i)
184 })
185 .collect();
186 let mut a = ThreeInside::new();
187 let mut b = ThreeInside::new();
188 assert_eq!(
189 a.batch(&candles),
190 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
191 );
192 }
193
194 #[test]
195 fn reset_clears_state() {
196 let mut t = ThreeInside::new();
197 t.update(c(12.0, 12.5, 9.5, 10.0, 0));
198 t.update(c(10.5, 11.5, 10.4, 11.0, 1));
199 t.update(c(11.0, 13.0, 10.9, 12.5, 2));
200 assert!(t.is_ready());
201 t.reset();
202 assert!(!t.is_ready());
203 assert_eq!(t.update(c(12.0, 12.5, 9.5, 10.0, 0)), None);
204 }
205}