1use crate::utilities::data_loader::{source_type, Candles};
2use crate::utilities::enums::Kernel;
3use crate::utilities::helpers::{
4 alloc_with_nan_prefix, detect_best_batch_kernel, detect_best_kernel, init_matrix_prefixes,
5 make_uninit_matrix,
6};
7use aligned_vec::{AVec, CACHELINE_ALIGN};
8#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
9use core::arch::x86_64::*;
10#[cfg(not(target_arch = "wasm32"))]
11use rayon::prelude::*;
12use std::convert::AsRef;
13use thiserror::Error;
14
15#[cfg(feature = "python")]
16use crate::utilities::kernel_validation::validate_kernel;
17#[cfg(feature = "python")]
18use numpy::{IntoPyArray, PyArray1, PyArrayMethods, PyReadonlyArray1};
19#[cfg(feature = "python")]
20use pyo3::exceptions::PyValueError;
21#[cfg(feature = "python")]
22use pyo3::prelude::*;
23#[cfg(feature = "python")]
24use pyo3::types::PyDict;
25
26#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
27use serde::{Deserialize, Serialize};
28#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
29use wasm_bindgen::prelude::*;
30
31const N_LEVELS: usize = 9;
32
33#[inline(always)]
34fn first_valid_ohlc(high: &[f64], low: &[f64], close: &[f64]) -> Option<usize> {
35 let len = high.len();
36 for i in 0..len {
37 if !(high[i].is_nan() || low[i].is_nan() || close[i].is_nan()) {
38 return Some(i);
39 }
40 }
41 None
42}
43
44#[inline(always)]
45fn pivot_compute_into(
46 high: &[f64],
47 low: &[f64],
48 close: &[f64],
49 open: &[f64],
50 mode: usize,
51 first: usize,
52 k: Kernel,
53 r4: &mut [f64],
54 r3: &mut [f64],
55 r2: &mut [f64],
56 r1: &mut [f64],
57 pp: &mut [f64],
58 s1: &mut [f64],
59 s2: &mut [f64],
60 s3: &mut [f64],
61 s4: &mut [f64],
62) {
63 unsafe {
64 match k {
65 Kernel::Scalar | Kernel::ScalarBatch => pivot_scalar(
66 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
67 ),
68 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
69 Kernel::Avx2 | Kernel::Avx2Batch => pivot_avx2(
70 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
71 ),
72 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
73 Kernel::Avx512 | Kernel::Avx512Batch => pivot_avx512(
74 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
75 ),
76 _ => unreachable!(),
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
82pub enum PivotData<'a> {
83 Candles {
84 candles: &'a Candles,
85 },
86 Slices {
87 high: &'a [f64],
88 low: &'a [f64],
89 close: &'a [f64],
90 open: &'a [f64],
91 },
92}
93
94#[derive(Debug, Clone)]
95pub struct PivotParams {
96 pub mode: Option<usize>,
97}
98impl Default for PivotParams {
99 fn default() -> Self {
100 Self { mode: Some(3) }
101 }
102}
103
104#[derive(Debug, Clone)]
105pub struct PivotInput<'a> {
106 pub data: PivotData<'a>,
107 pub params: PivotParams,
108}
109impl<'a> PivotInput<'a> {
110 #[inline]
111 pub fn from_candles(candles: &'a Candles, params: PivotParams) -> Self {
112 Self {
113 data: PivotData::Candles { candles },
114 params,
115 }
116 }
117 #[inline]
118 pub fn from_slices(
119 high: &'a [f64],
120 low: &'a [f64],
121 close: &'a [f64],
122 open: &'a [f64],
123 params: PivotParams,
124 ) -> Self {
125 Self {
126 data: PivotData::Slices {
127 high,
128 low,
129 close,
130 open,
131 },
132 params,
133 }
134 }
135 #[inline]
136 pub fn with_default_candles(candles: &'a Candles) -> Self {
137 Self::from_candles(candles, PivotParams::default())
138 }
139 #[inline]
140 pub fn get_mode(&self) -> usize {
141 self.params
142 .mode
143 .unwrap_or_else(|| PivotParams::default().mode.unwrap())
144 }
145}
146impl<'a> AsRef<PivotData<'a>> for PivotInput<'a> {
147 fn as_ref(&self) -> &PivotData<'a> {
148 &self.data
149 }
150}
151
152#[derive(Debug, Clone)]
153pub struct PivotOutput {
154 pub r4: Vec<f64>,
155 pub r3: Vec<f64>,
156 pub r2: Vec<f64>,
157 pub r1: Vec<f64>,
158 pub pp: Vec<f64>,
159 pub s1: Vec<f64>,
160 pub s2: Vec<f64>,
161 pub s3: Vec<f64>,
162 pub s4: Vec<f64>,
163}
164
165#[derive(Debug, Error)]
166pub enum PivotError {
167 #[error("pivot: One or more required fields is empty.")]
168 EmptyData,
169 #[error("pivot: All values are NaN.")]
170 AllValuesNaN,
171 #[error("pivot: Not enough valid data after the first valid index.")]
172 NotEnoughValidData,
173 #[error("pivot: Output slice length mismatch (expected {expected}, got {got}).")]
174 OutputLengthMismatch { expected: usize, got: usize },
175 #[error("pivot: Invalid range: start={start}, end={end}, step={step}.")]
176 InvalidRange {
177 start: usize,
178 end: usize,
179 step: usize,
180 },
181 #[error("pivot: Invalid kernel for batch path: {0:?}.")]
182 InvalidKernelForBatch(Kernel),
183}
184
185#[derive(Copy, Clone, Debug)]
186pub struct PivotBuilder {
187 mode: Option<usize>,
188 kernel: Kernel,
189}
190impl Default for PivotBuilder {
191 fn default() -> Self {
192 Self {
193 mode: None,
194 kernel: Kernel::Auto,
195 }
196 }
197}
198impl PivotBuilder {
199 #[inline(always)]
200 pub fn new() -> Self {
201 Self::default()
202 }
203 #[inline(always)]
204 pub fn mode(mut self, mode: usize) -> Self {
205 self.mode = Some(mode);
206 self
207 }
208 #[inline(always)]
209 pub fn kernel(mut self, k: Kernel) -> Self {
210 self.kernel = k;
211 self
212 }
213 #[inline(always)]
214 pub fn apply(self, candles: &Candles) -> Result<PivotOutput, PivotError> {
215 let params = PivotParams { mode: self.mode };
216 let input = PivotInput::from_candles(candles, params);
217 pivot_with_kernel(&input, self.kernel)
218 }
219 #[inline(always)]
220 pub fn apply_slices(
221 self,
222 high: &[f64],
223 low: &[f64],
224 close: &[f64],
225 open: &[f64],
226 ) -> Result<PivotOutput, PivotError> {
227 let params = PivotParams { mode: self.mode };
228 let input = PivotInput::from_slices(high, low, close, open, params);
229 pivot_with_kernel(&input, self.kernel)
230 }
231}
232
233#[inline]
234pub fn pivot(input: &PivotInput) -> Result<PivotOutput, PivotError> {
235 pivot_with_kernel(input, Kernel::Auto)
236}
237
238pub fn pivot_with_kernel(input: &PivotInput, kernel: Kernel) -> Result<PivotOutput, PivotError> {
239 let (high, low, close, open) = match &input.data {
240 PivotData::Candles { candles } => {
241 let high = source_type(candles, "high");
242 let low = source_type(candles, "low");
243 let close = source_type(candles, "close");
244 let open = source_type(candles, "open");
245 (high, low, close, open)
246 }
247 PivotData::Slices {
248 high,
249 low,
250 close,
251 open,
252 } => (*high, *low, *close, *open),
253 };
254 let len = high.len();
255 if high.is_empty() || low.is_empty() || close.is_empty() {
256 return Err(PivotError::EmptyData);
257 }
258 if low.len() != len || close.len() != len || open.len() != len {
259 return Err(PivotError::EmptyData);
260 }
261 let mode = input.get_mode();
262
263 let mut first_valid_idx = None;
264 for i in 0..len {
265 let h = high[i];
266 let l = low[i];
267 let c = close[i];
268 if !(h.is_nan() || l.is_nan() || c.is_nan()) {
269 first_valid_idx = Some(i);
270 break;
271 }
272 }
273 let first_valid_idx = match first_valid_idx {
274 Some(idx) => idx,
275 None => return Err(PivotError::AllValuesNaN),
276 };
277 if first_valid_idx >= len {
278 return Err(PivotError::NotEnoughValidData);
279 }
280
281 let mut r4 = alloc_with_nan_prefix(len, first_valid_idx);
282 let mut r3 = alloc_with_nan_prefix(len, first_valid_idx);
283 let mut r2 = alloc_with_nan_prefix(len, first_valid_idx);
284 let mut r1 = alloc_with_nan_prefix(len, first_valid_idx);
285 let mut pp = alloc_with_nan_prefix(len, first_valid_idx);
286 let mut s1 = alloc_with_nan_prefix(len, first_valid_idx);
287 let mut s2 = alloc_with_nan_prefix(len, first_valid_idx);
288 let mut s3 = alloc_with_nan_prefix(len, first_valid_idx);
289 let mut s4 = alloc_with_nan_prefix(len, first_valid_idx);
290
291 let chosen = match kernel {
292 Kernel::Auto => detect_best_kernel(),
293 other => other,
294 };
295 pivot_compute_into(
296 high,
297 low,
298 close,
299 open,
300 mode,
301 first_valid_idx,
302 chosen,
303 &mut r4,
304 &mut r3,
305 &mut r2,
306 &mut r1,
307 &mut pp,
308 &mut s1,
309 &mut s2,
310 &mut s3,
311 &mut s4,
312 );
313 Ok(PivotOutput {
314 r4,
315 r3,
316 r2,
317 r1,
318 pp,
319 s1,
320 s2,
321 s3,
322 s4,
323 })
324}
325
326#[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
327#[inline]
328pub fn pivot_into(
329 input: &PivotInput,
330 r4: &mut [f64],
331 r3: &mut [f64],
332 r2: &mut [f64],
333 r1: &mut [f64],
334 pp: &mut [f64],
335 s1: &mut [f64],
336 s2: &mut [f64],
337 s3: &mut [f64],
338 s4: &mut [f64],
339) -> Result<(), PivotError> {
340 let (high, low, close, open) = match &input.data {
341 PivotData::Candles { candles } => {
342 let high = source_type(candles, "high");
343 let low = source_type(candles, "low");
344 let close = source_type(candles, "close");
345 let open = source_type(candles, "open");
346 (high, low, close, open)
347 }
348 PivotData::Slices {
349 high,
350 low,
351 close,
352 open,
353 } => (*high, *low, *close, *open),
354 };
355
356 let len = high.len();
357 if high.is_empty() || low.is_empty() || close.is_empty() {
358 return Err(PivotError::EmptyData);
359 }
360 if low.len() != len || close.len() != len || open.len() != len {
361 return Err(PivotError::EmptyData);
362 }
363 let expected = len;
364 let first_mismatch = [
365 r4.len(),
366 r3.len(),
367 r2.len(),
368 r1.len(),
369 pp.len(),
370 s1.len(),
371 s2.len(),
372 s3.len(),
373 s4.len(),
374 ]
375 .into_iter()
376 .find(|&got| got != expected);
377 if let Some(got) = first_mismatch {
378 return Err(PivotError::OutputLengthMismatch { expected, got });
379 }
380
381 let mode = input.get_mode();
382
383 let first_valid_idx = first_valid_ohlc(high, low, close).ok_or(PivotError::AllValuesNaN)?;
384 if first_valid_idx >= len {
385 return Err(PivotError::NotEnoughValidData);
386 }
387
388 let qnan = f64::from_bits(0x7ff8_0000_0000_0000);
389 for i in 0..first_valid_idx {
390 r4[i] = qnan;
391 r3[i] = qnan;
392 r2[i] = qnan;
393 r1[i] = qnan;
394 pp[i] = qnan;
395 s1[i] = qnan;
396 s2[i] = qnan;
397 s3[i] = qnan;
398 s4[i] = qnan;
399 }
400
401 let chosen = detect_best_kernel();
402 pivot_compute_into(
403 high,
404 low,
405 close,
406 open,
407 mode,
408 first_valid_idx,
409 chosen,
410 r4,
411 r3,
412 r2,
413 r1,
414 pp,
415 s1,
416 s2,
417 s3,
418 s4,
419 );
420
421 Ok(())
422}
423
424#[inline]
425pub fn pivot_into_slices(
426 r4: &mut [f64],
427 r3: &mut [f64],
428 r2: &mut [f64],
429 r1: &mut [f64],
430 pp: &mut [f64],
431 s1: &mut [f64],
432 s2: &mut [f64],
433 s3: &mut [f64],
434 s4: &mut [f64],
435 input: &PivotInput,
436 kern: Kernel,
437) -> Result<(), PivotError> {
438 let (high, low, close, open) = match &input.data {
439 PivotData::Candles { candles } => {
440 let high = source_type(candles, "high");
441 let low = source_type(candles, "low");
442 let close = source_type(candles, "close");
443 let open = source_type(candles, "open");
444 (high, low, close, open)
445 }
446 PivotData::Slices {
447 high,
448 low,
449 close,
450 open,
451 } => (*high, *low, *close, *open),
452 };
453
454 let len = high.len();
455 if high.is_empty() || low.is_empty() || close.is_empty() {
456 return Err(PivotError::EmptyData);
457 }
458 if low.len() != len || close.len() != len || open.len() != len {
459 return Err(PivotError::EmptyData);
460 }
461 let expected = len;
462 let first_mismatch = [
463 r4.len(),
464 r3.len(),
465 r2.len(),
466 r1.len(),
467 pp.len(),
468 s1.len(),
469 s2.len(),
470 s3.len(),
471 s4.len(),
472 ]
473 .into_iter()
474 .find(|&got| got != expected);
475 if let Some(got) = first_mismatch {
476 return Err(PivotError::OutputLengthMismatch { expected, got });
477 }
478
479 let mode = input.get_mode();
480
481 let mut first_valid_idx = None;
482 for i in 0..len {
483 let h = high[i];
484 let l = low[i];
485 let c = close[i];
486 if !(h.is_nan() || l.is_nan() || c.is_nan()) {
487 first_valid_idx = Some(i);
488 break;
489 }
490 }
491 let first_valid_idx = match first_valid_idx {
492 Some(idx) => idx,
493 None => return Err(PivotError::AllValuesNaN),
494 };
495 if first_valid_idx >= len {
496 return Err(PivotError::NotEnoughValidData);
497 }
498
499 let chosen = match kern {
500 Kernel::Auto => detect_best_kernel(),
501 other => other,
502 };
503
504 pivot_compute_into(
505 high,
506 low,
507 close,
508 open,
509 mode,
510 first_valid_idx,
511 chosen,
512 r4,
513 r3,
514 r2,
515 r1,
516 pp,
517 s1,
518 s2,
519 s3,
520 s4,
521 );
522
523 for i in 0..first_valid_idx {
524 r4[i] = f64::NAN;
525 r3[i] = f64::NAN;
526 r2[i] = f64::NAN;
527 r1[i] = f64::NAN;
528 pp[i] = f64::NAN;
529 s1[i] = f64::NAN;
530 s2[i] = f64::NAN;
531 s3[i] = f64::NAN;
532 s4[i] = f64::NAN;
533 }
534
535 Ok(())
536}
537
538#[inline]
539pub unsafe fn pivot_scalar(
540 high: &[f64],
541 low: &[f64],
542 close: &[f64],
543 open: &[f64],
544 mode: usize,
545 first: usize,
546 r4: &mut [f64],
547 r3: &mut [f64],
548 r2: &mut [f64],
549 r1: &mut [f64],
550 pp: &mut [f64],
551 s1: &mut [f64],
552 s2: &mut [f64],
553 s3: &mut [f64],
554 s4: &mut [f64],
555) {
556 let len = high.len();
557 if first >= len {
558 return;
559 }
560
561 let nan = f64::NAN;
562
563 match mode {
564 0 => {
565 for i in first..len {
566 let h = high[i];
567 let l = low[i];
568 let c = close[i];
569 if h.is_nan() || l.is_nan() || c.is_nan() {
570 r4[i] = nan;
571 r3[i] = nan;
572 r2[i] = nan;
573 r1[i] = nan;
574 pp[i] = nan;
575 s1[i] = nan;
576 s2[i] = nan;
577 s3[i] = nan;
578 s4[i] = nan;
579 continue;
580 }
581 let d = h - l;
582 let p = (h + l + c) * (1.0 / 3.0);
583 let t2 = p + p;
584 pp[i] = p;
585 r1[i] = t2 - l;
586 r2[i] = p + d;
587 s1[i] = t2 - h;
588 s2[i] = p - d;
589 r3[i] = nan;
590 r4[i] = nan;
591 s3[i] = nan;
592 s4[i] = nan;
593 }
594 }
595
596 1 => {
597 for i in first..len {
598 let h = high[i];
599 let l = low[i];
600 let c = close[i];
601 if h.is_nan() || l.is_nan() || c.is_nan() {
602 r4[i] = nan;
603 r3[i] = nan;
604 r2[i] = nan;
605 r1[i] = nan;
606 pp[i] = nan;
607 s1[i] = nan;
608 s2[i] = nan;
609 s3[i] = nan;
610 s4[i] = nan;
611 continue;
612 }
613 let d = h - l;
614 let p = (h + l + c) * (1.0 / 3.0);
615 let d38 = d * 0.382_f64;
616 let d62 = d * 0.618_f64;
617 pp[i] = p;
618 r1[i] = p + d38;
619 r2[i] = p + d62;
620 r3[i] = p + d;
621 s1[i] = p - d38;
622 s2[i] = p - d62;
623 s3[i] = p - d;
624 r4[i] = nan;
625 s4[i] = nan;
626 }
627 }
628
629 2 => {
630 for i in first..len {
631 let h = high[i];
632 let l = low[i];
633 let c = close[i];
634 let o = open[i];
635 if h.is_nan() || l.is_nan() || c.is_nan() {
636 r4[i] = nan;
637 r3[i] = nan;
638 r2[i] = nan;
639 r1[i] = nan;
640 pp[i] = nan;
641 s1[i] = nan;
642 s2[i] = nan;
643 s3[i] = nan;
644 s4[i] = nan;
645 continue;
646 }
647 let p = if c < o {
648 (h + (l + l) + c) * 0.25
649 } else if c > o {
650 ((h + h) + l + c) * 0.25
651 } else {
652 (h + l + (c + c)) * 0.25
653 };
654 pp[i] = p;
655 let num = if c < o {
656 (h + (l + l) + c) * 0.5
657 } else if c > o {
658 ((h + h) + l + c) * 0.5
659 } else {
660 (h + l + (c + c)) * 0.5
661 };
662 r1[i] = num - l;
663 s1[i] = num - h;
664 r2[i] = nan;
665 r3[i] = nan;
666 r4[i] = nan;
667 s2[i] = nan;
668 s3[i] = nan;
669 s4[i] = nan;
670 }
671 }
672
673 3 => {
674 const C1: f64 = 0.0916_f64;
675 const C2: f64 = 0.183_f64;
676 const C3: f64 = 0.275_f64;
677 const C4: f64 = 0.55_f64;
678 for i in first..len {
679 let h = high[i];
680 let l = low[i];
681 let c = close[i];
682 if h.is_nan() || l.is_nan() || c.is_nan() {
683 r4[i] = nan;
684 r3[i] = nan;
685 r2[i] = nan;
686 r1[i] = nan;
687 pp[i] = nan;
688 s1[i] = nan;
689 s2[i] = nan;
690 s3[i] = nan;
691 s4[i] = nan;
692 continue;
693 }
694 let d = h - l;
695 let p = (h + l + c) * (1.0 / 3.0);
696 pp[i] = p;
697 let d1 = d * C1;
698 let d2 = d * C2;
699 let d3 = d * C3;
700 let d4 = d * C4;
701 r1[i] = d1 + c;
702 r2[i] = d2 + c;
703 r3[i] = d3 + c;
704 r4[i] = d4 + c;
705 s1[i] = c - d1;
706 s2[i] = c - d2;
707 s3[i] = c - d3;
708 s4[i] = c - d4;
709 }
710 }
711
712 4 => {
713 for i in first..len {
714 let h = high[i];
715 let l = low[i];
716 let c = close[i];
717 let o = open[i];
718 if h.is_nan() || l.is_nan() || c.is_nan() {
719 r4[i] = nan;
720 r3[i] = nan;
721 r2[i] = nan;
722 r1[i] = nan;
723 pp[i] = nan;
724 s1[i] = nan;
725 s2[i] = nan;
726 s3[i] = nan;
727 s4[i] = nan;
728 continue;
729 }
730 let d = h - l;
731 let p = (h + l + (o + o)) * 0.25;
732 pp[i] = p;
733 let t2p = p + p;
734 let t2l = l + l;
735 let t2h = h + h;
736 let r3v = (t2p - t2l) + h;
737 r3[i] = r3v;
738 r4[i] = r3v + d;
739 r2[i] = p + d;
740 r1[i] = t2p - l;
741 s1[i] = t2p - h;
742 s2[i] = p - d;
743 let s3v = (l + t2p) - t2h;
744 s3[i] = s3v;
745 s4[i] = s3v - d;
746 }
747 }
748
749 _ => {}
750 }
751}
752
753#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
754#[inline]
755#[target_feature(enable = "avx2,fma")]
756pub unsafe fn pivot_avx2(
757 high: &[f64],
758 low: &[f64],
759 close: &[f64],
760 open: &[f64],
761 mode: usize,
762 first: usize,
763 r4: &mut [f64],
764 r3: &mut [f64],
765 r2: &mut [f64],
766 r1: &mut [f64],
767 pp: &mut [f64],
768 s1: &mut [f64],
769 s2: &mut [f64],
770 s3: &mut [f64],
771 s4: &mut [f64],
772) {
773 use core::arch::x86_64::*;
774
775 let len = high.len();
776 if first >= len {
777 return;
778 }
779
780 let hp = high.as_ptr();
781 let lp = low.as_ptr();
782 let cp = close.as_ptr();
783 let op = open.as_ptr();
784
785 let r4p = r4.as_mut_ptr();
786 let r3p = r3.as_mut_ptr();
787 let r2p = r2.as_mut_ptr();
788 let r1p = r1.as_mut_ptr();
789 let ppp = pp.as_mut_ptr();
790 let s1p = s1.as_mut_ptr();
791 let s2p = s2.as_mut_ptr();
792 let s3p = s3.as_mut_ptr();
793 let s4p = s4.as_mut_ptr();
794
795 let v_nan = _mm256_set1_pd(f64::NAN);
796 let v_third = _mm256_set1_pd(1.0 / 3.0);
797 let v_quart = _mm256_set1_pd(0.25);
798 let v_half = _mm256_set1_pd(0.5);
799 let v_one = _mm256_set1_pd(1.0);
800 let v_c0916 = _mm256_set1_pd(0.0916);
801 let v_c0183 = _mm256_set1_pd(0.183);
802 let v_c0275 = _mm256_set1_pd(0.275);
803 let v_c0550 = _mm256_set1_pd(0.55);
804 let v_c0382 = _mm256_set1_pd(0.382);
805 let v_c0618 = _mm256_set1_pd(0.618);
806 let v_neg1 = _mm256_set1_pd(-1.0);
807 let v_n0382 = _mm256_set1_pd(-0.382);
808 let v_n0618 = _mm256_set1_pd(-0.618);
809
810 let mut i = first;
811 let end4 = first + ((len - first) & !3);
812
813 #[inline(always)]
814 unsafe fn valid_mask_avx2(h: __m256d, l: __m256d, c: __m256d) -> __m256d {
815 let ord_h = _mm256_cmp_pd(h, h, _CMP_ORD_Q);
816 let ord_l = _mm256_cmp_pd(l, l, _CMP_ORD_Q);
817 let ord_c = _mm256_cmp_pd(c, c, _CMP_ORD_Q);
818 _mm256_and_pd(_mm256_and_pd(ord_h, ord_l), ord_c)
819 }
820
821 #[inline(always)]
822 unsafe fn blendv(a: __m256d, b: __m256d, mask: __m256d) -> __m256d {
823 _mm256_blendv_pd(a, b, mask)
824 }
825
826 match mode {
827 0 => {
828 while i < end4 {
829 let h = _mm256_loadu_pd(hp.add(i));
830 let l = _mm256_loadu_pd(lp.add(i));
831 let c = _mm256_loadu_pd(cp.add(i));
832 let vld = valid_mask_avx2(h, l, c);
833
834 let p = _mm256_mul_pd(_mm256_add_pd(_mm256_add_pd(h, l), c), v_third);
835 let d = _mm256_sub_pd(h, l);
836 let t2 = _mm256_add_pd(p, p);
837
838 let r1v = _mm256_sub_pd(t2, l);
839 let r2v = _mm256_fmadd_pd(d, v_one, p);
840 let s1v = _mm256_sub_pd(t2, h);
841 let s2v = _mm256_fmadd_pd(d, v_neg1, p);
842
843 _mm256_storeu_pd(ppp.add(i), blendv(v_nan, p, vld));
844 _mm256_storeu_pd(r1p.add(i), blendv(v_nan, r1v, vld));
845 _mm256_storeu_pd(r2p.add(i), blendv(v_nan, r2v, vld));
846 _mm256_storeu_pd(s1p.add(i), blendv(v_nan, s1v, vld));
847 _mm256_storeu_pd(s2p.add(i), blendv(v_nan, s2v, vld));
848 _mm256_storeu_pd(r3p.add(i), v_nan);
849 _mm256_storeu_pd(r4p.add(i), v_nan);
850 _mm256_storeu_pd(s3p.add(i), v_nan);
851 _mm256_storeu_pd(s4p.add(i), v_nan);
852
853 i += 4;
854 }
855 }
856 1 => {
857 while i < end4 {
858 let h = _mm256_loadu_pd(hp.add(i));
859 let l = _mm256_loadu_pd(lp.add(i));
860 let c = _mm256_loadu_pd(cp.add(i));
861 let vld = valid_mask_avx2(h, l, c);
862
863 let p = _mm256_mul_pd(_mm256_add_pd(_mm256_add_pd(h, l), c), v_third);
864 let d = _mm256_sub_pd(h, l);
865 let r1v = _mm256_fmadd_pd(d, v_c0382, p);
866 let r2v = _mm256_fmadd_pd(d, v_c0618, p);
867 let r3v = _mm256_fmadd_pd(d, v_one, p);
868 let s1v = _mm256_fmadd_pd(d, v_n0382, p);
869 let s2v = _mm256_fmadd_pd(d, v_n0618, p);
870 let s3v = _mm256_fmadd_pd(d, v_neg1, p);
871
872 _mm256_storeu_pd(ppp.add(i), blendv(v_nan, p, vld));
873 _mm256_storeu_pd(r1p.add(i), blendv(v_nan, r1v, vld));
874 _mm256_storeu_pd(r2p.add(i), blendv(v_nan, r2v, vld));
875 _mm256_storeu_pd(r3p.add(i), blendv(v_nan, r3v, vld));
876 _mm256_storeu_pd(s1p.add(i), blendv(v_nan, s1v, vld));
877 _mm256_storeu_pd(s2p.add(i), blendv(v_nan, s2v, vld));
878 _mm256_storeu_pd(s3p.add(i), blendv(v_nan, s3v, vld));
879 _mm256_storeu_pd(r4p.add(i), v_nan);
880 _mm256_storeu_pd(s4p.add(i), v_nan);
881
882 i += 4;
883 }
884 }
885 2 => {
886 while i < end4 {
887 let h = _mm256_loadu_pd(hp.add(i));
888 let l = _mm256_loadu_pd(lp.add(i));
889 let c = _mm256_loadu_pd(cp.add(i));
890 let o = _mm256_loadu_pd(op.add(i));
891 let vld = valid_mask_avx2(h, l, c);
892
893 let mlt = _mm256_cmp_pd(c, o, _CMP_LT_OQ);
894 let mgt = _mm256_cmp_pd(c, o, _CMP_GT_OQ);
895
896 let p_lt = _mm256_mul_pd(
897 _mm256_add_pd(_mm256_add_pd(h, _mm256_add_pd(l, l)), c),
898 v_quart,
899 );
900 let p_gt = _mm256_mul_pd(
901 _mm256_add_pd(_mm256_add_pd(_mm256_add_pd(h, h), l), c),
902 v_quart,
903 );
904 let p_eq = _mm256_mul_pd(
905 _mm256_add_pd(_mm256_add_pd(h, l), _mm256_add_pd(c, c)),
906 v_quart,
907 );
908
909 let mut p = blendv(p_eq, p_gt, mgt);
910 p = blendv(p, p_lt, mlt);
911 _mm256_storeu_pd(ppp.add(i), blendv(v_nan, p, vld));
912
913 let n_lt = _mm256_mul_pd(
914 _mm256_add_pd(_mm256_add_pd(h, _mm256_add_pd(l, l)), c),
915 v_half,
916 );
917 let n_gt = _mm256_mul_pd(
918 _mm256_add_pd(_mm256_add_pd(_mm256_add_pd(h, h), l), c),
919 v_half,
920 );
921 let n_eq = _mm256_mul_pd(
922 _mm256_add_pd(_mm256_add_pd(h, l), _mm256_add_pd(c, c)),
923 v_half,
924 );
925
926 let mut n = blendv(n_eq, n_gt, mgt);
927 n = blendv(n, n_lt, mlt);
928
929 let r1v = _mm256_sub_pd(n, l);
930 let s1v = _mm256_sub_pd(n, h);
931
932 _mm256_storeu_pd(r1p.add(i), blendv(v_nan, r1v, vld));
933 _mm256_storeu_pd(s1p.add(i), blendv(v_nan, s1v, vld));
934 _mm256_storeu_pd(r2p.add(i), v_nan);
935 _mm256_storeu_pd(r3p.add(i), v_nan);
936 _mm256_storeu_pd(r4p.add(i), v_nan);
937 _mm256_storeu_pd(s2p.add(i), v_nan);
938 _mm256_storeu_pd(s3p.add(i), v_nan);
939 _mm256_storeu_pd(s4p.add(i), v_nan);
940
941 i += 4;
942 }
943 }
944 3 => {
945 while i < end4 {
946 let h = _mm256_loadu_pd(hp.add(i));
947 let l = _mm256_loadu_pd(lp.add(i));
948 let c = _mm256_loadu_pd(cp.add(i));
949 let vld = valid_mask_avx2(h, l, c);
950
951 let p = _mm256_mul_pd(_mm256_add_pd(_mm256_add_pd(h, l), c), v_third);
952 _mm256_storeu_pd(ppp.add(i), blendv(v_nan, p, vld));
953
954 let d = _mm256_sub_pd(h, l);
955 let d1 = _mm256_mul_pd(d, v_c0916);
956 let d2 = _mm256_mul_pd(d, v_c0183);
957 let d3 = _mm256_mul_pd(d, v_c0275);
958 let d4 = _mm256_mul_pd(d, v_c0550);
959
960 let r1v = _mm256_fmadd_pd(d, v_c0916, c);
961 let r2v = _mm256_fmadd_pd(d, v_c0183, c);
962 let r3v = _mm256_fmadd_pd(d, v_c0275, c);
963 let r4v = _mm256_fmadd_pd(d, v_c0550, c);
964
965 let s1v = _mm256_fmadd_pd(d, _mm256_sub_pd(_mm256_setzero_pd(), v_c0916), c);
966 let s2v = _mm256_fmadd_pd(d, _mm256_sub_pd(_mm256_setzero_pd(), v_c0183), c);
967 let s3v = _mm256_fmadd_pd(d, _mm256_sub_pd(_mm256_setzero_pd(), v_c0275), c);
968 let s4v = _mm256_fmadd_pd(d, _mm256_sub_pd(_mm256_setzero_pd(), v_c0550), c);
969
970 _mm256_storeu_pd(r1p.add(i), blendv(v_nan, r1v, vld));
971 _mm256_storeu_pd(r2p.add(i), blendv(v_nan, r2v, vld));
972 _mm256_storeu_pd(r3p.add(i), blendv(v_nan, r3v, vld));
973 _mm256_storeu_pd(r4p.add(i), blendv(v_nan, r4v, vld));
974 _mm256_storeu_pd(s1p.add(i), blendv(v_nan, s1v, vld));
975 _mm256_storeu_pd(s2p.add(i), blendv(v_nan, s2v, vld));
976 _mm256_storeu_pd(s3p.add(i), blendv(v_nan, s3v, vld));
977 _mm256_storeu_pd(s4p.add(i), blendv(v_nan, s4v, vld));
978
979 i += 4;
980 }
981 }
982 4 => {
983 while i < end4 {
984 let h = _mm256_loadu_pd(hp.add(i));
985 let l = _mm256_loadu_pd(lp.add(i));
986 let c = _mm256_loadu_pd(cp.add(i));
987 let o = _mm256_loadu_pd(op.add(i));
988 let vld = valid_mask_avx2(h, l, c);
989
990 let p = _mm256_mul_pd(
991 _mm256_add_pd(_mm256_add_pd(h, l), _mm256_add_pd(o, o)),
992 v_quart,
993 );
994 let t2p = _mm256_add_pd(p, p);
995 let t2l = _mm256_add_pd(l, l);
996 let t2h = _mm256_add_pd(h, h);
997 let d = _mm256_sub_pd(h, l);
998
999 let r3v = _mm256_add_pd(_mm256_sub_pd(t2p, t2l), h);
1000 let r4v = _mm256_fmadd_pd(d, v_one, r3v);
1001 let r2v = _mm256_fmadd_pd(d, v_one, p);
1002 let r1v = _mm256_sub_pd(t2p, l);
1003
1004 let s1v = _mm256_sub_pd(t2p, h);
1005 let s2v = _mm256_fmadd_pd(d, v_neg1, p);
1006 let s3v = _mm256_sub_pd(_mm256_add_pd(l, t2p), t2h);
1007 let s4v = _mm256_fmadd_pd(d, v_neg1, s3v);
1008
1009 _mm256_storeu_pd(ppp.add(i), blendv(v_nan, p, vld));
1010 _mm256_storeu_pd(r1p.add(i), blendv(v_nan, r1v, vld));
1011 _mm256_storeu_pd(r2p.add(i), blendv(v_nan, r2v, vld));
1012 _mm256_storeu_pd(r3p.add(i), blendv(v_nan, r3v, vld));
1013 _mm256_storeu_pd(r4p.add(i), blendv(v_nan, r4v, vld));
1014 _mm256_storeu_pd(s1p.add(i), blendv(v_nan, s1v, vld));
1015 _mm256_storeu_pd(s2p.add(i), blendv(v_nan, s2v, vld));
1016 _mm256_storeu_pd(s3p.add(i), blendv(v_nan, s3v, vld));
1017 _mm256_storeu_pd(s4p.add(i), blendv(v_nan, s4v, vld));
1018
1019 i += 4;
1020 }
1021 }
1022 _ => {}
1023 }
1024
1025 if i < len {
1026 pivot_scalar(
1027 high, low, close, open, mode, i, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1028 );
1029 }
1030}
1031
1032#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1033#[inline]
1034pub unsafe fn pivot_avx512(
1035 high: &[f64],
1036 low: &[f64],
1037 close: &[f64],
1038 open: &[f64],
1039 mode: usize,
1040 first: usize,
1041 r4: &mut [f64],
1042 r3: &mut [f64],
1043 r2: &mut [f64],
1044 r1: &mut [f64],
1045 pp: &mut [f64],
1046 s1: &mut [f64],
1047 s2: &mut [f64],
1048 s3: &mut [f64],
1049 s4: &mut [f64],
1050) {
1051 if high.len() <= 32 {
1052 pivot_avx512_short(
1053 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1054 )
1055 } else {
1056 pivot_avx512_long(
1057 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1058 )
1059 }
1060}
1061
1062#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1063#[inline]
1064pub unsafe fn pivot_avx512_short(
1065 high: &[f64],
1066 low: &[f64],
1067 close: &[f64],
1068 open: &[f64],
1069 mode: usize,
1070 first: usize,
1071 r4: &mut [f64],
1072 r3: &mut [f64],
1073 r2: &mut [f64],
1074 r1: &mut [f64],
1075 pp: &mut [f64],
1076 s1: &mut [f64],
1077 s2: &mut [f64],
1078 s3: &mut [f64],
1079 s4: &mut [f64],
1080) {
1081 pivot_avx512_long(
1082 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1083 )
1084}
1085#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1086#[inline]
1087#[target_feature(enable = "avx512f,fma")]
1088pub unsafe fn pivot_avx512_long(
1089 high: &[f64],
1090 low: &[f64],
1091 close: &[f64],
1092 open: &[f64],
1093 mode: usize,
1094 first: usize,
1095 r4: &mut [f64],
1096 r3: &mut [f64],
1097 r2: &mut [f64],
1098 r1: &mut [f64],
1099 pp: &mut [f64],
1100 s1: &mut [f64],
1101 s2: &mut [f64],
1102 s3: &mut [f64],
1103 s4: &mut [f64],
1104) {
1105 use core::arch::x86_64::*;
1106
1107 let len = high.len();
1108 if first >= len {
1109 return;
1110 }
1111
1112 let hp = high.as_ptr();
1113 let lp = low.as_ptr();
1114 let cp = close.as_ptr();
1115 let op = open.as_ptr();
1116
1117 let r4p = r4.as_mut_ptr();
1118 let r3p = r3.as_mut_ptr();
1119 let r2p = r2.as_mut_ptr();
1120 let r1p = r1.as_mut_ptr();
1121 let ppp = pp.as_mut_ptr();
1122 let s1p = s1.as_mut_ptr();
1123 let s2p = s2.as_mut_ptr();
1124 let s3p = s3.as_mut_ptr();
1125 let s4p = s4.as_mut_ptr();
1126
1127 let v_nan = _mm512_set1_pd(f64::NAN);
1128 let v_third = _mm512_set1_pd(1.0 / 3.0);
1129 let v_quart = _mm512_set1_pd(0.25);
1130 let v_half = _mm512_set1_pd(0.5);
1131 let v_one = _mm512_set1_pd(1.0);
1132 let v_c0916 = _mm512_set1_pd(0.0916);
1133 let v_c0183 = _mm512_set1_pd(0.183);
1134 let v_c0275 = _mm512_set1_pd(0.275);
1135 let v_c0550 = _mm512_set1_pd(0.55);
1136 let v_c0382 = _mm512_set1_pd(0.382);
1137 let v_c0618 = _mm512_set1_pd(0.618);
1138 let v_neg1 = _mm512_set1_pd(-1.0);
1139 let v_n0382 = _mm512_set1_pd(-0.382);
1140 let v_n0618 = _mm512_set1_pd(-0.618);
1141
1142 let mut i = first;
1143 let step = 8;
1144
1145 #[inline(always)]
1146 unsafe fn valid_mask_avx512(h: __m512d, l: __m512d, c: __m512d) -> u8 {
1147 let mh = _mm512_cmp_pd_mask(h, h, _CMP_ORD_Q);
1148 let ml = _mm512_cmp_pd_mask(l, l, _CMP_ORD_Q);
1149 let mc = _mm512_cmp_pd_mask(c, c, _CMP_ORD_Q);
1150 mh & ml & mc
1151 }
1152
1153 match mode {
1154 0 => {
1155 while i + step <= len {
1156 let h = _mm512_loadu_pd(hp.add(i));
1157 let l = _mm512_loadu_pd(lp.add(i));
1158 let c = _mm512_loadu_pd(cp.add(i));
1159 let mk = valid_mask_avx512(h, l, c);
1160
1161 let p = _mm512_mul_pd(_mm512_add_pd(_mm512_add_pd(h, l), c), v_third);
1162 let d = _mm512_sub_pd(h, l);
1163 let t2 = _mm512_add_pd(p, p);
1164
1165 let r1v = _mm512_sub_pd(t2, l);
1166 let r2v = _mm512_fmadd_pd(d, v_one, p);
1167 let s1v = _mm512_sub_pd(t2, h);
1168 let s2v = _mm512_fmadd_pd(d, v_neg1, p);
1169
1170 _mm512_storeu_pd(ppp.add(i), _mm512_mask_blend_pd(mk, v_nan, p));
1171 _mm512_storeu_pd(r1p.add(i), _mm512_mask_blend_pd(mk, v_nan, r1v));
1172 _mm512_storeu_pd(r2p.add(i), _mm512_mask_blend_pd(mk, v_nan, r2v));
1173 _mm512_storeu_pd(s1p.add(i), _mm512_mask_blend_pd(mk, v_nan, s1v));
1174 _mm512_storeu_pd(s2p.add(i), _mm512_mask_blend_pd(mk, v_nan, s2v));
1175 _mm512_storeu_pd(r3p.add(i), v_nan);
1176 _mm512_storeu_pd(r4p.add(i), v_nan);
1177 _mm512_storeu_pd(s3p.add(i), v_nan);
1178 _mm512_storeu_pd(s4p.add(i), v_nan);
1179
1180 i += step;
1181 }
1182 }
1183 1 => {
1184 while i + step <= len {
1185 let h = _mm512_loadu_pd(hp.add(i));
1186 let l = _mm512_loadu_pd(lp.add(i));
1187 let c = _mm512_loadu_pd(cp.add(i));
1188 let mk = valid_mask_avx512(h, l, c);
1189
1190 let p = _mm512_mul_pd(_mm512_add_pd(_mm512_add_pd(h, l), c), v_third);
1191 let d = _mm512_sub_pd(h, l);
1192 let r1v = _mm512_fmadd_pd(d, v_c0382, p);
1193 let r2v = _mm512_fmadd_pd(d, v_c0618, p);
1194 let r3v = _mm512_fmadd_pd(d, v_one, p);
1195 let s1v = _mm512_fmadd_pd(d, v_n0382, p);
1196 let s2v = _mm512_fmadd_pd(d, v_n0618, p);
1197 let s3v = _mm512_fmadd_pd(d, v_neg1, p);
1198
1199 _mm512_storeu_pd(ppp.add(i), _mm512_mask_blend_pd(mk, v_nan, p));
1200 _mm512_storeu_pd(r1p.add(i), _mm512_mask_blend_pd(mk, v_nan, r1v));
1201 _mm512_storeu_pd(r2p.add(i), _mm512_mask_blend_pd(mk, v_nan, r2v));
1202 _mm512_storeu_pd(r3p.add(i), _mm512_mask_blend_pd(mk, v_nan, r3v));
1203 _mm512_storeu_pd(s1p.add(i), _mm512_mask_blend_pd(mk, v_nan, s1v));
1204 _mm512_storeu_pd(s2p.add(i), _mm512_mask_blend_pd(mk, v_nan, s2v));
1205 _mm512_storeu_pd(s3p.add(i), _mm512_mask_blend_pd(mk, v_nan, s3v));
1206 _mm512_storeu_pd(r4p.add(i), v_nan);
1207 _mm512_storeu_pd(s4p.add(i), v_nan);
1208
1209 i += step;
1210 }
1211 }
1212 2 => {
1213 while i + step <= len {
1214 let h = _mm512_loadu_pd(hp.add(i));
1215 let l = _mm512_loadu_pd(lp.add(i));
1216 let c = _mm512_loadu_pd(cp.add(i));
1217 let o = _mm512_loadu_pd(op.add(i));
1218 let mk = valid_mask_avx512(h, l, c);
1219
1220 let mlt = _mm512_cmp_pd_mask(c, o, _CMP_LT_OQ);
1221 let mgt = _mm512_cmp_pd_mask(c, o, _CMP_GT_OQ);
1222 let meq = (!mlt) & (!mgt);
1223
1224 let p_lt = _mm512_mul_pd(
1225 _mm512_add_pd(_mm512_add_pd(h, _mm512_add_pd(l, l)), c),
1226 v_quart,
1227 );
1228 let p_gt = _mm512_mul_pd(
1229 _mm512_add_pd(_mm512_add_pd(_mm512_add_pd(h, h), l), c),
1230 v_quart,
1231 );
1232 let p_eq = _mm512_mul_pd(
1233 _mm512_add_pd(_mm512_add_pd(h, l), _mm512_add_pd(c, c)),
1234 v_quart,
1235 );
1236
1237 let mut p = p_eq;
1238 p = _mm512_mask_blend_pd(mgt, p, p_gt);
1239 p = _mm512_mask_blend_pd(mlt, p, p_lt);
1240 _mm512_storeu_pd(ppp.add(i), _mm512_mask_blend_pd(mk, v_nan, p));
1241
1242 let n_lt = _mm512_mul_pd(
1243 _mm512_add_pd(_mm512_add_pd(h, _mm512_add_pd(l, l)), c),
1244 v_half,
1245 );
1246 let n_gt = _mm512_mul_pd(
1247 _mm512_add_pd(_mm512_add_pd(_mm512_add_pd(h, h), l), c),
1248 v_half,
1249 );
1250 let n_eq = _mm512_mul_pd(
1251 _mm512_add_pd(_mm512_add_pd(h, l), _mm512_add_pd(c, c)),
1252 v_half,
1253 );
1254
1255 let mut n = n_eq;
1256 n = _mm512_mask_blend_pd(mgt, n, n_gt);
1257 n = _mm512_mask_blend_pd(mlt, n, n_lt);
1258
1259 let r1v = _mm512_sub_pd(n, l);
1260 let s1v = _mm512_sub_pd(n, h);
1261
1262 _mm512_storeu_pd(r1p.add(i), _mm512_mask_blend_pd(mk, v_nan, r1v));
1263 _mm512_storeu_pd(s1p.add(i), _mm512_mask_blend_pd(mk, v_nan, s1v));
1264 _mm512_storeu_pd(r2p.add(i), v_nan);
1265 _mm512_storeu_pd(r3p.add(i), v_nan);
1266 _mm512_storeu_pd(r4p.add(i), v_nan);
1267 _mm512_storeu_pd(s2p.add(i), v_nan);
1268 _mm512_storeu_pd(s3p.add(i), v_nan);
1269 _mm512_storeu_pd(s4p.add(i), v_nan);
1270
1271 i += step;
1272 }
1273 }
1274 3 => {
1275 while i + step <= len {
1276 let h = _mm512_loadu_pd(hp.add(i));
1277 let l = _mm512_loadu_pd(lp.add(i));
1278 let c = _mm512_loadu_pd(cp.add(i));
1279 let mk = valid_mask_avx512(h, l, c);
1280
1281 let p = _mm512_mul_pd(_mm512_add_pd(_mm512_add_pd(h, l), c), v_third);
1282 _mm512_storeu_pd(ppp.add(i), _mm512_mask_blend_pd(mk, v_nan, p));
1283
1284 let d = _mm512_sub_pd(h, l);
1285 let d1 = _mm512_mul_pd(d, v_c0916);
1286 let d2 = _mm512_mul_pd(d, v_c0183);
1287 let d3 = _mm512_mul_pd(d, v_c0275);
1288 let d4 = _mm512_mul_pd(d, v_c0550);
1289
1290 let r1v = _mm512_fmadd_pd(d, v_c0916, c);
1291 let r2v = _mm512_fmadd_pd(d, v_c0183, c);
1292 let r3v = _mm512_fmadd_pd(d, v_c0275, c);
1293 let r4v = _mm512_fmadd_pd(d, v_c0550, c);
1294
1295 let s1v = _mm512_fmadd_pd(d, _mm512_sub_pd(_mm512_setzero_pd(), v_c0916), c);
1296 let s2v = _mm512_fmadd_pd(d, _mm512_sub_pd(_mm512_setzero_pd(), v_c0183), c);
1297 let s3v = _mm512_fmadd_pd(d, _mm512_sub_pd(_mm512_setzero_pd(), v_c0275), c);
1298 let s4v = _mm512_fmadd_pd(d, _mm512_sub_pd(_mm512_setzero_pd(), v_c0550), c);
1299
1300 _mm512_storeu_pd(r1p.add(i), _mm512_mask_blend_pd(mk, v_nan, r1v));
1301 _mm512_storeu_pd(r2p.add(i), _mm512_mask_blend_pd(mk, v_nan, r2v));
1302 _mm512_storeu_pd(r3p.add(i), _mm512_mask_blend_pd(mk, v_nan, r3v));
1303 _mm512_storeu_pd(r4p.add(i), _mm512_mask_blend_pd(mk, v_nan, r4v));
1304 _mm512_storeu_pd(s1p.add(i), _mm512_mask_blend_pd(mk, v_nan, s1v));
1305 _mm512_storeu_pd(s2p.add(i), _mm512_mask_blend_pd(mk, v_nan, s2v));
1306 _mm512_storeu_pd(s3p.add(i), _mm512_mask_blend_pd(mk, v_nan, s3v));
1307 _mm512_storeu_pd(s4p.add(i), _mm512_mask_blend_pd(mk, v_nan, s4v));
1308
1309 i += step;
1310 }
1311 }
1312 4 => {
1313 while i + step <= len {
1314 let h = _mm512_loadu_pd(hp.add(i));
1315 let l = _mm512_loadu_pd(lp.add(i));
1316 let c = _mm512_loadu_pd(cp.add(i));
1317 let o = _mm512_loadu_pd(op.add(i));
1318 let mk = valid_mask_avx512(h, l, c);
1319
1320 let p = _mm512_mul_pd(
1321 _mm512_add_pd(_mm512_add_pd(h, l), _mm512_add_pd(o, o)),
1322 v_quart,
1323 );
1324 let t2p = _mm512_add_pd(p, p);
1325 let t2l = _mm512_add_pd(l, l);
1326 let t2h = _mm512_add_pd(h, h);
1327 let d = _mm512_sub_pd(h, l);
1328
1329 let r3v = _mm512_add_pd(_mm512_sub_pd(t2p, t2l), h);
1330 let r4v = _mm512_fmadd_pd(d, v_one, r3v);
1331 let r2v = _mm512_fmadd_pd(d, v_one, p);
1332 let r1v = _mm512_sub_pd(t2p, l);
1333
1334 let s1v = _mm512_sub_pd(t2p, h);
1335 let s2v = _mm512_fmadd_pd(d, v_neg1, p);
1336 let s3v = _mm512_sub_pd(_mm512_add_pd(l, t2p), t2h);
1337 let s4v = _mm512_fmadd_pd(d, v_neg1, s3v);
1338
1339 _mm512_storeu_pd(ppp.add(i), _mm512_mask_blend_pd(mk, v_nan, p));
1340 _mm512_storeu_pd(r1p.add(i), _mm512_mask_blend_pd(mk, v_nan, r1v));
1341 _mm512_storeu_pd(r2p.add(i), _mm512_mask_blend_pd(mk, v_nan, r2v));
1342 _mm512_storeu_pd(r3p.add(i), _mm512_mask_blend_pd(mk, v_nan, r3v));
1343 _mm512_storeu_pd(r4p.add(i), _mm512_mask_blend_pd(mk, v_nan, r4v));
1344 _mm512_storeu_pd(s1p.add(i), _mm512_mask_blend_pd(mk, v_nan, s1v));
1345 _mm512_storeu_pd(s2p.add(i), _mm512_mask_blend_pd(mk, v_nan, s2v));
1346 _mm512_storeu_pd(s3p.add(i), _mm512_mask_blend_pd(mk, v_nan, s3v));
1347 _mm512_storeu_pd(s4p.add(i), _mm512_mask_blend_pd(mk, v_nan, s4v));
1348
1349 i += step;
1350 }
1351 }
1352 _ => {}
1353 }
1354
1355 if i < len {
1356 pivot_scalar(
1357 high, low, close, open, mode, i, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1358 );
1359 }
1360}
1361
1362#[inline(always)]
1363pub unsafe fn pivot_row_scalar(
1364 high: &[f64],
1365 low: &[f64],
1366 close: &[f64],
1367 open: &[f64],
1368 mode: usize,
1369 first: usize,
1370 r4: &mut [f64],
1371 r3: &mut [f64],
1372 r2: &mut [f64],
1373 r1: &mut [f64],
1374 pp: &mut [f64],
1375 s1: &mut [f64],
1376 s2: &mut [f64],
1377 s3: &mut [f64],
1378 s4: &mut [f64],
1379) {
1380 pivot_scalar(
1381 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1382 )
1383}
1384#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1385#[inline(always)]
1386pub unsafe fn pivot_row_avx2(
1387 high: &[f64],
1388 low: &[f64],
1389 close: &[f64],
1390 open: &[f64],
1391 mode: usize,
1392 first: usize,
1393 r4: &mut [f64],
1394 r3: &mut [f64],
1395 r2: &mut [f64],
1396 r1: &mut [f64],
1397 pp: &mut [f64],
1398 s1: &mut [f64],
1399 s2: &mut [f64],
1400 s3: &mut [f64],
1401 s4: &mut [f64],
1402) {
1403 pivot_avx2(
1404 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1405 )
1406}
1407#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1408#[inline(always)]
1409pub unsafe fn pivot_row_avx512(
1410 high: &[f64],
1411 low: &[f64],
1412 close: &[f64],
1413 open: &[f64],
1414 mode: usize,
1415 first: usize,
1416 r4: &mut [f64],
1417 r3: &mut [f64],
1418 r2: &mut [f64],
1419 r1: &mut [f64],
1420 pp: &mut [f64],
1421 s1: &mut [f64],
1422 s2: &mut [f64],
1423 s3: &mut [f64],
1424 s4: &mut [f64],
1425) {
1426 pivot_avx512(
1427 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1428 )
1429}
1430#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1431#[inline(always)]
1432pub unsafe fn pivot_row_avx512_short(
1433 high: &[f64],
1434 low: &[f64],
1435 close: &[f64],
1436 open: &[f64],
1437 mode: usize,
1438 first: usize,
1439 r4: &mut [f64],
1440 r3: &mut [f64],
1441 r2: &mut [f64],
1442 r1: &mut [f64],
1443 pp: &mut [f64],
1444 s1: &mut [f64],
1445 s2: &mut [f64],
1446 s3: &mut [f64],
1447 s4: &mut [f64],
1448) {
1449 pivot_avx512_short(
1450 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1451 )
1452}
1453#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1454#[inline(always)]
1455pub unsafe fn pivot_row_avx512_long(
1456 high: &[f64],
1457 low: &[f64],
1458 close: &[f64],
1459 open: &[f64],
1460 mode: usize,
1461 first: usize,
1462 r4: &mut [f64],
1463 r3: &mut [f64],
1464 r2: &mut [f64],
1465 r1: &mut [f64],
1466 pp: &mut [f64],
1467 s1: &mut [f64],
1468 s2: &mut [f64],
1469 s3: &mut [f64],
1470 s4: &mut [f64],
1471) {
1472 pivot_avx512_long(
1473 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1474 )
1475}
1476
1477#[inline(always)]
1478unsafe fn pivot_rows_scalar_into(
1479 high: &[f64],
1480 low: &[f64],
1481 close: &[f64],
1482 open: &[f64],
1483 mode: usize,
1484 first: usize,
1485 r4: &mut [f64],
1486 r3: &mut [f64],
1487 r2: &mut [f64],
1488 r1: &mut [f64],
1489 pp: &mut [f64],
1490 s1: &mut [f64],
1491 s2: &mut [f64],
1492 s3: &mut [f64],
1493 s4: &mut [f64],
1494) {
1495 pivot_scalar(
1496 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1497 )
1498}
1499
1500#[inline(always)]
1501fn pivot_batch_inner_into(
1502 high: &[f64],
1503 low: &[f64],
1504 close: &[f64],
1505 open: &[f64],
1506 sweep: &PivotBatchRange,
1507 kern: Kernel,
1508 parallel: bool,
1509 out: &mut [f64],
1510) -> Result<Vec<PivotParams>, PivotError> {
1511 let combos = expand_grid(sweep)?;
1512 if combos.is_empty() {
1513 let (start, end, step) = sweep.mode;
1514 return Err(PivotError::InvalidRange { start, end, step });
1515 }
1516 let cols = high.len();
1517 if cols == 0 || low.len() != cols || close.len() != cols || open.len() != cols {
1518 return Err(PivotError::EmptyData);
1519 }
1520
1521 let first = first_valid_ohlc(high, low, close).ok_or(PivotError::AllValuesNaN)?;
1522 if first >= cols {
1523 return Err(PivotError::NotEnoughValidData);
1524 }
1525
1526 let rows = combos
1527 .len()
1528 .checked_mul(N_LEVELS)
1529 .ok_or(PivotError::InvalidRange {
1530 start: combos.len(),
1531 end: N_LEVELS,
1532 step: 0,
1533 })?;
1534 let expected_len = rows.checked_mul(cols).ok_or(PivotError::InvalidRange {
1535 start: rows,
1536 end: cols,
1537 step: 0,
1538 })?;
1539 if out.len() != expected_len {
1540 return Err(PivotError::OutputLengthMismatch {
1541 expected: expected_len,
1542 got: out.len(),
1543 });
1544 }
1545
1546 let warm: Vec<usize> = vec![first; rows];
1547
1548 let out_mu = unsafe {
1549 let mu = std::slice::from_raw_parts_mut(
1550 out.as_mut_ptr() as *mut std::mem::MaybeUninit<f64>,
1551 out.len(),
1552 );
1553 init_matrix_prefixes(mu, cols, &warm);
1554 mu
1555 };
1556
1557 let chosen = match kern {
1558 Kernel::Auto => detect_best_batch_kernel(),
1559 k => k,
1560 };
1561
1562 if parallel {
1563 #[cfg(not(target_arch = "wasm32"))]
1564 {
1565 use rayon::prelude::*;
1566 use std::sync::atomic::{AtomicPtr, Ordering};
1567
1568 let out_ptr = AtomicPtr::new(out.as_mut_ptr());
1569 let out_len = out.len();
1570
1571 (0..combos.len()).into_par_iter().for_each(|ci| {
1572 let mode = combos[ci].mode.unwrap_or(3);
1573
1574 let base = ci * N_LEVELS * cols;
1575 unsafe {
1576 let ptr = out_ptr.load(Ordering::Relaxed);
1577 let mu = std::slice::from_raw_parts_mut(
1578 ptr as *mut std::mem::MaybeUninit<f64>,
1579 out_len,
1580 );
1581 let mut rows_mu = mu[base..base + N_LEVELS * cols].chunks_mut(cols);
1582 let mut cast = |mu: &mut [std::mem::MaybeUninit<f64>]| {
1583 std::slice::from_raw_parts_mut(mu.as_mut_ptr() as *mut f64, mu.len())
1584 };
1585 let r4_mu = rows_mu.next().unwrap();
1586 let r3_mu = rows_mu.next().unwrap();
1587 let r2_mu = rows_mu.next().unwrap();
1588 let r1_mu = rows_mu.next().unwrap();
1589 let pp_mu = rows_mu.next().unwrap();
1590 let s1_mu = rows_mu.next().unwrap();
1591 let s2_mu = rows_mu.next().unwrap();
1592 let s3_mu = rows_mu.next().unwrap();
1593 let s4_mu = rows_mu.next().unwrap();
1594
1595 let r4 = cast(r4_mu);
1596 let r3 = cast(r3_mu);
1597 let r2 = cast(r2_mu);
1598 let r1 = cast(r1_mu);
1599 let pp = cast(pp_mu);
1600 let s1 = cast(s1_mu);
1601 let s2 = cast(s2_mu);
1602 let s3 = cast(s3_mu);
1603 let s4 = cast(s4_mu);
1604
1605 match chosen {
1606 Kernel::Scalar | Kernel::ScalarBatch => pivot_rows_scalar_into(
1607 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1608 ),
1609 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1610 Kernel::Avx2 | Kernel::Avx2Batch => pivot_row_avx2(
1611 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1612 ),
1613 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1614 Kernel::Avx512 | Kernel::Avx512Batch => pivot_row_avx512(
1615 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1616 ),
1617 _ => unreachable!(),
1618 }
1619 }
1620 });
1621 }
1622 #[cfg(target_arch = "wasm32")]
1623 {
1624 let mut row_chunks = out_mu.chunks_mut(cols);
1625 for p in &combos {
1626 let mode = p.mode.unwrap_or(3);
1627 unsafe {
1628 let r4_mu = row_chunks.next().unwrap();
1629 let r3_mu = row_chunks.next().unwrap();
1630 let r2_mu = row_chunks.next().unwrap();
1631 let r1_mu = row_chunks.next().unwrap();
1632 let pp_mu = row_chunks.next().unwrap();
1633 let s1_mu = row_chunks.next().unwrap();
1634 let s2_mu = row_chunks.next().unwrap();
1635 let s3_mu = row_chunks.next().unwrap();
1636 let s4_mu = row_chunks.next().unwrap();
1637
1638 let mut cast = |mu: &mut [std::mem::MaybeUninit<f64>]| {
1639 std::slice::from_raw_parts_mut(mu.as_mut_ptr() as *mut f64, mu.len())
1640 };
1641 let (r4, r3, r2, r1, pp, s1, s2, s3, s4) = (
1642 cast(r4_mu),
1643 cast(r3_mu),
1644 cast(r2_mu),
1645 cast(r1_mu),
1646 cast(pp_mu),
1647 cast(s1_mu),
1648 cast(s2_mu),
1649 cast(s3_mu),
1650 cast(s4_mu),
1651 );
1652
1653 pivot_rows_scalar_into(
1654 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1655 );
1656 }
1657 }
1658 }
1659 } else {
1660 let mut row_chunks = out_mu.chunks_mut(cols);
1661 for p in &combos {
1662 let mode = p.mode.unwrap_or(3);
1663 unsafe {
1664 let r4_mu = row_chunks.next().unwrap();
1665 let r3_mu = row_chunks.next().unwrap();
1666 let r2_mu = row_chunks.next().unwrap();
1667 let r1_mu = row_chunks.next().unwrap();
1668 let pp_mu = row_chunks.next().unwrap();
1669 let s1_mu = row_chunks.next().unwrap();
1670 let s2_mu = row_chunks.next().unwrap();
1671 let s3_mu = row_chunks.next().unwrap();
1672 let s4_mu = row_chunks.next().unwrap();
1673
1674 let mut cast = |mu: &mut [std::mem::MaybeUninit<f64>]| {
1675 std::slice::from_raw_parts_mut(mu.as_mut_ptr() as *mut f64, mu.len())
1676 };
1677 let (r4, r3, r2, r1, pp, s1, s2, s3, s4) = (
1678 cast(r4_mu),
1679 cast(r3_mu),
1680 cast(r2_mu),
1681 cast(r1_mu),
1682 cast(pp_mu),
1683 cast(s1_mu),
1684 cast(s2_mu),
1685 cast(s3_mu),
1686 cast(s4_mu),
1687 );
1688
1689 pivot_rows_scalar_into(
1690 high, low, close, open, mode, first, r4, r3, r2, r1, pp, s1, s2, s3, s4,
1691 );
1692 }
1693 }
1694 }
1695 Ok(combos)
1696}
1697
1698#[derive(Clone, Debug)]
1699pub struct PivotBatchRange {
1700 pub mode: (usize, usize, usize),
1701}
1702impl Default for PivotBatchRange {
1703 fn default() -> Self {
1704 Self { mode: (3, 3, 1) }
1705 }
1706}
1707#[derive(Clone, Debug, Default)]
1708pub struct PivotBatchBuilder {
1709 range: PivotBatchRange,
1710 kernel: Kernel,
1711}
1712impl PivotBatchBuilder {
1713 pub fn new() -> Self {
1714 Self::default()
1715 }
1716 pub fn kernel(mut self, k: Kernel) -> Self {
1717 self.kernel = k;
1718 self
1719 }
1720 #[inline]
1721 pub fn mode_range(mut self, start: usize, end: usize, step: usize) -> Self {
1722 self.range.mode = (start, end, step);
1723 self
1724 }
1725 #[inline]
1726 pub fn mode_static(mut self, m: usize) -> Self {
1727 self.range.mode = (m, m, 1);
1728 self
1729 }
1730 pub fn apply_slice(
1731 self,
1732 high: &[f64],
1733 low: &[f64],
1734 close: &[f64],
1735 open: &[f64],
1736 ) -> Result<PivotBatchOutput, PivotError> {
1737 pivot_batch_with_kernel(high, low, close, open, &self.range, self.kernel)
1738 }
1739 pub fn apply_candles(self, candles: &Candles) -> Result<PivotBatchOutput, PivotError> {
1740 let high = source_type(candles, "high");
1741 let low = source_type(candles, "low");
1742 let close = source_type(candles, "close");
1743 let open = source_type(candles, "open");
1744 self.apply_slice(high, low, close, open)
1745 }
1746 pub fn with_default_candles(candles: &Candles) -> Result<PivotBatchOutput, PivotError> {
1747 PivotBatchBuilder::new()
1748 .kernel(Kernel::Auto)
1749 .apply_candles(candles)
1750 }
1751}
1752
1753pub fn pivot_batch_with_kernel(
1754 high: &[f64],
1755 low: &[f64],
1756 close: &[f64],
1757 open: &[f64],
1758 sweep: &PivotBatchRange,
1759 k: Kernel,
1760) -> Result<PivotBatchOutput, PivotError> {
1761 let kernel = match k {
1762 Kernel::Auto => detect_best_batch_kernel(),
1763 other if other.is_batch() => other,
1764 _ => return Err(PivotError::InvalidKernelForBatch(k)),
1765 };
1766 pivot_batch_inner(high, low, close, open, sweep, kernel)
1767}
1768
1769#[derive(Clone, Debug)]
1770pub struct PivotBatchOutput {
1771 pub levels: Vec<[Vec<f64>; 9]>,
1772 pub combos: Vec<PivotParams>,
1773 pub rows: usize,
1774 pub cols: usize,
1775}
1776
1777#[derive(Clone, Debug)]
1778pub struct PivotBatchFlatOutput {
1779 pub values: Vec<f64>,
1780 pub combos: Vec<PivotParams>,
1781 pub rows: usize,
1782 pub cols: usize,
1783}
1784
1785pub fn pivot_batch_flat_with_kernel(
1786 high: &[f64],
1787 low: &[f64],
1788 close: &[f64],
1789 open: &[f64],
1790 sweep: &PivotBatchRange,
1791 k: Kernel,
1792) -> Result<PivotBatchFlatOutput, PivotError> {
1793 let kernel = match k {
1794 Kernel::Auto => detect_best_batch_kernel(),
1795 other if other.is_batch() => other,
1796 _ => return Err(PivotError::InvalidKernelForBatch(k)),
1797 };
1798 let combos = expand_grid(sweep)?;
1799 if combos.is_empty() {
1800 let (start, end, step) = sweep.mode;
1801 return Err(PivotError::InvalidRange { start, end, step });
1802 }
1803 let cols = high.len();
1804 let rows = combos
1805 .len()
1806 .checked_mul(N_LEVELS)
1807 .ok_or(PivotError::InvalidRange {
1808 start: combos.len(),
1809 end: N_LEVELS,
1810 step: 0,
1811 })?;
1812 let _ = rows.checked_mul(cols).ok_or(PivotError::InvalidRange {
1813 start: rows,
1814 end: cols,
1815 step: 0,
1816 })?;
1817
1818 let mut buf_mu = make_uninit_matrix(rows, cols);
1819 let warm: Vec<usize> =
1820 vec![first_valid_ohlc(high, low, close).ok_or(PivotError::AllValuesNaN)?; rows];
1821 init_matrix_prefixes(&mut buf_mu, cols, &warm);
1822
1823 let mut guard = core::mem::ManuallyDrop::new(buf_mu);
1824 let out: &mut [f64] =
1825 unsafe { core::slice::from_raw_parts_mut(guard.as_mut_ptr() as *mut f64, guard.len()) };
1826
1827 pivot_batch_inner_into(high, low, close, open, sweep, kernel, true, out)?;
1828
1829 let values = unsafe {
1830 Vec::from_raw_parts(
1831 guard.as_mut_ptr() as *mut f64,
1832 guard.len(),
1833 guard.capacity(),
1834 )
1835 };
1836 Ok(PivotBatchFlatOutput {
1837 values,
1838 combos,
1839 rows,
1840 cols,
1841 })
1842}
1843
1844fn expand_grid(r: &PivotBatchRange) -> Result<Vec<PivotParams>, PivotError> {
1845 fn axis_usize((start, end, step): (usize, usize, usize)) -> Result<Vec<usize>, PivotError> {
1846 if step == 0 || start == end {
1847 return Ok(vec![start]);
1848 }
1849 let mut vals = Vec::new();
1850 if start < end {
1851 let mut cur = start;
1852 while cur <= end {
1853 vals.push(cur);
1854 cur = cur
1855 .checked_add(step)
1856 .ok_or(PivotError::InvalidRange { start, end, step })?;
1857 }
1858 } else {
1859 let mut cur = start;
1860 while cur >= end {
1861 vals.push(cur);
1862 cur = cur
1863 .checked_sub(step)
1864 .ok_or(PivotError::InvalidRange { start, end, step })?;
1865 if cur == 0 && end > 0 {
1866 break;
1867 }
1868 if let Some(&last) = vals.last() {
1869 if last == cur {
1870 break;
1871 }
1872 }
1873 }
1874 if let Some(&last) = vals.last() {
1875 if last < end {
1876 vals.pop();
1877 }
1878 }
1879 }
1880 if vals.is_empty() {
1881 return Err(PivotError::InvalidRange { start, end, step });
1882 }
1883 Ok(vals)
1884 }
1885
1886 let modes = axis_usize(r.mode)?;
1887 let mut v = Vec::with_capacity(modes.len());
1888 for m in modes {
1889 v.push(PivotParams { mode: Some(m) });
1890 }
1891 Ok(v)
1892}
1893fn pivot_batch_inner(
1894 high: &[f64],
1895 low: &[f64],
1896 close: &[f64],
1897 open: &[f64],
1898 sweep: &PivotBatchRange,
1899 kernel: Kernel,
1900) -> Result<PivotBatchOutput, PivotError> {
1901 let combos = expand_grid(sweep)?;
1902 if combos.is_empty() {
1903 let (start, end, step) = sweep.mode;
1904 return Err(PivotError::InvalidRange { start, end, step });
1905 }
1906 let len = high.len();
1907 let mut levels = Vec::with_capacity(combos.len());
1908 for p in &combos {
1909 let mode = p.mode.unwrap_or(3);
1910 let mut first = None;
1911 for i in 0..len {
1912 if !(high[i].is_nan() || low[i].is_nan() || close[i].is_nan()) {
1913 first = Some(i);
1914 break;
1915 }
1916 }
1917 let first = first.unwrap_or(len);
1918
1919 let mut r4 = alloc_with_nan_prefix(len, first);
1920 let mut r3 = alloc_with_nan_prefix(len, first);
1921 let mut r2 = alloc_with_nan_prefix(len, first);
1922 let mut r1 = alloc_with_nan_prefix(len, first);
1923 let mut pp = alloc_with_nan_prefix(len, first);
1924 let mut s1 = alloc_with_nan_prefix(len, first);
1925 let mut s2 = alloc_with_nan_prefix(len, first);
1926 let mut s3 = alloc_with_nan_prefix(len, first);
1927 let mut s4 = alloc_with_nan_prefix(len, first);
1928 unsafe {
1929 match kernel {
1930 Kernel::Scalar | Kernel::ScalarBatch => pivot_row_scalar(
1931 high, low, close, open, mode, first, &mut r4, &mut r3, &mut r2, &mut r1,
1932 &mut pp, &mut s1, &mut s2, &mut s3, &mut s4,
1933 ),
1934 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1935 Kernel::Avx2 | Kernel::Avx2Batch => pivot_row_avx2(
1936 high, low, close, open, mode, first, &mut r4, &mut r3, &mut r2, &mut r1,
1937 &mut pp, &mut s1, &mut s2, &mut s3, &mut s4,
1938 ),
1939 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1940 Kernel::Avx512 | Kernel::Avx512Batch => pivot_row_avx512(
1941 high, low, close, open, mode, first, &mut r4, &mut r3, &mut r2, &mut r1,
1942 &mut pp, &mut s1, &mut s2, &mut s3, &mut s4,
1943 ),
1944 _ => unreachable!(),
1945 }
1946 }
1947 levels.push([r4, r3, r2, r1, pp, s1, s2, s3, s4]);
1948 }
1949 let rows = combos.len();
1950 let cols = high.len();
1951 Ok(PivotBatchOutput {
1952 levels,
1953 combos,
1954 rows,
1955 cols,
1956 })
1957}
1958
1959pub struct PivotStream {
1960 mode: usize,
1961}
1962
1963impl PivotStream {
1964 pub fn new(mode: usize) -> Self {
1965 Self { mode }
1966 }
1967
1968 pub fn try_new(params: PivotParams) -> Result<Self, PivotError> {
1969 let mode = params.mode.unwrap_or(3);
1970 if mode > 4 {
1971 return Err(PivotError::EmptyData);
1972 }
1973 Ok(Self { mode })
1974 }
1975
1976 #[inline(always)]
1977 pub fn update(
1978 &mut self,
1979 high: f64,
1980 low: f64,
1981 close: f64,
1982 open: f64,
1983 ) -> Option<(f64, f64, f64, f64, f64, f64, f64, f64, f64)> {
1984 const C1: f64 = 0.0916;
1985 const C2: f64 = 0.183;
1986 const C3: f64 = 0.275;
1987 const C4: f64 = 0.55;
1988
1989 const INV3: f64 = 1.0 / 3.0;
1990 const INV4: f64 = 0.25;
1991 const INV2: f64 = 0.5;
1992
1993 match self.mode {
1994 0 => {
1995 if high.is_nan() || low.is_nan() || close.is_nan() {
1996 return None;
1997 }
1998 let d = high - low;
1999 let p = (high + low + close) * INV3;
2000 let t2 = p + p;
2001
2002 let r1 = t2 - low;
2003 let r2 = d.mul_add(1.0, p);
2004 let s1 = t2 - high;
2005 let s2 = (-d).mul_add(1.0, p);
2006
2007 Some((f64::NAN, f64::NAN, r2, r1, p, s1, s2, f64::NAN, f64::NAN))
2008 }
2009
2010 1 => {
2011 if high.is_nan() || low.is_nan() || close.is_nan() {
2012 return None;
2013 }
2014 let d = high - low;
2015 let p = (high + low + close) * INV3;
2016
2017 let r1 = d.mul_add(0.382, p);
2018 let r2 = d.mul_add(0.618, p);
2019 let r3 = d.mul_add(1.000, p);
2020 let s1 = d.mul_add(-0.382, p);
2021 let s2 = d.mul_add(-0.618, p);
2022 let s3 = d.mul_add(-1.000, p);
2023
2024 Some((f64::NAN, r3, r2, r1, p, s1, s2, s3, f64::NAN))
2025 }
2026
2027 2 => {
2028 if high.is_nan() || low.is_nan() || close.is_nan() || open.is_nan() {
2029 return None;
2030 }
2031
2032 let x_lt = high + low + low + close;
2033 let x_gt = high + high + low + close;
2034 let x_eq = high + low + close + close;
2035
2036 let lt: f64 = if close < open { 1.0 } else { 0.0 };
2037 let gt: f64 = if close > open { 1.0 } else { 0.0 };
2038 let eq: f64 = 1.0 - lt - gt;
2039
2040 let x = lt.mul_add(x_lt, gt.mul_add(x_gt, eq * x_eq));
2041 let pp = x * INV4;
2042 let half = x * INV2;
2043
2044 let r1 = half - low;
2045 let s1 = half - high;
2046
2047 Some((
2048 f64::NAN,
2049 f64::NAN,
2050 f64::NAN,
2051 r1,
2052 pp,
2053 s1,
2054 f64::NAN,
2055 f64::NAN,
2056 f64::NAN,
2057 ))
2058 }
2059
2060 3 => {
2061 if high.is_nan() || low.is_nan() || close.is_nan() {
2062 return None;
2063 }
2064 let d = high - low;
2065 let p = (high + low + close) * INV3;
2066
2067 let r1 = d.mul_add(C1, close);
2068 let r2 = d.mul_add(C2, close);
2069 let r3 = d.mul_add(C3, close);
2070 let r4 = d.mul_add(C4, close);
2071 let s1 = (-C1).mul_add(d, close);
2072 let s2 = (-C2).mul_add(d, close);
2073 let s3 = (-C3).mul_add(d, close);
2074 let s4 = (-C4).mul_add(d, close);
2075
2076 Some((r4, r3, r2, r1, p, s1, s2, s3, s4))
2077 }
2078
2079 4 => {
2080 if high.is_nan() || low.is_nan() || close.is_nan() || open.is_nan() {
2081 return None;
2082 }
2083 let d = high - low;
2084 let p = (high + low + open + open) * INV4;
2085 let t2 = p + p;
2086
2087 let r1 = t2 - low;
2088 let r2 = d.mul_add(1.0, p);
2089 let r3 = high + (p - low) * 2.0;
2090 let r4 = (high - low).mul_add(1.0, r3);
2091
2092 let s1 = t2 - high;
2093 let s2 = (-d).mul_add(1.0, p);
2094 let s3 = low - (high - p) * 2.0;
2095 let s4 = (-(high - low)).mul_add(1.0, s3);
2096
2097 Some((r4, r3, r2, r1, p, s1, s2, s3, s4))
2098 }
2099
2100 _ => None,
2101 }
2102 }
2103}
2104
2105#[cfg(feature = "python")]
2106#[pyfunction(name = "pivot")]
2107#[pyo3(signature = (high, low, close, open, mode=3, kernel=None))]
2108pub fn pivot_py<'py>(
2109 py: Python<'py>,
2110 high: PyReadonlyArray1<'py, f64>,
2111 low: PyReadonlyArray1<'py, f64>,
2112 close: PyReadonlyArray1<'py, f64>,
2113 open: PyReadonlyArray1<'py, f64>,
2114 mode: usize,
2115 kernel: Option<&str>,
2116) -> PyResult<(
2117 Bound<'py, PyArray1<f64>>,
2118 Bound<'py, PyArray1<f64>>,
2119 Bound<'py, PyArray1<f64>>,
2120 Bound<'py, PyArray1<f64>>,
2121 Bound<'py, PyArray1<f64>>,
2122 Bound<'py, PyArray1<f64>>,
2123 Bound<'py, PyArray1<f64>>,
2124 Bound<'py, PyArray1<f64>>,
2125 Bound<'py, PyArray1<f64>>,
2126)> {
2127 use numpy::{IntoPyArray, PyArrayMethods};
2128
2129 let high_slice = high.as_slice()?;
2130 let low_slice = low.as_slice()?;
2131 let close_slice = close.as_slice()?;
2132 let open_slice = open.as_slice()?;
2133
2134 let kern = validate_kernel(kernel, false)?;
2135
2136 let params = PivotParams { mode: Some(mode) };
2137 let input = PivotInput::from_slices(high_slice, low_slice, close_slice, open_slice, params);
2138
2139 let result = py
2140 .allow_threads(|| pivot_with_kernel(&input, kern))
2141 .map_err(|e| PyValueError::new_err(e.to_string()))?;
2142
2143 Ok((
2144 result.r4.into_pyarray(py),
2145 result.r3.into_pyarray(py),
2146 result.r2.into_pyarray(py),
2147 result.r1.into_pyarray(py),
2148 result.pp.into_pyarray(py),
2149 result.s1.into_pyarray(py),
2150 result.s2.into_pyarray(py),
2151 result.s3.into_pyarray(py),
2152 result.s4.into_pyarray(py),
2153 ))
2154}
2155
2156#[cfg(all(feature = "python", feature = "cuda"))]
2157use crate::cuda::pivot_wrapper::CudaPivot;
2158#[cfg(all(feature = "python", feature = "cuda"))]
2159use crate::indicators::moving_averages::alma::{make_device_array_py, DeviceArrayF32Py};
2160#[cfg(all(feature = "python", feature = "cuda"))]
2161#[pyfunction(name = "pivot_cuda_batch_dev")]
2162#[pyo3(signature = (high_f32, low_f32, close_f32, open_f32, mode_range, device_id=0))]
2163pub fn pivot_cuda_batch_dev_py<'py>(
2164 py: Python<'py>,
2165 high_f32: numpy::PyReadonlyArray1<'py, f32>,
2166 low_f32: numpy::PyReadonlyArray1<'py, f32>,
2167 close_f32: numpy::PyReadonlyArray1<'py, f32>,
2168 open_f32: numpy::PyReadonlyArray1<'py, f32>,
2169 mode_range: (usize, usize, usize),
2170 device_id: usize,
2171) -> PyResult<(DeviceArrayF32Py, Bound<'py, PyDict>)> {
2172 use crate::cuda::cuda_available;
2173 if !cuda_available() {
2174 return Err(PyValueError::new_err("CUDA not available"));
2175 }
2176 let h = high_f32.as_slice()?;
2177 let l = low_f32.as_slice()?;
2178 let c = close_f32.as_slice()?;
2179 let o = open_f32.as_slice()?;
2180 let sweep = PivotBatchRange { mode: mode_range };
2181 let (inner, combos) = py.allow_threads(|| {
2182 let cuda = CudaPivot::new(device_id).map_err(|e| PyValueError::new_err(e.to_string()))?;
2183 cuda.pivot_batch_dev(h, l, c, o, &sweep)
2184 .map_err(|e| PyValueError::new_err(e.to_string()))
2185 })?;
2186 let dict = PyDict::new(py);
2187 dict.set_item(
2188 "modes",
2189 combos
2190 .iter()
2191 .map(|p| p.mode.unwrap() as u64)
2192 .collect::<Vec<_>>()
2193 .into_pyarray(py),
2194 )?;
2195 let handle = make_device_array_py(device_id, inner)?;
2196 Ok((handle, dict))
2197}
2198
2199#[cfg(all(feature = "python", feature = "cuda"))]
2200#[pyfunction(name = "pivot_cuda_many_series_one_param_dev")]
2201#[pyo3(signature = (high_tm_f32, low_tm_f32, close_tm_f32, open_tm_f32, cols, rows, mode, device_id=0))]
2202pub fn pivot_cuda_many_series_one_param_dev_py(
2203 py: Python<'_>,
2204 high_tm_f32: numpy::PyReadonlyArray1<'_, f32>,
2205 low_tm_f32: numpy::PyReadonlyArray1<'_, f32>,
2206 close_tm_f32: numpy::PyReadonlyArray1<'_, f32>,
2207 open_tm_f32: numpy::PyReadonlyArray1<'_, f32>,
2208 cols: usize,
2209 rows: usize,
2210 mode: usize,
2211 device_id: usize,
2212) -> PyResult<DeviceArrayF32Py> {
2213 use crate::cuda::cuda_available;
2214 if !cuda_available() {
2215 return Err(PyValueError::new_err("CUDA not available"));
2216 }
2217 let h = high_tm_f32.as_slice()?;
2218 let l = low_tm_f32.as_slice()?;
2219 let c = close_tm_f32.as_slice()?;
2220 let o = open_tm_f32.as_slice()?;
2221 let inner = py.allow_threads(|| {
2222 let cuda = CudaPivot::new(device_id).map_err(|e| PyValueError::new_err(e.to_string()))?;
2223 cuda.pivot_many_series_one_param_time_major_dev(h, l, c, o, cols, rows, mode)
2224 .map_err(|e| PyValueError::new_err(e.to_string()))
2225 })?;
2226 let handle = make_device_array_py(device_id, inner)?;
2227 Ok(handle)
2228}
2229
2230#[cfg(feature = "python")]
2231#[pyclass(name = "PivotStream")]
2232pub struct PivotStreamPy {
2233 inner: PivotStream,
2234}
2235
2236#[cfg(feature = "python")]
2237#[pymethods]
2238impl PivotStreamPy {
2239 #[new]
2240 fn new(mode: Option<usize>) -> PyResult<Self> {
2241 let params = PivotParams { mode };
2242 let inner =
2243 PivotStream::try_new(params).map_err(|e| PyValueError::new_err(e.to_string()))?;
2244 Ok(PivotStreamPy { inner })
2245 }
2246
2247 fn update(
2248 &mut self,
2249 high: f64,
2250 low: f64,
2251 close: f64,
2252 open: f64,
2253 ) -> Option<(f64, f64, f64, f64, f64, f64, f64, f64, f64)> {
2254 self.inner.update(high, low, close, open)
2255 }
2256}
2257
2258#[cfg(feature = "python")]
2259#[pyfunction(name = "pivot_batch")]
2260#[pyo3(signature = (high, low, close, open, mode_range, kernel=None))]
2261pub fn pivot_batch_py<'py>(
2262 py: Python<'py>,
2263 high: PyReadonlyArray1<'py, f64>,
2264 low: PyReadonlyArray1<'py, f64>,
2265 close: PyReadonlyArray1<'py, f64>,
2266 open: PyReadonlyArray1<'py, f64>,
2267 mode_range: (usize, usize, usize),
2268 kernel: Option<&str>,
2269) -> PyResult<Bound<'py, PyDict>> {
2270 use numpy::{IntoPyArray, PyArray1, PyArrayMethods};
2271
2272 let (h, l, c, o) = (
2273 high.as_slice()?,
2274 low.as_slice()?,
2275 close.as_slice()?,
2276 open.as_slice()?,
2277 );
2278 let sweep = PivotBatchRange { mode: mode_range };
2279 let kern = validate_kernel(kernel, true)?;
2280
2281 let flat = py
2282 .allow_threads(|| pivot_batch_flat_with_kernel(h, l, c, o, &sweep, kern))
2283 .map_err(|e| PyValueError::new_err(e.to_string()))?;
2284
2285 let combos = flat.combos.len();
2286 let cols = flat.cols;
2287 let vals = flat.values;
2288
2289 let arr = unsafe { PyArray1::<f64>::new(py, [vals.len()], false) };
2290 unsafe {
2291 arr.as_slice_mut()?.copy_from_slice(&vals);
2292 }
2293
2294 let dict = PyDict::new(py);
2295 let names = ["r4", "r3", "r2", "r1", "pp", "s1", "s2", "s3", "s4"];
2296
2297 for (li, name) in names.iter().enumerate() {
2298 let level_arr = unsafe { PyArray1::<f64>::new(py, [combos * cols], false) };
2299 let level_slice = unsafe { level_arr.as_slice_mut()? };
2300
2301 for combo_idx in 0..combos {
2302 let base_idx = combo_idx * N_LEVELS * cols;
2303 let level_base = li * cols;
2304 for col_idx in 0..cols {
2305 level_slice[combo_idx * cols + col_idx] = vals[base_idx + level_base + col_idx];
2306 }
2307 }
2308
2309 dict.set_item(*name, level_arr.reshape((combos, cols))?)?;
2310 }
2311
2312 dict.set_item(
2313 "modes",
2314 flat.combos
2315 .iter()
2316 .map(|p| p.mode.unwrap() as u64)
2317 .collect::<Vec<_>>()
2318 .into_pyarray(py),
2319 )?;
2320 dict.set_item("rows_per_level", combos)?;
2321 dict.set_item("cols", cols)?;
2322 Ok(dict)
2323}
2324
2325#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2326#[wasm_bindgen]
2327pub fn pivot_js(
2328 high: &[f64],
2329 low: &[f64],
2330 close: &[f64],
2331 open: &[f64],
2332 mode: usize,
2333) -> Result<Vec<f64>, JsValue> {
2334 let len = high.len();
2335 if low.len() != len || close.len() != len || open.len() != len {
2336 return Err(JsValue::from_str(
2337 "pivot: Input arrays must have the same length",
2338 ));
2339 }
2340
2341 let params = PivotParams { mode: Some(mode) };
2342 let input = PivotInput::from_slices(high, low, close, open, params);
2343 let out =
2344 pivot_with_kernel(&input, Kernel::Auto).map_err(|e| JsValue::from_str(&e.to_string()))?;
2345 let cols = high.len();
2346 let mut values = Vec::with_capacity(N_LEVELS * cols);
2347 values.extend_from_slice(&out.r4);
2348 values.extend_from_slice(&out.r3);
2349 values.extend_from_slice(&out.r2);
2350 values.extend_from_slice(&out.r1);
2351 values.extend_from_slice(&out.pp);
2352 values.extend_from_slice(&out.s1);
2353 values.extend_from_slice(&out.s2);
2354 values.extend_from_slice(&out.s3);
2355 values.extend_from_slice(&out.s4);
2356 Ok(values)
2357}
2358
2359#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2360#[wasm_bindgen]
2361pub fn pivot_into(
2362 high_ptr: *const f64,
2363 low_ptr: *const f64,
2364 close_ptr: *const f64,
2365 open_ptr: *const f64,
2366 r4_ptr: *mut f64,
2367 r3_ptr: *mut f64,
2368 r2_ptr: *mut f64,
2369 r1_ptr: *mut f64,
2370 pp_ptr: *mut f64,
2371 s1_ptr: *mut f64,
2372 s2_ptr: *mut f64,
2373 s3_ptr: *mut f64,
2374 s4_ptr: *mut f64,
2375 len: usize,
2376 mode: usize,
2377) -> Result<(), JsValue> {
2378 if high_ptr.is_null() || low_ptr.is_null() || close_ptr.is_null() || open_ptr.is_null() {
2379 return Err(JsValue::from_str("Null input pointer provided"));
2380 }
2381
2382 if r4_ptr.is_null()
2383 || r3_ptr.is_null()
2384 || r2_ptr.is_null()
2385 || r1_ptr.is_null()
2386 || pp_ptr.is_null()
2387 || s1_ptr.is_null()
2388 || s2_ptr.is_null()
2389 || s3_ptr.is_null()
2390 || s4_ptr.is_null()
2391 {
2392 return Err(JsValue::from_str("Null output pointer provided"));
2393 }
2394
2395 unsafe {
2396 let high = std::slice::from_raw_parts(high_ptr, len);
2397 let low = std::slice::from_raw_parts(low_ptr, len);
2398 let close = std::slice::from_raw_parts(close_ptr, len);
2399 let open = std::slice::from_raw_parts(open_ptr, len);
2400
2401 let params = PivotParams { mode: Some(mode) };
2402 let input = PivotInput::from_slices(high, low, close, open, params);
2403
2404 let input_ptrs = [
2405 high_ptr as *const u8,
2406 low_ptr as *const u8,
2407 close_ptr as *const u8,
2408 open_ptr as *const u8,
2409 ];
2410 let output_ptrs = [
2411 r4_ptr as *const u8,
2412 r3_ptr as *const u8,
2413 r2_ptr as *const u8,
2414 r1_ptr as *const u8,
2415 pp_ptr as *const u8,
2416 s1_ptr as *const u8,
2417 s2_ptr as *const u8,
2418 s3_ptr as *const u8,
2419 s4_ptr as *const u8,
2420 ];
2421
2422 let has_aliasing = input_ptrs
2423 .iter()
2424 .any(|&inp| output_ptrs.iter().any(|&out| inp == out));
2425
2426 if has_aliasing {
2427 let mut temp = vec![0.0; len * 9];
2428
2429 let (r4_temp, rest) = temp.split_at_mut(len);
2430 let (r3_temp, rest) = rest.split_at_mut(len);
2431 let (r2_temp, rest) = rest.split_at_mut(len);
2432 let (r1_temp, rest) = rest.split_at_mut(len);
2433 let (pp_temp, rest) = rest.split_at_mut(len);
2434 let (s1_temp, rest) = rest.split_at_mut(len);
2435 let (s2_temp, rest) = rest.split_at_mut(len);
2436 let (s3_temp, s4_temp) = rest.split_at_mut(len);
2437
2438 pivot_into_slices(
2439 r4_temp,
2440 r3_temp,
2441 r2_temp,
2442 r1_temp,
2443 pp_temp,
2444 s1_temp,
2445 s2_temp,
2446 s3_temp,
2447 s4_temp,
2448 &input,
2449 Kernel::Auto,
2450 )
2451 .map_err(|e| JsValue::from_str(&e.to_string()))?;
2452
2453 let r4_out = std::slice::from_raw_parts_mut(r4_ptr, len);
2454 let r3_out = std::slice::from_raw_parts_mut(r3_ptr, len);
2455 let r2_out = std::slice::from_raw_parts_mut(r2_ptr, len);
2456 let r1_out = std::slice::from_raw_parts_mut(r1_ptr, len);
2457 let pp_out = std::slice::from_raw_parts_mut(pp_ptr, len);
2458 let s1_out = std::slice::from_raw_parts_mut(s1_ptr, len);
2459 let s2_out = std::slice::from_raw_parts_mut(s2_ptr, len);
2460 let s3_out = std::slice::from_raw_parts_mut(s3_ptr, len);
2461 let s4_out = std::slice::from_raw_parts_mut(s4_ptr, len);
2462
2463 r4_out.copy_from_slice(r4_temp);
2464 r3_out.copy_from_slice(r3_temp);
2465 r2_out.copy_from_slice(r2_temp);
2466 r1_out.copy_from_slice(r1_temp);
2467 pp_out.copy_from_slice(pp_temp);
2468 s1_out.copy_from_slice(s1_temp);
2469 s2_out.copy_from_slice(s2_temp);
2470 s3_out.copy_from_slice(s3_temp);
2471 s4_out.copy_from_slice(s4_temp);
2472 } else {
2473 let r4_out = std::slice::from_raw_parts_mut(r4_ptr, len);
2474 let r3_out = std::slice::from_raw_parts_mut(r3_ptr, len);
2475 let r2_out = std::slice::from_raw_parts_mut(r2_ptr, len);
2476 let r1_out = std::slice::from_raw_parts_mut(r1_ptr, len);
2477 let pp_out = std::slice::from_raw_parts_mut(pp_ptr, len);
2478 let s1_out = std::slice::from_raw_parts_mut(s1_ptr, len);
2479 let s2_out = std::slice::from_raw_parts_mut(s2_ptr, len);
2480 let s3_out = std::slice::from_raw_parts_mut(s3_ptr, len);
2481 let s4_out = std::slice::from_raw_parts_mut(s4_ptr, len);
2482
2483 pivot_into_slices(
2484 r4_out,
2485 r3_out,
2486 r2_out,
2487 r1_out,
2488 pp_out,
2489 s1_out,
2490 s2_out,
2491 s3_out,
2492 s4_out,
2493 &input,
2494 Kernel::Auto,
2495 )
2496 .map_err(|e| JsValue::from_str(&e.to_string()))?;
2497 }
2498
2499 Ok(())
2500 }
2501}
2502
2503#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2504#[wasm_bindgen]
2505pub fn pivot_alloc(len: usize) -> *mut f64 {
2506 let mut vec = Vec::<f64>::with_capacity(len);
2507 let ptr = vec.as_mut_ptr();
2508 std::mem::forget(vec);
2509 ptr
2510}
2511
2512#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2513#[wasm_bindgen]
2514pub fn pivot_free(ptr: *mut f64, len: usize) {
2515 if !ptr.is_null() {
2516 unsafe {
2517 let _ = Vec::from_raw_parts(ptr, len, len);
2518 }
2519 }
2520}
2521
2522#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2523#[derive(Serialize, Deserialize)]
2524pub struct PivotBatchConfig {
2525 pub mode_range: (usize, usize, usize),
2526}
2527
2528#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2529#[derive(Serialize, Deserialize)]
2530pub struct PivotBatchFlatJsOutput {
2531 pub values: Vec<f64>,
2532 pub modes: Vec<usize>,
2533 pub rows: usize,
2534 pub cols: usize,
2535}
2536
2537#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2538#[wasm_bindgen(js_name = pivot_batch)]
2539pub fn pivot_batch_js(
2540 high: &[f64],
2541 low: &[f64],
2542 close: &[f64],
2543 open: &[f64],
2544 config: JsValue,
2545) -> Result<JsValue, JsValue> {
2546 let cfg: PivotBatchConfig =
2547 serde_wasm_bindgen::from_value(config).map_err(|e| JsValue::from_str(&e.to_string()))?;
2548 let sweep = PivotBatchRange {
2549 mode: cfg.mode_range,
2550 };
2551 let flat = pivot_batch_flat_with_kernel(high, low, close, open, &sweep, Kernel::Auto)
2552 .map_err(|e| JsValue::from_str(&e.to_string()))?;
2553 let modes = flat.combos.iter().map(|p| p.mode.unwrap()).collect();
2554 let out = PivotBatchFlatJsOutput {
2555 values: flat.values,
2556 modes,
2557 rows: flat.rows,
2558 cols: flat.cols,
2559 };
2560 serde_wasm_bindgen::to_value(&out).map_err(|e| JsValue::from_str(&e.to_string()))
2561}
2562
2563#[cfg(test)]
2564mod tests {
2565 use super::*;
2566 use crate::skip_if_unsupported;
2567 use crate::utilities::data_loader::read_candles_from_csv;
2568 use crate::utilities::enums::Kernel;
2569 use paste::paste;
2570
2571 fn check_pivot_default_mode_camarilla(
2572 test_name: &str,
2573 kernel: Kernel,
2574 ) -> Result<(), Box<dyn std::error::Error>> {
2575 skip_if_unsupported!(kernel, test_name);
2576 let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2577 let candles = read_candles_from_csv(file_path)?;
2578
2579 let params = PivotParams { mode: None };
2580 let input = PivotInput::from_candles(&candles, params);
2581 let result = pivot_with_kernel(&input, kernel)?;
2582
2583 assert_eq!(result.r4.len(), candles.close.len());
2584 assert_eq!(result.r3.len(), candles.close.len());
2585 assert_eq!(result.r2.len(), candles.close.len());
2586 assert_eq!(result.r1.len(), candles.close.len());
2587 assert_eq!(result.pp.len(), candles.close.len());
2588 assert_eq!(result.s1.len(), candles.close.len());
2589 assert_eq!(result.s2.len(), candles.close.len());
2590 assert_eq!(result.s3.len(), candles.close.len());
2591 assert_eq!(result.s4.len(), candles.close.len());
2592
2593 let last_five_r4 = &result.r4[result.r4.len().saturating_sub(5)..];
2594 let expected_r4 = [59466.5, 59357.55, 59243.6, 59334.85, 59170.35];
2595 for (i, &val) in last_five_r4.iter().enumerate() {
2596 let exp = expected_r4[i];
2597 assert!(
2598 (val - exp).abs() < 1e-1,
2599 "Camarilla r4 mismatch at index {}, expected {}, got {}",
2600 i,
2601 exp,
2602 val
2603 );
2604 }
2605 Ok(())
2606 }
2607
2608 fn check_pivot_nan_values(
2609 test_name: &str,
2610 kernel: Kernel,
2611 ) -> Result<(), Box<dyn std::error::Error>> {
2612 skip_if_unsupported!(kernel, test_name);
2613 let high = [10.0, f64::NAN, 30.0];
2614 let low = [9.0, 8.5, f64::NAN];
2615 let close = [9.5, 9.0, 29.0];
2616 let open = [9.1, 8.8, 28.5];
2617
2618 let params = PivotParams { mode: Some(3) };
2619 let input = PivotInput::from_slices(&high, &low, &close, &open, params);
2620 let result = pivot_with_kernel(&input, kernel)?;
2621 assert_eq!(result.pp.len(), high.len());
2622 Ok(())
2623 }
2624
2625 fn check_pivot_no_data(
2626 test_name: &str,
2627 kernel: Kernel,
2628 ) -> Result<(), Box<dyn std::error::Error>> {
2629 skip_if_unsupported!(kernel, test_name);
2630 let high: [f64; 0] = [];
2631 let low: [f64; 0] = [];
2632 let close: [f64; 0] = [];
2633 let open: [f64; 0] = [];
2634 let params = PivotParams { mode: Some(3) };
2635 let input = PivotInput::from_slices(&high, &low, &close, &open, params);
2636 let result = pivot_with_kernel(&input, kernel);
2637 assert!(result.is_err());
2638 if let Err(e) = result {
2639 assert!(
2640 e.to_string().contains("One or more required fields"),
2641 "Expected 'EmptyData' error, got: {}",
2642 e
2643 );
2644 }
2645 Ok(())
2646 }
2647
2648 fn check_pivot_all_nan(
2649 test_name: &str,
2650 kernel: Kernel,
2651 ) -> Result<(), Box<dyn std::error::Error>> {
2652 skip_if_unsupported!(kernel, test_name);
2653 let high = [f64::NAN, f64::NAN];
2654 let low = [f64::NAN, f64::NAN];
2655 let close = [f64::NAN, f64::NAN];
2656 let open = [f64::NAN, f64::NAN];
2657 let params = PivotParams { mode: Some(3) };
2658 let input = PivotInput::from_slices(&high, &low, &close, &open, params);
2659 let result = pivot_with_kernel(&input, kernel);
2660 assert!(result.is_err());
2661 if let Err(e) = result {
2662 assert!(
2663 e.to_string().contains("All values are NaN"),
2664 "Expected 'AllValuesNaN' error, got: {}",
2665 e
2666 );
2667 }
2668 Ok(())
2669 }
2670
2671 fn check_pivot_fibonacci_mode(
2672 test_name: &str,
2673 kernel: Kernel,
2674 ) -> Result<(), Box<dyn std::error::Error>> {
2675 skip_if_unsupported!(kernel, test_name);
2676 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2677 let candles = read_candles_from_csv(file)?;
2678 let params = PivotParams { mode: Some(1) };
2679 let input = PivotInput::from_candles(&candles, params);
2680 let output = pivot_with_kernel(&input, kernel)?;
2681 assert_eq!(output.r3.len(), candles.close.len());
2682 Ok(())
2683 }
2684
2685 fn check_pivot_standard_mode(
2686 test_name: &str,
2687 kernel: Kernel,
2688 ) -> Result<(), Box<dyn std::error::Error>> {
2689 skip_if_unsupported!(kernel, test_name);
2690 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2691 let candles = read_candles_from_csv(file)?;
2692 let params = PivotParams { mode: Some(0) };
2693 let input = PivotInput::from_candles(&candles, params);
2694 let output = pivot_with_kernel(&input, kernel)?;
2695 assert_eq!(output.r2.len(), candles.close.len());
2696 Ok(())
2697 }
2698
2699 fn check_pivot_demark_mode(
2700 test_name: &str,
2701 kernel: Kernel,
2702 ) -> Result<(), Box<dyn std::error::Error>> {
2703 skip_if_unsupported!(kernel, test_name);
2704 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2705 let candles = read_candles_from_csv(file)?;
2706 let params = PivotParams { mode: Some(2) };
2707 let input = PivotInput::from_candles(&candles, params);
2708 let output = pivot_with_kernel(&input, kernel)?;
2709 assert_eq!(output.r1.len(), candles.close.len());
2710 Ok(())
2711 }
2712
2713 fn check_pivot_woodie_mode(
2714 test_name: &str,
2715 kernel: Kernel,
2716 ) -> Result<(), Box<dyn std::error::Error>> {
2717 skip_if_unsupported!(kernel, test_name);
2718 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2719 let candles = read_candles_from_csv(file)?;
2720 let params = PivotParams { mode: Some(4) };
2721 let input = PivotInput::from_candles(&candles, params);
2722 let output = pivot_with_kernel(&input, kernel)?;
2723 assert_eq!(output.r4.len(), candles.close.len());
2724 Ok(())
2725 }
2726
2727 #[cfg(feature = "proptest")]
2728 #[allow(clippy::float_cmp)]
2729 fn check_pivot_property(
2730 test_name: &str,
2731 kernel: Kernel,
2732 ) -> Result<(), Box<dyn std::error::Error>> {
2733 use proptest::prelude::*;
2734 skip_if_unsupported!(kernel, test_name);
2735
2736 let strat = (10usize..=200).prop_flat_map(|len| {
2737 prop_oneof![
2738 prop::collection::vec(
2739 (100f64..10000f64).prop_filter("finite", |x| x.is_finite()),
2740 len,
2741 )
2742 .prop_flat_map(move |base_prices| {
2743 let ohlc_strat = prop::collection::vec(
2744 (0f64..1f64, 0f64..1f64, 0f64..1f64, 0f64..1f64),
2745 len,
2746 );
2747
2748 (ohlc_strat, 0usize..=4).prop_map(move |(factors, mode)| {
2749 let mut high_data = Vec::with_capacity(len);
2750 let mut low_data = Vec::with_capacity(len);
2751 let mut close_data = Vec::with_capacity(len);
2752 let mut open_data = Vec::with_capacity(len);
2753
2754 for (i, base) in base_prices.iter().enumerate() {
2755 let (high_factor, low_factor, close_factor, open_factor) = factors[i];
2756
2757 let range = base * 0.1;
2758 let low = base - range * low_factor;
2759 let high = base + range * high_factor;
2760 let open = low + (high - low) * open_factor;
2761 let close = low + (high - low) * close_factor;
2762
2763 high_data.push(high);
2764 low_data.push(low);
2765 open_data.push(open);
2766 close_data.push(close);
2767 }
2768
2769 (high_data, low_data, close_data, open_data, mode)
2770 })
2771 }),
2772 (100f64..1000f64, 0usize..=4).prop_map(move |(price, mode)| {
2773 let data = vec![price; len];
2774 (data.clone(), data.clone(), data.clone(), data, mode)
2775 }),
2776 (100f64..1000f64, 0usize..=4).prop_map(move |(base, mode)| {
2777 let mut high_data = Vec::with_capacity(len);
2778 let mut low_data = Vec::with_capacity(len);
2779 let mut close_data = Vec::with_capacity(len);
2780 let mut open_data = Vec::with_capacity(len);
2781
2782 for _ in 0..len {
2783 let epsilon = 1e-10;
2784 let low = base;
2785 let high = base + epsilon;
2786 let open = base + epsilon * 0.3;
2787 let close = base + epsilon * 0.7;
2788
2789 high_data.push(high);
2790 low_data.push(low);
2791 open_data.push(open);
2792 close_data.push(close);
2793 }
2794
2795 (high_data, low_data, close_data, open_data, mode)
2796 }),
2797 ]
2798 });
2799
2800 proptest::test_runner::TestRunner::default().run(
2801 &strat,
2802 |(high, low, close, open, mode)| {
2803 let params = PivotParams { mode: Some(mode) };
2804 let input = PivotInput::from_slices(&high, &low, &close, &open, params);
2805
2806 let output = pivot_with_kernel(&input, kernel)?;
2807 let ref_output = pivot_with_kernel(&input, Kernel::Scalar)?;
2808
2809 prop_assert_eq!(output.pp.len(), high.len());
2810 prop_assert_eq!(output.r1.len(), high.len());
2811 prop_assert_eq!(output.s1.len(), high.len());
2812
2813 for i in 0..high.len() {
2814 let h = high[i];
2815 let l = low[i];
2816 let c = close[i];
2817 let o = open[i];
2818
2819 if h.is_nan() || l.is_nan() || c.is_nan() || o.is_nan() {
2820 continue;
2821 }
2822
2823 let pp = output.pp[i];
2824 let r4 = output.r4[i];
2825 let r3 = output.r3[i];
2826 let r2 = output.r2[i];
2827 let r1 = output.r1[i];
2828 let s1 = output.s1[i];
2829 let s2 = output.s2[i];
2830 let s3 = output.s3[i];
2831 let s4 = output.s4[i];
2832
2833 let tolerance = 1e-9;
2834 let range = h - l;
2835
2836 match mode {
2837 0 => {
2838 let expected_pp = (h + l + c) / 3.0;
2839 prop_assert!(
2840 (pp - expected_pp).abs() < tolerance,
2841 "Standard PP at {}: {} vs {}",
2842 i,
2843 pp,
2844 expected_pp
2845 );
2846
2847 let expected_r1 = 2.0 * pp - l;
2848 prop_assert!(
2849 (r1 - expected_r1).abs() < tolerance,
2850 "Standard R1 at {}: {} vs {}",
2851 i,
2852 r1,
2853 expected_r1
2854 );
2855
2856 let expected_r2 = pp + range;
2857 prop_assert!(
2858 (r2 - expected_r2).abs() < tolerance,
2859 "Standard R2 at {}: {} vs {}",
2860 i,
2861 r2,
2862 expected_r2
2863 );
2864
2865 let expected_s1 = 2.0 * pp - h;
2866 prop_assert!(
2867 (s1 - expected_s1).abs() < tolerance,
2868 "Standard S1 at {}: {} vs {}",
2869 i,
2870 s1,
2871 expected_s1
2872 );
2873
2874 let expected_s2 = pp - range;
2875 prop_assert!(
2876 (s2 - expected_s2).abs() < tolerance,
2877 "Standard S2 at {}: {} vs {}",
2878 i,
2879 s2,
2880 expected_s2
2881 );
2882
2883 prop_assert!(r3.is_nan(), "Standard R3 should be NaN at {}", i);
2884 prop_assert!(r4.is_nan(), "Standard R4 should be NaN at {}", i);
2885 prop_assert!(s3.is_nan(), "Standard S3 should be NaN at {}", i);
2886 prop_assert!(s4.is_nan(), "Standard S4 should be NaN at {}", i);
2887
2888 prop_assert!(s2 <= s1 + tolerance, "S2 > S1 at {}", i);
2889 prop_assert!(s1 <= pp + tolerance, "S1 > PP at {}", i);
2890 prop_assert!(pp <= r1 + tolerance, "PP > R1 at {}", i);
2891 prop_assert!(r1 <= r2 + tolerance, "R1 > R2 at {}", i);
2892 }
2893 1 => {
2894 let expected_pp = (h + l + c) / 3.0;
2895 prop_assert!(
2896 (pp - expected_pp).abs() < tolerance,
2897 "Fibonacci PP at {}: {} vs {}",
2898 i,
2899 pp,
2900 expected_pp
2901 );
2902
2903 let expected_r1 = pp + 0.382 * range;
2904 let expected_r2 = pp + 0.618 * range;
2905 let expected_r3 = pp + 1.0 * range;
2906 let expected_s1 = pp - 0.382 * range;
2907 let expected_s2 = pp - 0.618 * range;
2908 let expected_s3 = pp - 1.0 * range;
2909
2910 prop_assert!(
2911 (r1 - expected_r1).abs() < tolerance,
2912 "Fibonacci R1 at {}: {} vs {}",
2913 i,
2914 r1,
2915 expected_r1
2916 );
2917 prop_assert!(
2918 (r2 - expected_r2).abs() < tolerance,
2919 "Fibonacci R2 at {}: {} vs {}",
2920 i,
2921 r2,
2922 expected_r2
2923 );
2924 prop_assert!(
2925 (r3 - expected_r3).abs() < tolerance,
2926 "Fibonacci R3 at {}: {} vs {}",
2927 i,
2928 r3,
2929 expected_r3
2930 );
2931 prop_assert!(
2932 (s1 - expected_s1).abs() < tolerance,
2933 "Fibonacci S1 at {}: {} vs {}",
2934 i,
2935 s1,
2936 expected_s1
2937 );
2938 prop_assert!(
2939 (s2 - expected_s2).abs() < tolerance,
2940 "Fibonacci S2 at {}: {} vs {}",
2941 i,
2942 s2,
2943 expected_s2
2944 );
2945 prop_assert!(
2946 (s3 - expected_s3).abs() < tolerance,
2947 "Fibonacci S3 at {}: {} vs {}",
2948 i,
2949 s3,
2950 expected_s3
2951 );
2952
2953 prop_assert!(r4.is_nan(), "Fibonacci R4 should be NaN at {}", i);
2954 prop_assert!(s4.is_nan(), "Fibonacci S4 should be NaN at {}", i);
2955
2956 prop_assert!(s3 <= s2 + tolerance, "S3 > S2 at {}", i);
2957 prop_assert!(s2 <= s1 + tolerance, "S2 > S1 at {}", i);
2958 prop_assert!(s1 <= pp + tolerance, "S1 > PP at {}", i);
2959 prop_assert!(pp <= r1 + tolerance, "PP > R1 at {}", i);
2960 prop_assert!(r1 <= r2 + tolerance, "R1 > R2 at {}", i);
2961 prop_assert!(r2 <= r3 + tolerance, "R2 > R3 at {}", i);
2962 }
2963 2 => {
2964 let expected_pp = if c < o {
2965 (h + 2.0 * l + c) / 4.0
2966 } else if c > o {
2967 (2.0 * h + l + c) / 4.0
2968 } else {
2969 (h + l + 2.0 * c) / 4.0
2970 };
2971 prop_assert!(
2972 (pp - expected_pp).abs() < tolerance,
2973 "Demark PP at {}: {} vs {}",
2974 i,
2975 pp,
2976 expected_pp
2977 );
2978
2979 let expected_r1 = if c < o {
2980 (h + 2.0 * l + c) / 2.0 - l
2981 } else if c > o {
2982 (2.0 * h + l + c) / 2.0 - l
2983 } else {
2984 (h + l + 2.0 * c) / 2.0 - l
2985 };
2986 let expected_s1 = if c < o {
2987 (h + 2.0 * l + c) / 2.0 - h
2988 } else if c > o {
2989 (2.0 * h + l + c) / 2.0 - h
2990 } else {
2991 (h + l + 2.0 * c) / 2.0 - h
2992 };
2993
2994 prop_assert!(
2995 (r1 - expected_r1).abs() < tolerance,
2996 "Demark R1 at {}: {} vs {}",
2997 i,
2998 r1,
2999 expected_r1
3000 );
3001 prop_assert!(
3002 (s1 - expected_s1).abs() < tolerance,
3003 "Demark S1 at {}: {} vs {}",
3004 i,
3005 s1,
3006 expected_s1
3007 );
3008
3009 prop_assert!(r2.is_nan(), "Demark R2 should be NaN at {}", i);
3010 prop_assert!(r3.is_nan(), "Demark R3 should be NaN at {}", i);
3011 prop_assert!(r4.is_nan(), "Demark R4 should be NaN at {}", i);
3012 prop_assert!(s2.is_nan(), "Demark S2 should be NaN at {}", i);
3013 prop_assert!(s3.is_nan(), "Demark S3 should be NaN at {}", i);
3014 prop_assert!(s4.is_nan(), "Demark S4 should be NaN at {}", i);
3015 }
3016 3 => {
3017 let expected_pp = (h + l + c) / 3.0;
3018 prop_assert!(
3019 (pp - expected_pp).abs() < tolerance,
3020 "Camarilla PP at {}: {} vs {}",
3021 i,
3022 pp,
3023 expected_pp
3024 );
3025
3026 let expected_r4 = 0.55 * range + c;
3027 let expected_r3 = 0.275 * range + c;
3028 let expected_r2 = 0.183 * range + c;
3029 let expected_r1 = 0.0916 * range + c;
3030 let expected_s1 = c - 0.0916 * range;
3031 let expected_s2 = c - 0.183 * range;
3032 let expected_s3 = c - 0.275 * range;
3033 let expected_s4 = c - 0.55 * range;
3034
3035 prop_assert!(
3036 (r4 - expected_r4).abs() < tolerance,
3037 "Camarilla R4 at {}: {} vs {}",
3038 i,
3039 r4,
3040 expected_r4
3041 );
3042 prop_assert!(
3043 (r3 - expected_r3).abs() < tolerance,
3044 "Camarilla R3 at {}: {} vs {}",
3045 i,
3046 r3,
3047 expected_r3
3048 );
3049 prop_assert!(
3050 (r2 - expected_r2).abs() < tolerance,
3051 "Camarilla R2 at {}: {} vs {}",
3052 i,
3053 r2,
3054 expected_r2
3055 );
3056 prop_assert!(
3057 (r1 - expected_r1).abs() < tolerance,
3058 "Camarilla R1 at {}: {} vs {}",
3059 i,
3060 r1,
3061 expected_r1
3062 );
3063 prop_assert!(
3064 (s1 - expected_s1).abs() < tolerance,
3065 "Camarilla S1 at {}: {} vs {}",
3066 i,
3067 s1,
3068 expected_s1
3069 );
3070 prop_assert!(
3071 (s2 - expected_s2).abs() < tolerance,
3072 "Camarilla S2 at {}: {} vs {}",
3073 i,
3074 s2,
3075 expected_s2
3076 );
3077 prop_assert!(
3078 (s3 - expected_s3).abs() < tolerance,
3079 "Camarilla S3 at {}: {} vs {}",
3080 i,
3081 s3,
3082 expected_s3
3083 );
3084 prop_assert!(
3085 (s4 - expected_s4).abs() < tolerance,
3086 "Camarilla S4 at {}: {} vs {}",
3087 i,
3088 s4,
3089 expected_s4
3090 );
3091
3092 prop_assert!(s4 <= s3 + tolerance, "S4 > S3 at {}", i);
3093 prop_assert!(s3 <= s2 + tolerance, "S3 > S2 at {}", i);
3094 prop_assert!(s2 <= s1 + tolerance, "S2 > S1 at {}", i);
3095 prop_assert!(r1 <= r2 + tolerance, "R1 > R2 at {}", i);
3096 prop_assert!(r2 <= r3 + tolerance, "R2 > R3 at {}", i);
3097 prop_assert!(r3 <= r4 + tolerance, "R3 > R4 at {}", i);
3098 }
3099 4 => {
3100 let expected_pp = (h + l + 2.0 * o) / 4.0;
3101 prop_assert!(
3102 (pp - expected_pp).abs() < tolerance,
3103 "Woodie PP at {}: {} vs {}",
3104 i,
3105 pp,
3106 expected_pp
3107 );
3108
3109 let expected_r1 = 2.0 * pp - l;
3110 let expected_r2 = pp + range;
3111 let expected_r3 = h + 2.0 * (pp - l);
3112 let expected_r4 = expected_r3 + range;
3113 let expected_s1 = 2.0 * pp - h;
3114 let expected_s2 = pp - range;
3115 let expected_s3 = l - 2.0 * (h - pp);
3116 let expected_s4 = expected_s3 - range;
3117
3118 prop_assert!(
3119 (r1 - expected_r1).abs() < tolerance,
3120 "Woodie R1 at {}: {} vs {}",
3121 i,
3122 r1,
3123 expected_r1
3124 );
3125 prop_assert!(
3126 (r2 - expected_r2).abs() < tolerance,
3127 "Woodie R2 at {}: {} vs {}",
3128 i,
3129 r2,
3130 expected_r2
3131 );
3132 prop_assert!(
3133 (r3 - expected_r3).abs() < tolerance,
3134 "Woodie R3 at {}: {} vs {}",
3135 i,
3136 r3,
3137 expected_r3
3138 );
3139 prop_assert!(
3140 (r4 - expected_r4).abs() < tolerance,
3141 "Woodie R4 at {}: {} vs {}",
3142 i,
3143 r4,
3144 expected_r4
3145 );
3146 prop_assert!(
3147 (s1 - expected_s1).abs() < tolerance,
3148 "Woodie S1 at {}: {} vs {}",
3149 i,
3150 s1,
3151 expected_s1
3152 );
3153 prop_assert!(
3154 (s2 - expected_s2).abs() < tolerance,
3155 "Woodie S2 at {}: {} vs {}",
3156 i,
3157 s2,
3158 expected_s2
3159 );
3160 prop_assert!(
3161 (s3 - expected_s3).abs() < tolerance,
3162 "Woodie S3 at {}: {} vs {}",
3163 i,
3164 s3,
3165 expected_s3
3166 );
3167 prop_assert!(
3168 (s4 - expected_s4).abs() < tolerance,
3169 "Woodie S4 at {}: {} vs {}",
3170 i,
3171 s4,
3172 expected_s4
3173 );
3174
3175 prop_assert!(s4 <= s3 + tolerance, "S4 > S3 at {}", i);
3176 prop_assert!(s3 <= s2 + tolerance, "S3 > S2 at {}", i);
3177 prop_assert!(s2 <= s1 + tolerance, "S2 > S1 at {}", i);
3178 prop_assert!(r1 <= r2 + tolerance, "R1 > R2 at {}", i);
3179 prop_assert!(r2 <= r3 + tolerance, "R2 > R3 at {}", i);
3180 prop_assert!(r3 <= r4 + tolerance, "R3 > R4 at {}", i);
3181 }
3182 _ => {}
3183 }
3184
3185 prop_assert!(
3186 (pp - ref_output.pp[i]).abs() < tolerance,
3187 "PP kernel mismatch at {}",
3188 i
3189 );
3190 prop_assert!(
3191 (r1 - ref_output.r1[i]).abs() < tolerance
3192 || (r1.is_nan() && ref_output.r1[i].is_nan()),
3193 "R1 kernel mismatch at {}",
3194 i
3195 );
3196 prop_assert!(
3197 (s1 - ref_output.s1[i]).abs() < tolerance
3198 || (s1.is_nan() && ref_output.s1[i].is_nan()),
3199 "S1 kernel mismatch at {}",
3200 i
3201 );
3202
3203 #[cfg(debug_assertions)]
3204 {
3205 let check_poison = |val: f64, name: &str| {
3206 if !val.is_nan() {
3207 let bits = val.to_bits();
3208 prop_assert_ne!(
3209 bits,
3210 0x11111111_11111111,
3211 "{} poison at {}",
3212 name,
3213 i
3214 );
3215 prop_assert_ne!(
3216 bits,
3217 0x22222222_22222222,
3218 "{} poison at {}",
3219 name,
3220 i
3221 );
3222 prop_assert_ne!(
3223 bits,
3224 0x33333333_33333333,
3225 "{} poison at {}",
3226 name,
3227 i
3228 );
3229 }
3230 Ok(())
3231 };
3232
3233 check_poison(pp, "PP")?;
3234 check_poison(r4, "R4")?;
3235 check_poison(r3, "R3")?;
3236 check_poison(r2, "R2")?;
3237 check_poison(r1, "R1")?;
3238 check_poison(s1, "S1")?;
3239 check_poison(s2, "S2")?;
3240 check_poison(s3, "S3")?;
3241 check_poison(s4, "S4")?;
3242 }
3243 }
3244
3245 Ok(())
3246 },
3247 )?;
3248
3249 Ok(())
3250 }
3251
3252 #[cfg(debug_assertions)]
3253 fn check_pivot_no_poison(
3254 test_name: &str,
3255 kernel: Kernel,
3256 ) -> Result<(), Box<dyn std::error::Error>> {
3257 skip_if_unsupported!(kernel, test_name);
3258
3259 let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3260 let candles = read_candles_from_csv(file_path)?;
3261
3262 let test_params = vec![
3263 PivotParams::default(),
3264 PivotParams { mode: Some(0) },
3265 PivotParams { mode: Some(1) },
3266 PivotParams { mode: Some(2) },
3267 PivotParams { mode: Some(3) },
3268 PivotParams { mode: Some(4) },
3269 ];
3270
3271 for (param_idx, params) in test_params.iter().enumerate() {
3272 let input = PivotInput::from_candles(&candles, params.clone());
3273 let output = pivot_with_kernel(&input, kernel)?;
3274
3275 let arrays = vec![
3276 ("r4", &output.r4),
3277 ("r3", &output.r3),
3278 ("r2", &output.r2),
3279 ("r1", &output.r1),
3280 ("pp", &output.pp),
3281 ("s1", &output.s1),
3282 ("s2", &output.s2),
3283 ("s3", &output.s3),
3284 ("s4", &output.s4),
3285 ];
3286
3287 for (array_name, values) in arrays {
3288 for (i, &val) in values.iter().enumerate() {
3289 if val.is_nan() {
3290 continue;
3291 }
3292
3293 let bits = val.to_bits();
3294
3295 if bits == 0x11111111_11111111 {
3296 panic!(
3297 "[{}] Found alloc_with_nan_prefix poison value {} (0x{:016X}) at index {} \
3298 in array {} with params: {:?} (param set {})",
3299 test_name, val, bits, i, array_name, params, param_idx
3300 );
3301 }
3302
3303 if bits == 0x22222222_22222222 {
3304 panic!(
3305 "[{}] Found init_matrix_prefixes poison value {} (0x{:016X}) at index {} \
3306 in array {} with params: {:?} (param set {})",
3307 test_name, val, bits, i, array_name, params, param_idx
3308 );
3309 }
3310
3311 if bits == 0x33333333_33333333 {
3312 panic!(
3313 "[{}] Found make_uninit_matrix poison value {} (0x{:016X}) at index {} \
3314 in array {} with params: {:?} (param set {})",
3315 test_name, val, bits, i, array_name, params, param_idx
3316 );
3317 }
3318 }
3319 }
3320 }
3321
3322 Ok(())
3323 }
3324
3325 #[cfg(not(debug_assertions))]
3326 fn check_pivot_no_poison(
3327 _test_name: &str,
3328 _kernel: Kernel,
3329 ) -> Result<(), Box<dyn std::error::Error>> {
3330 Ok(())
3331 }
3332
3333 fn check_pivot_batch_default_row(
3334 test_name: &str,
3335 kernel: Kernel,
3336 ) -> Result<(), Box<dyn std::error::Error>> {
3337 skip_if_unsupported!(kernel, test_name);
3338 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3339 let candles = read_candles_from_csv(file)?;
3340 let output = PivotBatchBuilder::new()
3341 .kernel(kernel)
3342 .apply_candles(&candles)?;
3343 let default = PivotParams::default();
3344 let def_idx = output
3345 .combos
3346 .iter()
3347 .position(|p| p.mode == default.mode)
3348 .expect("default row missing");
3349 for arr in &output.levels[def_idx] {
3350 assert_eq!(arr.len(), candles.close.len());
3351 }
3352 Ok(())
3353 }
3354
3355 macro_rules! generate_all_pivot_tests {
3356 ($($test_fn:ident),*) => {
3357 paste! {
3358 $(
3359 #[test]
3360 fn [<$test_fn _scalar>]() { let _ = $test_fn(stringify!([<$test_fn _scalar>]), Kernel::Scalar); }
3361 )*
3362 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
3363 $(
3364 #[test]
3365 fn [<$test_fn _avx2>]() { let _ = $test_fn(stringify!([<$test_fn _avx2>]), Kernel::Avx2); }
3366 #[test]
3367 fn [<$test_fn _avx512>]() { let _ = $test_fn(stringify!([<$test_fn _avx512>]), Kernel::Avx512); }
3368 )*
3369 $(
3370 #[test]
3371 fn [<$test_fn _auto_detect>]() { let _ = $test_fn(stringify!([<$test_fn _auto_detect>]), Kernel::Auto); }
3372 )*
3373 }
3374 }
3375 }
3376
3377 generate_all_pivot_tests!(
3378 check_pivot_default_mode_camarilla,
3379 check_pivot_nan_values,
3380 check_pivot_no_data,
3381 check_pivot_all_nan,
3382 check_pivot_fibonacci_mode,
3383 check_pivot_standard_mode,
3384 check_pivot_demark_mode,
3385 check_pivot_woodie_mode,
3386 check_pivot_batch_default_row,
3387 check_pivot_no_poison
3388 );
3389
3390 #[cfg(feature = "proptest")]
3391 generate_all_pivot_tests!(check_pivot_property);
3392
3393 fn check_batch_default_row(
3394 test: &str,
3395 kernel: Kernel,
3396 ) -> Result<(), Box<dyn std::error::Error>> {
3397 skip_if_unsupported!(kernel, test);
3398
3399 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3400 let candles = read_candles_from_csv(file)?;
3401
3402 let output = PivotBatchBuilder::new()
3403 .kernel(kernel)
3404 .apply_candles(&candles)?;
3405
3406 let def = PivotParams::default();
3407 let row = output
3408 .combos
3409 .iter()
3410 .position(|p| p.mode == def.mode)
3411 .expect("default row missing");
3412 let levels = &output.levels[row];
3413
3414 for arr in levels.iter() {
3415 assert_eq!(arr.len(), candles.close.len());
3416 }
3417
3418 let expected_r4 = [59466.5, 59357.55, 59243.6, 59334.85, 59170.35];
3419 let r4 = &levels[0];
3420 let last_five_r4 = &r4[r4.len().saturating_sub(5)..];
3421 for (i, &val) in last_five_r4.iter().enumerate() {
3422 let exp = expected_r4[i];
3423 assert!(
3424 (val - exp).abs() < 1e-1,
3425 "[{test}] Camarilla r4 mismatch at idx {i}: {val} vs {exp:?}"
3426 );
3427 }
3428 Ok(())
3429 }
3430
3431 #[cfg(debug_assertions)]
3432 fn check_batch_no_poison(test: &str, kernel: Kernel) -> Result<(), Box<dyn std::error::Error>> {
3433 skip_if_unsupported!(kernel, test);
3434
3435 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3436 let c = read_candles_from_csv(file)?;
3437
3438 let test_configs = vec![
3439 (0, 2, 1),
3440 (0, 4, 1),
3441 (0, 4, 2),
3442 (1, 3, 1),
3443 (3, 4, 1),
3444 (2, 2, 1),
3445 (0, 0, 1),
3446 ];
3447
3448 for (cfg_idx, &(mode_start, mode_end, mode_step)) in test_configs.iter().enumerate() {
3449 let output = PivotBatchBuilder::new()
3450 .kernel(kernel)
3451 .mode_range(mode_start, mode_end, mode_step)
3452 .apply_candles(&c)?;
3453
3454 for (row_idx, levels) in output.levels.iter().enumerate() {
3455 let combo = &output.combos[row_idx];
3456
3457 for (level_idx, level_array) in levels.iter().enumerate() {
3458 let level_name = match level_idx {
3459 0 => "r4",
3460 1 => "r3",
3461 2 => "r2",
3462 3 => "r1",
3463 4 => "pp",
3464 5 => "s1",
3465 6 => "s2",
3466 7 => "s3",
3467 8 => "s4",
3468 _ => "unknown",
3469 };
3470
3471 for (col, &val) in level_array.iter().enumerate() {
3472 if val.is_nan() {
3473 continue;
3474 }
3475
3476 let bits = val.to_bits();
3477
3478 if bits == 0x11111111_11111111 {
3479 panic!(
3480 "[{}] Config {}: Found alloc_with_nan_prefix poison value {} (0x{:016X}) \
3481 at row {} col {} in array {} with params: {:?}",
3482 test, cfg_idx, val, bits, row_idx, col, level_name, combo
3483 );
3484 }
3485
3486 if bits == 0x22222222_22222222 {
3487 panic!(
3488 "[{}] Config {}: Found init_matrix_prefixes poison value {} (0x{:016X}) \
3489 at row {} col {} in array {} with params: {:?}",
3490 test, cfg_idx, val, bits, row_idx, col, level_name, combo
3491 );
3492 }
3493
3494 if bits == 0x33333333_33333333 {
3495 panic!(
3496 "[{}] Config {}: Found make_uninit_matrix poison value {} (0x{:016X}) \
3497 at row {} col {} in array {} with params: {:?}",
3498 test, cfg_idx, val, bits, row_idx, col, level_name, combo
3499 );
3500 }
3501 }
3502 }
3503 }
3504 }
3505
3506 Ok(())
3507 }
3508
3509 #[cfg(not(debug_assertions))]
3510 fn check_batch_no_poison(
3511 _test: &str,
3512 _kernel: Kernel,
3513 ) -> Result<(), Box<dyn std::error::Error>> {
3514 Ok(())
3515 }
3516
3517 macro_rules! gen_batch_tests {
3518 ($fn_name:ident) => {
3519 paste! {
3520 #[test] fn [<$fn_name _scalar>]() {
3521 let _ = $fn_name(stringify!([<$fn_name _scalar>]), Kernel::ScalarBatch);
3522 }
3523 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
3524 #[test] fn [<$fn_name _avx2>]() {
3525 let _ = $fn_name(stringify!([<$fn_name _avx2>]), Kernel::Avx2Batch);
3526 }
3527 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
3528 #[test] fn [<$fn_name _avx512>]() {
3529 let _ = $fn_name(stringify!([<$fn_name _avx512>]), Kernel::Avx512Batch);
3530 }
3531 #[test] fn [<$fn_name _auto_detect>]() {
3532 let _ = $fn_name(stringify!([<$fn_name _auto_detect>]), Kernel::Auto);
3533 }
3534 }
3535 };
3536 }
3537
3538 gen_batch_tests!(check_batch_default_row);
3539 gen_batch_tests!(check_batch_no_poison);
3540
3541 #[test]
3542 fn test_pivot_into_matches_api() -> Result<(), Box<dyn std::error::Error>> {
3543 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3544 let candles = read_candles_from_csv(file)?;
3545 let params = PivotParams::default();
3546 let input = PivotInput::from_candles(&candles, params);
3547
3548 let base = pivot(&input)?;
3549
3550 let len = candles.close.len();
3551
3552 let mut r4 = vec![0.0; len];
3553 let mut r3 = vec![0.0; len];
3554 let mut r2 = vec![0.0; len];
3555 let mut r1 = vec![0.0; len];
3556 let mut pp = vec![0.0; len];
3557 let mut s1 = vec![0.0; len];
3558 let mut s2 = vec![0.0; len];
3559 let mut s3 = vec![0.0; len];
3560 let mut s4 = vec![0.0; len];
3561
3562 #[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
3563 {
3564 pivot_into(
3565 &input, &mut r4, &mut r3, &mut r2, &mut r1, &mut pp, &mut s1, &mut s2, &mut s3,
3566 &mut s4,
3567 )?;
3568
3569 assert_eq!(r4.len(), base.r4.len());
3570 assert_eq!(r3.len(), base.r3.len());
3571 assert_eq!(r2.len(), base.r2.len());
3572 assert_eq!(r1.len(), base.r1.len());
3573 assert_eq!(pp.len(), base.pp.len());
3574 assert_eq!(s1.len(), base.s1.len());
3575 assert_eq!(s2.len(), base.s2.len());
3576 assert_eq!(s3.len(), base.s3.len());
3577 assert_eq!(s4.len(), base.s4.len());
3578
3579 fn eq_or_both_nan(a: f64, b: f64) -> bool {
3580 (a.is_nan() && b.is_nan()) || (a == b)
3581 }
3582
3583 for i in 0..len {
3584 assert!(eq_or_both_nan(r4[i], base.r4[i]), "r4 mismatch at {i}");
3585 assert!(eq_or_both_nan(r3[i], base.r3[i]), "r3 mismatch at {i}");
3586 assert!(eq_or_both_nan(r2[i], base.r2[i]), "r2 mismatch at {i}");
3587 assert!(eq_or_both_nan(r1[i], base.r1[i]), "r1 mismatch at {i}");
3588 assert!(eq_or_both_nan(pp[i], base.pp[i]), "pp mismatch at {i}");
3589 assert!(eq_or_both_nan(s1[i], base.s1[i]), "s1 mismatch at {i}");
3590 assert!(eq_or_both_nan(s2[i], base.s2[i]), "s2 mismatch at {i}");
3591 assert!(eq_or_both_nan(s3[i], base.s3[i]), "s3 mismatch at {i}");
3592 assert!(eq_or_both_nan(s4[i], base.s4[i]), "s4 mismatch at {i}");
3593 }
3594 }
3595
3596 Ok(())
3597 }
3598}