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