1use std::fmt::{Debug, Display};
17
18use arraydeque::{ArrayDeque, Wrapping};
19use nautilus_model::data::Bar;
20use strum::Display;
21
22use crate::indicator::Indicator;
23
24#[repr(C)]
25#[derive(Debug, Display, Clone, Hash, PartialEq, Eq, Copy)]
26#[strum(ascii_case_insensitive)]
27#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
28#[cfg_attr(
29 feature = "python",
30 pyo3::pyclass(
31 frozen,
32 eq,
33 eq_int,
34 hash,
35 module = "nautilus_trader.core.nautilus_pyo3.indicators"
36 )
37)]
38pub enum CandleBodySize {
39 None = 0,
40 Small = 1,
41 Medium = 2,
42 Large = 3,
43 Trend = 4,
44}
45
46#[repr(C)]
47#[derive(Debug, Display, Clone, Hash, PartialEq, Eq, Copy)]
48#[strum(ascii_case_insensitive)]
49#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
50#[cfg_attr(
51 feature = "python",
52 pyo3::pyclass(
53 frozen,
54 eq,
55 eq_int,
56 hash,
57 module = "nautilus_trader.core.nautilus_pyo3.indicators"
58 )
59)]
60pub enum CandleDirection {
61 Bull = 1,
62 None = 0,
63 Bear = -1,
64}
65
66#[repr(C)]
67#[derive(Debug, Display, Clone, Hash, PartialEq, Eq, Copy)]
68#[strum(ascii_case_insensitive)]
69#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
70#[cfg_attr(
71 feature = "python",
72 pyo3::pyclass(
73 frozen,
74 eq,
75 eq_int,
76 hash,
77 module = "nautilus_trader.core.nautilus_pyo3.indicators"
78 )
79)]
80pub enum CandleSize {
81 None = 0,
82 VerySmall = 1,
83 Small = 2,
84 Medium = 3,
85 Large = 4,
86 VeryLarge = 5,
87 ExtremelyLarge = 6,
88}
89
90#[repr(C)]
91#[derive(Debug, Display, Clone, Hash, PartialEq, Eq, Copy)]
92#[strum(ascii_case_insensitive)]
93#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
94#[cfg_attr(
95 feature = "python",
96 pyo3::pyclass(
97 frozen,
98 eq,
99 eq_int,
100 hash,
101 module = "nautilus_trader.core.nautilus_pyo3.indicators"
102 )
103)]
104pub enum CandleWickSize {
105 None = 0,
106 Small = 1,
107 Medium = 2,
108 Large = 3,
109}
110
111#[repr(C)]
112#[derive(Debug, Clone, Copy)]
113#[cfg_attr(
114 feature = "python",
115 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators")
116)]
117pub struct FuzzyCandle {
118 pub direction: CandleDirection,
119 pub size: CandleSize,
120 pub body_size: CandleBodySize,
121 pub upper_wick_size: CandleWickSize,
122 pub lower_wick_size: CandleWickSize,
123}
124
125impl Display for FuzzyCandle {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(
128 f,
129 "{}({},{},{},{})",
130 self.direction, self.size, self.body_size, self.lower_wick_size, self.upper_wick_size
131 )
132 }
133}
134
135impl FuzzyCandle {
136 #[must_use]
137 pub const fn new(
138 direction: CandleDirection,
139 size: CandleSize,
140 body_size: CandleBodySize,
141 upper_wick_size: CandleWickSize,
142 lower_wick_size: CandleWickSize,
143 ) -> Self {
144 Self {
145 direction,
146 size,
147 body_size,
148 upper_wick_size,
149 lower_wick_size,
150 }
151 }
152}
153
154const MAX_CAPACITY: usize = 1024;
155
156#[repr(C)]
157#[derive(Debug)]
158#[cfg_attr(
159 feature = "python",
160 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators")
161)]
162pub struct FuzzyCandlesticks {
163 pub period: usize,
164 pub threshold1: f64,
165 pub threshold2: f64,
166 pub threshold3: f64,
167 pub threshold4: f64,
168 pub vector: Vec<i32>,
169 pub value: FuzzyCandle,
170 pub initialized: bool,
171 has_inputs: bool,
172 lengths: ArrayDeque<f64, MAX_CAPACITY, Wrapping>,
173 body_percents: ArrayDeque<f64, MAX_CAPACITY, Wrapping>,
174 upper_wick_percents: ArrayDeque<f64, MAX_CAPACITY, Wrapping>,
175 lower_wick_percents: ArrayDeque<f64, MAX_CAPACITY, Wrapping>,
176 last_open: f64,
177 last_high: f64,
178 last_low: f64,
179 last_close: f64,
180}
181
182impl Display for FuzzyCandlesticks {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(
185 f,
186 "{}({},{},{},{},{})",
187 self.name(),
188 self.period,
189 self.threshold1,
190 self.threshold2,
191 self.threshold3,
192 self.threshold4
193 )
194 }
195}
196
197impl Indicator for FuzzyCandlesticks {
198 fn name(&self) -> String {
199 stringify!(FuzzyCandlesticks).to_string()
200 }
201
202 fn has_inputs(&self) -> bool {
203 self.has_inputs
204 }
205
206 fn initialized(&self) -> bool {
207 self.initialized
208 }
209
210 fn handle_bar(&mut self, bar: &Bar) {
211 self.update_raw(
212 (&bar.open).into(),
213 (&bar.high).into(),
214 (&bar.low).into(),
215 (&bar.close).into(),
216 );
217 }
218
219 fn reset(&mut self) {
220 self.lengths.clear();
221 self.body_percents.clear();
222 self.upper_wick_percents.clear();
223 self.lower_wick_percents.clear();
224 self.last_open = 0.0;
225 self.last_high = 0.0;
226 self.last_close = 0.0;
227 self.last_low = 0.0;
228 self.has_inputs = false;
229 self.initialized = false;
230 }
231}
232
233impl FuzzyCandlesticks {
234 #[must_use]
246 pub fn new(
247 period: usize,
248 threshold1: f64,
249 threshold2: f64,
250 threshold3: f64,
251 threshold4: f64,
252 ) -> Self {
253 assert!(period <= MAX_CAPACITY);
254 Self {
255 period,
256 threshold1,
257 threshold2,
258 threshold3,
259 threshold4,
260 vector: Vec::new(),
261 value: FuzzyCandle::new(
262 CandleDirection::None,
263 CandleSize::None,
264 CandleBodySize::None,
265 CandleWickSize::None,
266 CandleWickSize::None,
267 ),
268 has_inputs: false,
269 initialized: false,
270 lengths: ArrayDeque::new(),
271 body_percents: ArrayDeque::new(),
272 upper_wick_percents: ArrayDeque::new(),
273 lower_wick_percents: ArrayDeque::new(),
274 last_open: 0.0,
275 last_high: 0.0,
276 last_low: 0.0,
277 last_close: 0.0,
278 }
279 }
280
281 pub fn update_raw(&mut self, open: f64, high: f64, low: f64, close: f64) {
282 if !self.has_inputs {
283 self.last_close = close;
284 self.last_open = open;
285 self.last_high = high;
286 self.last_low = low;
287 self.has_inputs = true;
288 }
289
290 self.last_close = close;
291 self.last_open = open;
292 self.last_high = high;
293 self.last_low = low;
294
295 let total = (high - low).abs();
296 let _ = self.lengths.push_back(total);
297
298 if total == 0.0 {
299 let _ = self.body_percents.push_back(0.0);
300 let _ = self.upper_wick_percents.push_back(0.0);
301 let _ = self.lower_wick_percents.push_back(0.0);
302 } else {
303 let body = (close - open).abs();
304 let upper_wick = high - f64::max(open, close);
305 let lower_wick = f64::min(open, close) - low;
306
307 let _ = self.body_percents.push_back(body / total);
308 let _ = self.upper_wick_percents.push_back(upper_wick / total);
309 let _ = self.lower_wick_percents.push_back(lower_wick / total);
310 }
311
312 if self.lengths.len() >= self.period {
313 self.initialized = true;
314 }
315
316 if !self.initialized {
318 return;
319 }
320
321 let mean_length = self.lengths.iter().sum::<f64>() / (self.period as f64);
322 let mean_body_percent = self.body_percents.iter().sum::<f64>() / (self.period as f64);
323 let mean_upper_percent =
324 self.upper_wick_percents.iter().sum::<f64>() / (self.period as f64);
325 let mean_lower_percent =
326 self.lower_wick_percents.iter().sum::<f64>() / (self.period as f64);
327
328 let sd_length = Self::std_dev(&self.lengths, mean_length);
329 let sd_body = Self::std_dev(&self.body_percents, mean_body_percent);
330 let sd_upper = Self::std_dev(&self.upper_wick_percents, mean_upper_percent);
331 let sd_lower = Self::std_dev(&self.lower_wick_percents, mean_lower_percent);
332 let latest_body = *self.body_percents.back().unwrap_or(&0.0);
333 let latest_upper = *self.upper_wick_percents.back().unwrap_or(&0.0);
334 let latest_lower = *self.lower_wick_percents.back().unwrap_or(&0.0);
335
336 self.value = FuzzyCandle::new(
337 self.fuzzify_direction(open, close),
338 self.fuzzify_size(total, mean_length, sd_length),
339 self.fuzzify_body_size(latest_body, mean_body_percent, sd_body),
340 self.fuzzify_wick_size(latest_upper, mean_upper_percent, sd_upper),
341 self.fuzzify_wick_size(latest_lower, mean_lower_percent, sd_lower),
342 );
343
344 self.vector = vec![
345 self.value.direction as i32,
346 self.value.size as i32,
347 self.value.body_size as i32,
348 self.value.upper_wick_size as i32,
349 self.value.lower_wick_size as i32,
350 ];
351 }
352
353 pub fn reset(&mut self) {
354 self.lengths.clear();
355 self.body_percents.clear();
356 self.upper_wick_percents.clear();
357 self.lower_wick_percents.clear();
358 self.value = FuzzyCandle::new(
359 CandleDirection::None,
360 CandleSize::None,
361 CandleBodySize::None,
362 CandleWickSize::None,
363 CandleWickSize::None,
364 );
365 self.vector = Vec::new();
366 self.last_open = 0.0;
367 self.last_high = 0.0;
368 self.last_close = 0.0;
369 self.last_low = 0.0;
370 self.has_inputs = false;
371 self.initialized = false;
372 }
373
374 fn fuzzify_direction(&self, open: f64, close: f64) -> CandleDirection {
375 if close > open {
376 CandleDirection::Bull
377 } else if close < open {
378 CandleDirection::Bear
379 } else {
380 CandleDirection::None
381 }
382 }
383
384 fn fuzzify_size(&self, length: f64, mean_length: f64, sd_lengths: f64) -> CandleSize {
385 if !length.is_finite() || length == 0.0 {
386 return CandleSize::None;
387 }
388
389 let thresholds = [
390 mean_length - self.threshold2 * sd_lengths, mean_length - self.threshold1 * sd_lengths, mean_length + self.threshold1 * sd_lengths, mean_length + self.threshold2 * sd_lengths, mean_length + self.threshold3 * sd_lengths, ];
396 if length <= thresholds[0] {
397 CandleSize::VerySmall
398 } else if length <= thresholds[1] {
399 CandleSize::Small
400 } else if length <= thresholds[2] {
401 CandleSize::Medium
402 } else if length <= thresholds[3] {
403 CandleSize::Large
404 } else if length <= thresholds[4] {
405 CandleSize::VeryLarge
406 } else {
407 CandleSize::ExtremelyLarge
408 }
409 }
410
411 fn fuzzify_body_size(
412 &self,
413 body_percent: f64,
414 mean_body_percent: f64,
415 sd_body_percent: f64,
416 ) -> CandleBodySize {
417 if body_percent == 0.0 {
418 return CandleBodySize::None;
419 }
420
421 let mut x;
422
423 x = sd_body_percent.mul_add(-self.threshold1, mean_body_percent);
424 if body_percent <= x {
425 return CandleBodySize::Small;
426 }
427
428 x = sd_body_percent.mul_add(self.threshold1, mean_body_percent);
429 if body_percent <= x {
430 return CandleBodySize::Medium;
431 }
432
433 x = sd_body_percent.mul_add(self.threshold2, mean_body_percent);
434 if body_percent <= x {
435 return CandleBodySize::Large;
436 }
437
438 CandleBodySize::Trend
439 }
440
441 fn fuzzify_wick_size(
442 &self,
443 wick_percent: f64,
444 mean_wick_percent: f64,
445 sd_wick_percents: f64,
446 ) -> CandleWickSize {
447 if wick_percent == 0.0 {
448 return CandleWickSize::None;
449 }
450
451 let mut x;
452 x = sd_wick_percents.mul_add(-self.threshold1, mean_wick_percent);
453 if wick_percent <= x {
454 return CandleWickSize::Small;
455 }
456
457 x = sd_wick_percents.mul_add(self.threshold2, mean_wick_percent);
458 if wick_percent <= x {
459 return CandleWickSize::Medium;
460 }
461
462 CandleWickSize::Large
463 }
464
465 fn std_dev<const CAP: usize>(buffer: &ArrayDeque<f64, CAP, Wrapping>, mean: f64) -> f64 {
466 if buffer.is_empty() {
467 return 0.0;
468 }
469 let variance = buffer
470 .iter()
471 .map(|v| {
472 let d = v - mean;
473 d * d
474 })
475 .sum::<f64>()
476 / (buffer.len() as f64);
477 variance.sqrt()
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use rstest::rstest;
484
485 use super::*;
486 use crate::{
487 stubs::{fuzzy_candlesticks_1, fuzzy_candlesticks_3, fuzzy_candlesticks_10},
488 volatility::fuzzy::FuzzyCandlesticks,
489 };
490
491 #[rstest]
492 fn test_psl_initialized(fuzzy_candlesticks_10: FuzzyCandlesticks) {
493 let display_str = format!("{fuzzy_candlesticks_10}");
494 assert_eq!(display_str, "FuzzyCandlesticks(10,0.1,0.15,0.2,0.3)");
495 assert_eq!(fuzzy_candlesticks_10.period, 10);
496 assert!(!fuzzy_candlesticks_10.initialized);
497 assert!(!fuzzy_candlesticks_10.has_inputs);
498 }
499
500 #[rstest]
501 fn test_value_with_one_input(mut fuzzy_candlesticks_1: FuzzyCandlesticks) {
502 fuzzy_candlesticks_1.update_raw(123.90, 135.79, 117.09, 125.09);
504 assert_eq!(fuzzy_candlesticks_1.value.direction, CandleDirection::Bull);
505 assert_eq!(fuzzy_candlesticks_1.value.size, CandleSize::VerySmall);
506 assert_eq!(fuzzy_candlesticks_1.value.body_size, CandleBodySize::Small);
507 assert_eq!(
508 fuzzy_candlesticks_1.value.upper_wick_size,
509 CandleWickSize::Small
510 );
511 assert_eq!(
512 fuzzy_candlesticks_1.value.lower_wick_size,
513 CandleWickSize::Small
514 );
515
516 let expected_vec = vec![1, 1, 1, 1, 1];
517 assert_eq!(fuzzy_candlesticks_1.vector, expected_vec);
518 }
519
520 #[rstest]
521 fn test_value_with_three_inputs(mut fuzzy_candlesticks_3: FuzzyCandlesticks) {
522 fuzzy_candlesticks_3.update_raw(142.35, 145.82, 141.20, 144.75);
524 fuzzy_candlesticks_3.update_raw(144.75, 144.93, 103.55, 108.22);
525 fuzzy_candlesticks_3.update_raw(108.22, 120.15, 105.01, 119.89);
526 assert_eq!(fuzzy_candlesticks_3.value.direction, CandleDirection::Bull);
527 assert_eq!(fuzzy_candlesticks_3.value.size, CandleSize::VerySmall);
528 assert_eq!(fuzzy_candlesticks_3.value.body_size, CandleBodySize::Trend);
529 assert_eq!(
530 fuzzy_candlesticks_3.value.upper_wick_size,
531 CandleWickSize::Small
532 );
533 assert_eq!(
534 fuzzy_candlesticks_3.value.lower_wick_size,
535 CandleWickSize::Large
536 );
537
538 let expected_vec = vec![1, 1, 4, 1, 3];
539 assert_eq!(fuzzy_candlesticks_3.vector, expected_vec);
540 }
541
542 #[rstest]
543 fn test_value_not_updated_before_initialization(mut fuzzy_candlesticks_10: FuzzyCandlesticks) {
544 fuzzy_candlesticks_10.update_raw(100.0, 105.0, 95.0, 102.0);
546 fuzzy_candlesticks_10.update_raw(102.0, 108.0, 100.0, 98.0);
547 fuzzy_candlesticks_10.update_raw(98.0, 101.0, 96.0, 100.0);
548
549 assert_eq!(fuzzy_candlesticks_10.vector.len(), 0);
550 assert!(
551 !fuzzy_candlesticks_10.initialized,
552 "Should not be initialized before period"
553 );
554 assert!(fuzzy_candlesticks_10.has_inputs, "Should has inputs");
555 assert_eq!(fuzzy_candlesticks_10.lengths.len(), 3);
556 assert_eq!(fuzzy_candlesticks_10.body_percents.len(), 3);
557 }
558
559 #[rstest]
560 fn test_value_with_ten_inputs(mut fuzzy_candlesticks_10: FuzzyCandlesticks) {
561 fuzzy_candlesticks_10.update_raw(150.25, 153.4, 148.1, 152.75);
562 fuzzy_candlesticks_10.update_raw(152.8, 155.2, 151.3, 151.95);
563 fuzzy_candlesticks_10.update_raw(151.9, 152.85, 147.6, 148.2);
564 fuzzy_candlesticks_10.update_raw(148.3, 150.75, 146.9, 150.4);
565 fuzzy_candlesticks_10.update_raw(150.5, 154.3, 149.8, 153.9);
566 fuzzy_candlesticks_10.update_raw(153.95, 155.8, 152.2, 152.6);
567 fuzzy_candlesticks_10.update_raw(152.7, 153.4, 148.5, 149.1);
568 fuzzy_candlesticks_10.update_raw(149.2, 151.9, 147.3, 151.5);
569 fuzzy_candlesticks_10.update_raw(151.6, 156.4, 151.0, 155.8);
570 fuzzy_candlesticks_10.update_raw(155.9, 157.2, 153.7, 154.3);
571
572 assert_eq!(fuzzy_candlesticks_10.value.direction, CandleDirection::Bear);
573 assert_eq!(fuzzy_candlesticks_10.value.size, CandleSize::VerySmall);
574 assert_eq!(fuzzy_candlesticks_10.value.body_size, CandleBodySize::Small);
575 assert_eq!(
576 fuzzy_candlesticks_10.value.upper_wick_size,
577 CandleWickSize::Large
578 );
579 assert_eq!(
580 fuzzy_candlesticks_10.value.lower_wick_size,
581 CandleWickSize::Small
582 );
583
584 let expected_vec = vec![-1, 1, 1, 3, 1];
585 assert_eq!(fuzzy_candlesticks_10.vector, expected_vec);
586 }
587
588 #[rstest]
589 fn test_reset(mut fuzzy_candlesticks_10: FuzzyCandlesticks) {
590 fuzzy_candlesticks_10.update_raw(151.6, 156.4, 151.0, 155.8);
591 fuzzy_candlesticks_10.reset();
592 assert_eq!(fuzzy_candlesticks_10.lengths.len(), 0);
593 assert_eq!(fuzzy_candlesticks_10.body_percents.len(), 0);
594 assert_eq!(fuzzy_candlesticks_10.upper_wick_percents.len(), 0);
595 assert_eq!(fuzzy_candlesticks_10.lower_wick_percents.len(), 0);
596 assert_eq!(fuzzy_candlesticks_10.value.direction, CandleDirection::None);
597 assert_eq!(fuzzy_candlesticks_10.value.size, CandleSize::None);
598 assert_eq!(fuzzy_candlesticks_10.value.body_size, CandleBodySize::None);
599 assert_eq!(
600 fuzzy_candlesticks_10.value.upper_wick_size,
601 CandleWickSize::None
602 );
603 assert_eq!(
604 fuzzy_candlesticks_10.value.lower_wick_size,
605 CandleWickSize::None
606 );
607 assert_eq!(fuzzy_candlesticks_10.vector.len(), 0);
608 assert_eq!(fuzzy_candlesticks_10.last_open, 0.0);
609 assert_eq!(fuzzy_candlesticks_10.last_low, 0.0);
610 assert_eq!(fuzzy_candlesticks_10.last_high, 0.0);
611 assert_eq!(fuzzy_candlesticks_10.last_close, 0.0);
612 assert!(!fuzzy_candlesticks_10.has_inputs);
613 assert!(!fuzzy_candlesticks_10.initialized);
614 }
615 #[rstest]
616 fn test_zero_length_candle(mut fuzzy_candlesticks_1: FuzzyCandlesticks) {
617 fuzzy_candlesticks_1.update_raw(100.0, 100.0, 100.0, 100.0); assert_eq!(fuzzy_candlesticks_1.value.size, CandleSize::None);
619 assert_eq!(fuzzy_candlesticks_1.value.body_size, CandleBodySize::None);
620 assert_eq!(
621 fuzzy_candlesticks_1.value.upper_wick_size,
622 CandleWickSize::None
623 );
624 assert_eq!(
625 fuzzy_candlesticks_1.value.lower_wick_size,
626 CandleWickSize::None
627 );
628 assert_eq!(fuzzy_candlesticks_1.value.direction, CandleDirection::None);
629 }
630
631 #[rstest]
632 fn test_constant_input_stddev_zero(mut fuzzy_candlesticks_1: FuzzyCandlesticks) {
633 for _ in 0..10 {
634 fuzzy_candlesticks_1.update_raw(100.0, 110.0, 90.0, 105.0);
635 }
636 assert!(fuzzy_candlesticks_1.lengths.iter().all(|&v| v == 20.0));
637 assert!(matches!(
638 fuzzy_candlesticks_1.value.size,
639 CandleSize::VerySmall | CandleSize::Small | CandleSize::Medium
640 ));
641 }
642
643 #[rstest]
644 fn test_nan_inf_safety(mut fuzzy_candlesticks_1: FuzzyCandlesticks) {
645 fuzzy_candlesticks_1.update_raw(f64::INFINITY, f64::INFINITY, f64::INFINITY, f64::INFINITY);
646 fuzzy_candlesticks_1.update_raw(f64::NAN, f64::NAN, f64::NAN, f64::NAN);
647 assert_eq!(fuzzy_candlesticks_1.value.direction, CandleDirection::None);
648 }
649
650 #[rstest]
651 fn test_direction_cases(mut fuzzy_candlesticks_1: FuzzyCandlesticks) {
652 fuzzy_candlesticks_1.update_raw(100.0, 105.0, 95.0, 110.0); assert_eq!(fuzzy_candlesticks_1.value.direction, CandleDirection::Bull);
654
655 fuzzy_candlesticks_1.update_raw(110.0, 115.0, 105.0, 100.0); assert_eq!(fuzzy_candlesticks_1.value.direction, CandleDirection::Bear);
657
658 fuzzy_candlesticks_1.update_raw(100.0, 110.0, 90.0, 100.0); assert_eq!(fuzzy_candlesticks_1.value.direction, CandleDirection::None);
660 }
661
662 #[rstest]
663 fn test_body_and_wick_percentages(mut fuzzy_candlesticks_1: FuzzyCandlesticks) {
664 let open: f64 = 100.0;
665 let close: f64 = 110.0;
666 let high: f64 = 120.0;
667 let low: f64 = 90.0;
668
669 let total = high - low; let expected_body = (close - open).abs() / total; let expected_upper_wick = (high - close.max(open)) / total; let expected_lower_wick = (open.min(close) - low) / total; fuzzy_candlesticks_1.update_raw(open, high, low, close);
675
676 let actual_body = fuzzy_candlesticks_1.body_percents[0];
677 let actual_upper = fuzzy_candlesticks_1.upper_wick_percents[0];
678 let actual_lower = fuzzy_candlesticks_1.lower_wick_percents[0];
679
680 assert!(
681 (actual_body - expected_body).abs() < 1e-6,
682 "Body percent mismatch"
683 );
684 assert!(
685 (actual_upper - expected_upper_wick).abs() < 1e-6,
686 "Upper wick percent mismatch"
687 );
688 assert!(
689 (actual_lower - expected_lower_wick).abs() < 1e-6,
690 "Lower wick percent mismatch"
691 );
692 }
693
694 #[rstest]
695 fn test_body_size_large(mut fuzzy_candlesticks_3: FuzzyCandlesticks) {
696 fuzzy_candlesticks_3.update_raw(100.0, 101.0, 99.0, 100.0);
698 fuzzy_candlesticks_3.update_raw(100.0, 102.0, 98.0, 100.5);
702 fuzzy_candlesticks_3.update_raw(101.0, 105.0, 100.0, 104.8);
706 assert_eq!(fuzzy_candlesticks_3.value.body_size, CandleBodySize::Trend);
712 }
713
714 #[rstest]
715 fn test_lower_wick_size_large(mut fuzzy_candlesticks_3: FuzzyCandlesticks) {
716 fuzzy_candlesticks_3.update_raw(100.0, 101.0, 100.0, 101.0);
718 fuzzy_candlesticks_3.update_raw(102.0, 103.0, 101.5, 102.5);
722 fuzzy_candlesticks_3.update_raw(110.0, 115.0, 100.0, 114.0);
729 assert_eq!(
736 fuzzy_candlesticks_3.value.lower_wick_size,
737 CandleWickSize::Large
738 );
739 }
740
741 #[rstest]
742 fn test_upper_wick_size_large(mut fuzzy_candlesticks_3: FuzzyCandlesticks) {
743 fuzzy_candlesticks_3.update_raw(100.0, 100.0, 99.0, 100.0);
745 fuzzy_candlesticks_3.update_raw(101.0, 102.0, 100.0, 101.5);
749 fuzzy_candlesticks_3.update_raw(105.0, 115.0, 104.0, 106.0);
755 assert_eq!(
762 fuzzy_candlesticks_3.value.upper_wick_size,
763 CandleWickSize::Large
764 );
765 }
766}