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