1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
44pub struct UniqueThreeRiver {
45 c1: Option<Candle>,
46 c2: Option<Candle>,
47 has_emitted: bool,
48}
49
50impl UniqueThreeRiver {
51 pub const fn new() -> Self {
53 Self {
54 c1: None,
55 c2: None,
56 has_emitted: false,
57 }
58 }
59}
60
61impl Indicator for UniqueThreeRiver {
62 type Input = Candle;
63 type Output = f64;
64
65 #[inline]
66 fn update(&mut self, candle: Candle) -> Option<f64> {
67 let bar1 = self.c1;
68 let bar2 = self.c2;
69 self.c1 = self.c2;
70 self.c2 = Some(candle);
71 let (Some(bar1), Some(bar2)) = (bar1, bar2) else {
72 return None;
73 };
74 self.has_emitted = true;
75 if bar1.open <= bar1.close {
77 return Some(0.0);
78 }
79 let range1 = bar1.high - bar1.low;
80 if bar1.open - bar1.close < 0.5 * range1 {
81 return Some(0.0);
82 }
83 if bar2.open <= bar2.close {
85 return Some(0.0);
86 }
87 if bar2.open > bar1.open || bar2.close < bar1.close {
88 return Some(0.0);
89 }
90 if bar2.low >= bar1.low {
91 return Some(0.0);
92 }
93 if candle.close <= candle.open {
95 return Some(0.0);
96 }
97 let range3 = candle.high - candle.low;
98 if candle.close - candle.open > 0.3 * range3 {
99 return Some(0.0);
100 }
101 if candle.high > bar2.close {
102 return Some(0.0);
103 }
104 Some(1.0)
105 }
106
107 fn reset(&mut self) {
108 self.c1 = None;
109 self.c2 = None;
110 self.has_emitted = false;
111 }
112
113 #[inline]
114 fn warmup_period(&self) -> usize {
115 3
116 }
117
118 #[inline]
119 fn is_ready(&self) -> bool {
120 self.has_emitted
121 }
122
123 #[inline]
124 fn name(&self) -> &'static str {
125 "UniqueThreeRiver"
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::traits::BatchExt;
133
134 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
135 Candle::new(open, high, low, close, 1.0, ts).unwrap()
136 }
137
138 #[test]
139 fn accessors_and_metadata() {
140 let t = UniqueThreeRiver::new();
141 assert_eq!(t.name(), "UniqueThreeRiver");
142 assert_eq!(t.warmup_period(), 3);
143 assert!(!t.is_ready());
144 }
145
146 #[test]
147 fn unique_three_river_is_plus_one() {
148 let mut t = UniqueThreeRiver::new();
149 assert_eq!(t.update(c(15.0, 15.1, 10.0, 10.5, 0)), None);
150 assert_eq!(t.update(c(14.0, 14.1, 9.0, 11.0, 1)), None);
151 assert_eq!(t.update(c(10.2, 10.9, 9.5, 10.4, 2)), Some(1.0));
152 }
153
154 #[test]
155 fn first_two_bars_return_zero() {
156 let mut t = UniqueThreeRiver::new();
157 assert_eq!(t.update(c(15.0, 15.1, 10.0, 10.5, 0)), None);
158 assert_eq!(t.update(c(14.0, 14.1, 9.0, 11.0, 1)), None);
159 }
160
161 #[test]
162 fn first_bar_not_black_yields_zero() {
163 let mut t = UniqueThreeRiver::new();
164 t.update(c(10.5, 15.1, 10.0, 15.0, 0));
166 t.update(c(14.0, 14.1, 9.0, 11.0, 1));
167 assert_eq!(t.update(c(10.2, 10.9, 9.5, 10.4, 2)), Some(0.0));
168 }
169
170 #[test]
171 fn first_bar_short_body_yields_zero() {
172 let mut t = UniqueThreeRiver::new();
173 t.update(c(15.0, 15.1, 10.0, 14.5, 0));
175 t.update(c(14.0, 14.1, 9.0, 11.0, 1));
176 assert_eq!(t.update(c(10.2, 10.9, 9.5, 10.4, 2)), Some(0.0));
177 }
178
179 #[test]
180 fn second_bar_not_black_yields_zero() {
181 let mut t = UniqueThreeRiver::new();
182 t.update(c(15.0, 15.1, 10.0, 10.5, 0));
183 t.update(c(11.0, 14.1, 9.0, 13.0, 1));
185 assert_eq!(t.update(c(10.2, 10.9, 9.5, 10.4, 2)), Some(0.0));
186 }
187
188 #[test]
189 fn second_bar_not_inside_yields_zero() {
190 let mut t = UniqueThreeRiver::new();
191 t.update(c(15.0, 15.1, 10.0, 10.5, 0));
192 t.update(c(16.0, 16.1, 9.0, 11.0, 1));
194 assert_eq!(t.update(c(10.2, 10.9, 9.5, 10.4, 2)), Some(0.0));
195 }
196
197 #[test]
198 fn second_bar_no_new_low_yields_zero() {
199 let mut t = UniqueThreeRiver::new();
200 t.update(c(15.0, 15.1, 10.0, 10.5, 0));
201 t.update(c(14.0, 14.1, 10.5, 11.0, 1));
203 assert_eq!(t.update(c(10.2, 10.9, 9.5, 10.4, 2)), Some(0.0));
204 }
205
206 #[test]
207 fn third_bar_not_white_yields_zero() {
208 let mut t = UniqueThreeRiver::new();
209 t.update(c(15.0, 15.1, 10.0, 10.5, 0));
210 t.update(c(14.0, 14.1, 9.0, 11.0, 1));
211 assert_eq!(t.update(c(10.6, 10.9, 9.5, 10.2, 2)), Some(0.0));
213 }
214
215 #[test]
216 fn third_bar_large_body_yields_zero() {
217 let mut t = UniqueThreeRiver::new();
218 t.update(c(15.0, 15.1, 10.0, 10.5, 0));
219 t.update(c(14.0, 14.1, 9.0, 11.0, 1));
220 assert_eq!(t.update(c(9.6, 10.9, 9.5, 10.8, 2)), Some(0.0));
222 }
223
224 #[test]
225 fn third_bar_not_below_second_yields_zero() {
226 let mut t = UniqueThreeRiver::new();
227 t.update(c(15.0, 15.1, 10.0, 10.5, 0));
228 t.update(c(14.0, 14.1, 9.0, 11.0, 1));
229 assert_eq!(t.update(c(10.5, 11.5, 10.4, 10.7, 2)), Some(0.0));
231 }
232
233 #[test]
234 fn batch_equals_streaming() {
235 let candles: Vec<Candle> = (0..40)
236 .map(|i| {
237 let base = 200.0 - i as f64;
238 c(base, base + 0.1, base - 5.2, base - 5.0, i)
239 })
240 .collect();
241 let mut a = UniqueThreeRiver::new();
242 let mut b = UniqueThreeRiver::new();
243 assert_eq!(
244 a.batch(&candles),
245 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
246 );
247 }
248
249 #[test]
250 fn reset_clears_state() {
251 let mut t = UniqueThreeRiver::new();
252 t.update(c(15.0, 15.1, 10.0, 10.5, 0));
253 t.update(c(14.0, 14.1, 9.0, 11.0, 1));
254 t.update(c(10.2, 10.9, 9.5, 10.4, 2));
255 assert!(t.is_ready());
256 t.reset();
257 assert!(!t.is_ready());
258 assert_eq!(t.update(c(15.0, 15.1, 10.0, 10.5, 0)), None);
259 }
260}