1#[cfg(feature = "python")]
2use numpy::{IntoPyArray, PyArray1, PyArrayMethods, PyReadonlyArray1};
3#[cfg(feature = "python")]
4use pyo3::exceptions::PyValueError;
5#[cfg(feature = "python")]
6use pyo3::prelude::*;
7#[cfg(feature = "python")]
8use pyo3::types::PyDict;
9#[cfg(feature = "python")]
10use pyo3::wrap_pyfunction;
11
12#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
13use serde::{Deserialize, Serialize};
14#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
15use wasm_bindgen::prelude::*;
16
17use crate::utilities::data_loader::Candles;
18use crate::utilities::enums::Kernel;
19use crate::utilities::helpers::{
20 alloc_with_nan_prefix, detect_best_batch_kernel, detect_best_kernel, init_matrix_prefixes,
21 make_uninit_matrix,
22};
23#[cfg(feature = "python")]
24use crate::utilities::kernel_validation::validate_kernel;
25use std::mem::ManuallyDrop;
26use thiserror::Error;
27
28const DIFF_FAST_PERIOD: usize = 12;
29const DIFF_SLOW_PERIOD: usize = 26;
30const DEA_PERIOD: usize = 9;
31const LINE_LONG_PERIOD: usize = 40;
32const LINE_SHORT_START: usize = 6;
33const LINE_SHORT_END: usize = 20;
34const LINE_SHORT_COUNT: usize = LINE_SHORT_END - LINE_SHORT_START + 1;
35const LINE_SHORT_AVG_INV: f64 = 1.0 / LINE_SHORT_COUNT as f64;
36const MID_CLOSE_WEIGHT: f64 = 7.0;
37const MID_DIVISOR: f64 = 10.0;
38const MACD_SCALE: f64 = 2.0;
39
40#[derive(Debug, Clone)]
41pub enum MacdWaveSignalProData<'a> {
42 Candles(&'a Candles),
43 Slices {
44 open: &'a [f64],
45 high: &'a [f64],
46 low: &'a [f64],
47 close: &'a [f64],
48 },
49}
50
51#[derive(Debug, Clone)]
52pub struct MacdWaveSignalProOutput {
53 pub diff: Vec<f64>,
54 pub dea: Vec<f64>,
55 pub macd_histogram: Vec<f64>,
56 pub line_convergence: Vec<f64>,
57 pub buy_signal: Vec<f64>,
58 pub sell_signal: Vec<f64>,
59}
60
61#[derive(Debug, Clone, Copy)]
62pub struct MacdWaveSignalProPoint {
63 pub diff: f64,
64 pub dea: f64,
65 pub macd_histogram: f64,
66 pub line_convergence: f64,
67 pub buy_signal: f64,
68 pub sell_signal: f64,
69}
70
71impl MacdWaveSignalProPoint {
72 #[inline(always)]
73 fn nan() -> Self {
74 Self {
75 diff: f64::NAN,
76 dea: f64::NAN,
77 macd_histogram: f64::NAN,
78 line_convergence: f64::NAN,
79 buy_signal: f64::NAN,
80 sell_signal: f64::NAN,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Default, PartialEq, Eq)]
86#[cfg_attr(
87 all(target_arch = "wasm32", feature = "wasm"),
88 derive(Serialize, Deserialize)
89)]
90pub struct MacdWaveSignalProParams;
91
92#[derive(Debug, Clone)]
93pub struct MacdWaveSignalProInput<'a> {
94 pub data: MacdWaveSignalProData<'a>,
95 pub params: MacdWaveSignalProParams,
96}
97
98impl<'a> MacdWaveSignalProInput<'a> {
99 #[inline]
100 pub fn from_candles(candles: &'a Candles, params: MacdWaveSignalProParams) -> Self {
101 Self {
102 data: MacdWaveSignalProData::Candles(candles),
103 params,
104 }
105 }
106
107 #[inline]
108 pub fn from_slices(
109 open: &'a [f64],
110 high: &'a [f64],
111 low: &'a [f64],
112 close: &'a [f64],
113 params: MacdWaveSignalProParams,
114 ) -> Self {
115 Self {
116 data: MacdWaveSignalProData::Slices {
117 open,
118 high,
119 low,
120 close,
121 },
122 params,
123 }
124 }
125
126 #[inline]
127 pub fn with_default_candles(candles: &'a Candles) -> Self {
128 Self::from_candles(candles, MacdWaveSignalProParams)
129 }
130
131 #[inline]
132 pub fn as_slices(&self) -> (&'a [f64], &'a [f64], &'a [f64], &'a [f64]) {
133 match &self.data {
134 MacdWaveSignalProData::Candles(candles) => {
135 (&candles.open, &candles.high, &candles.low, &candles.close)
136 }
137 MacdWaveSignalProData::Slices {
138 open,
139 high,
140 low,
141 close,
142 } => (open, high, low, close),
143 }
144 }
145}
146
147#[derive(Clone, Copy, Debug, Default)]
148pub struct MacdWaveSignalProBuilder {
149 kernel: Kernel,
150}
151
152impl MacdWaveSignalProBuilder {
153 #[inline]
154 pub fn new() -> Self {
155 Self::default()
156 }
157
158 #[inline]
159 pub fn kernel(mut self, kernel: Kernel) -> Self {
160 self.kernel = kernel;
161 self
162 }
163
164 #[inline]
165 pub fn apply(
166 self,
167 candles: &Candles,
168 ) -> Result<MacdWaveSignalProOutput, MacdWaveSignalProError> {
169 let input = MacdWaveSignalProInput::from_candles(candles, MacdWaveSignalProParams);
170 macd_wave_signal_pro_with_kernel(&input, self.kernel)
171 }
172
173 #[inline]
174 pub fn apply_slices(
175 self,
176 open: &[f64],
177 high: &[f64],
178 low: &[f64],
179 close: &[f64],
180 ) -> Result<MacdWaveSignalProOutput, MacdWaveSignalProError> {
181 let input =
182 MacdWaveSignalProInput::from_slices(open, high, low, close, MacdWaveSignalProParams);
183 macd_wave_signal_pro_with_kernel(&input, self.kernel)
184 }
185
186 #[inline]
187 pub fn into_stream(self) -> Result<MacdWaveSignalProStream, MacdWaveSignalProError> {
188 let _ = self.kernel;
189 MacdWaveSignalProStream::try_new(MacdWaveSignalProParams)
190 }
191}
192
193#[derive(Debug, Error)]
194pub enum MacdWaveSignalProError {
195 #[error("macd_wave_signal_pro: Input data slice is empty.")]
196 EmptyInputData,
197 #[error("macd_wave_signal_pro: All values are NaN.")]
198 AllValuesNaN,
199 #[error(
200 "macd_wave_signal_pro: Inconsistent slice lengths: open={open_len}, high={high_len}, low={low_len}, close={close_len}"
201 )]
202 InconsistentSliceLengths {
203 open_len: usize,
204 high_len: usize,
205 low_len: usize,
206 close_len: usize,
207 },
208 #[error("macd_wave_signal_pro: Not enough valid data: needed = {needed}, valid = {valid}")]
209 NotEnoughValidData { needed: usize, valid: usize },
210 #[error(
211 "macd_wave_signal_pro: Output length mismatch: expected = {expected}, diff = {diff_got}, dea = {dea_got}, macd_histogram = {macd_histogram_got}, line_convergence = {line_convergence_got}, buy_signal = {buy_signal_got}, sell_signal = {sell_signal_got}"
212 )]
213 OutputLengthMismatch {
214 expected: usize,
215 diff_got: usize,
216 dea_got: usize,
217 macd_histogram_got: usize,
218 line_convergence_got: usize,
219 buy_signal_got: usize,
220 sell_signal_got: usize,
221 },
222 #[error("macd_wave_signal_pro: Invalid range: start={start}, end={end}, step={step}")]
223 InvalidRange {
224 start: String,
225 end: String,
226 step: String,
227 },
228 #[error("macd_wave_signal_pro: Invalid kernel for batch: {0:?}")]
229 InvalidKernelForBatch(Kernel),
230}
231
232#[derive(Clone, Debug)]
233struct RollingSma {
234 period: usize,
235 values: Vec<f64>,
236 index: usize,
237 count: usize,
238 sum: f64,
239}
240
241impl RollingSma {
242 #[inline]
243 fn new(period: usize) -> Self {
244 Self {
245 period,
246 values: vec![0.0; period],
247 index: 0,
248 count: 0,
249 sum: 0.0,
250 }
251 }
252
253 #[inline]
254 fn reset(&mut self) {
255 self.index = 0;
256 self.count = 0;
257 self.sum = 0.0;
258 }
259
260 #[inline]
261 fn update(&mut self, value: f64) -> Option<f64> {
262 if self.count < self.period {
263 self.values[self.index] = value;
264 self.sum += value;
265 self.index += 1;
266 if self.index == self.period {
267 self.index = 0;
268 }
269 self.count += 1;
270 if self.count == self.period {
271 Some(self.sum / self.period as f64)
272 } else {
273 None
274 }
275 } else {
276 let old = self.values[self.index];
277 self.values[self.index] = value;
278 self.sum += value - old;
279 self.index += 1;
280 if self.index == self.period {
281 self.index = 0;
282 }
283 Some(self.sum / self.period as f64)
284 }
285 }
286}
287
288#[derive(Clone, Debug)]
289struct SeededEma {
290 period: usize,
291 alpha: f64,
292 beta: f64,
293 count: usize,
294 sum: f64,
295 value: f64,
296 ready: bool,
297}
298
299impl SeededEma {
300 #[inline]
301 fn new(period: usize) -> Self {
302 let alpha = 2.0 / (period as f64 + 1.0);
303 Self {
304 period,
305 alpha,
306 beta: 1.0 - alpha,
307 count: 0,
308 sum: 0.0,
309 value: f64::NAN,
310 ready: false,
311 }
312 }
313
314 #[inline]
315 fn reset(&mut self) {
316 self.count = 0;
317 self.sum = 0.0;
318 self.value = f64::NAN;
319 self.ready = false;
320 }
321
322 #[inline]
323 fn update(&mut self, value: f64) -> Option<f64> {
324 if self.count < self.period {
325 self.count += 1;
326 self.sum += value;
327 if self.count == self.period {
328 self.value = self.sum / self.period as f64;
329 self.ready = true;
330 Some(self.value)
331 } else {
332 None
333 }
334 } else {
335 self.value = value.mul_add(self.alpha, self.beta * self.value);
336 Some(self.value)
337 }
338 }
339}
340
341#[derive(Clone, Debug)]
342struct CoreState {
343 ema_fast: SeededEma,
344 ema_slow: SeededEma,
345 ema_dea: SeededEma,
346 line_short: Vec<RollingSma>,
347 line_long: RollingSma,
348 prev_diff: f64,
349 prev_dea: f64,
350}
351
352impl CoreState {
353 #[inline]
354 fn new() -> Self {
355 let mut line_short = Vec::with_capacity(LINE_SHORT_COUNT);
356 for period in LINE_SHORT_START..=LINE_SHORT_END {
357 line_short.push(RollingSma::new(period));
358 }
359 Self {
360 ema_fast: SeededEma::new(DIFF_FAST_PERIOD),
361 ema_slow: SeededEma::new(DIFF_SLOW_PERIOD),
362 ema_dea: SeededEma::new(DEA_PERIOD),
363 line_short,
364 line_long: RollingSma::new(LINE_LONG_PERIOD),
365 prev_diff: f64::NAN,
366 prev_dea: f64::NAN,
367 }
368 }
369
370 #[inline]
371 fn reset(&mut self) {
372 self.ema_fast.reset();
373 self.ema_slow.reset();
374 self.ema_dea.reset();
375 for sma in &mut self.line_short {
376 sma.reset();
377 }
378 self.line_long.reset();
379 self.prev_diff = f64::NAN;
380 self.prev_dea = f64::NAN;
381 }
382
383 #[inline]
384 fn update(&mut self, open: f64, high: f64, low: f64, close: f64) -> MacdWaveSignalProPoint {
385 let mut point = MacdWaveSignalProPoint::nan();
386
387 let fast = self.ema_fast.update(close);
388 let slow = self.ema_slow.update(close);
389 if let (Some(fast), Some(slow)) = (fast, slow) {
390 let diff = fast - slow;
391 point.diff = diff;
392
393 if let Some(dea) = self.ema_dea.update(diff) {
394 point.dea = dea;
395 point.macd_histogram = MACD_SCALE * (diff - dea);
396 if self.prev_diff.is_finite() && self.prev_dea.is_finite() {
397 point.buy_signal = if diff > dea && self.prev_diff <= self.prev_dea {
398 1.0
399 } else {
400 0.0
401 };
402 point.sell_signal = if diff < dea && self.prev_diff >= self.prev_dea {
403 1.0
404 } else {
405 0.0
406 };
407 } else {
408 point.buy_signal = 0.0;
409 point.sell_signal = 0.0;
410 }
411 }
412
413 self.prev_diff = diff;
414 self.prev_dea = point.dea;
415 }
416
417 let mid = (MID_CLOSE_WEIGHT.mul_add(close, open + high + low)) / MID_DIVISOR;
418 let mut short_sum = 0.0;
419 let mut short_ready = 0usize;
420 for sma in &mut self.line_short {
421 if let Some(value) = sma.update(mid) {
422 short_sum += value;
423 short_ready += 1;
424 }
425 }
426 if let Some(long) = self.line_long.update(mid) {
427 if short_ready == LINE_SHORT_COUNT {
428 point.line_convergence = short_sum * LINE_SHORT_AVG_INV - long;
429 }
430 }
431
432 point
433 }
434}
435
436#[derive(Debug, Clone)]
437pub struct MacdWaveSignalProStream {
438 state: CoreState,
439}
440
441impl MacdWaveSignalProStream {
442 #[inline]
443 pub fn try_new(_params: MacdWaveSignalProParams) -> Result<Self, MacdWaveSignalProError> {
444 Ok(Self {
445 state: CoreState::new(),
446 })
447 }
448
449 #[inline]
450 pub fn update(
451 &mut self,
452 open: f64,
453 high: f64,
454 low: f64,
455 close: f64,
456 ) -> Option<MacdWaveSignalProPoint> {
457 if !valid_ohlc_bar(open, high, low, close) {
458 self.state.reset();
459 return None;
460 }
461 Some(self.state.update(open, high, low, close))
462 }
463
464 #[inline]
465 pub fn reset(&mut self) {
466 self.state.reset();
467 }
468
469 #[inline]
470 pub fn get_warmup_period(&self) -> usize {
471 LINE_LONG_PERIOD.saturating_sub(1)
472 }
473}
474
475#[inline(always)]
476fn valid_ohlc_bar(open: f64, high: f64, low: f64, close: f64) -> bool {
477 open.is_finite() && high.is_finite() && low.is_finite() && close.is_finite()
478}
479
480#[inline(always)]
481fn first_valid_ohlc(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> usize {
482 let len = close.len();
483 let mut i = 0usize;
484 while i < len {
485 if valid_ohlc_bar(open[i], high[i], low[i], close[i]) {
486 return i;
487 }
488 i += 1;
489 }
490 len
491}
492
493#[inline(always)]
494fn count_valid_ohlc(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> usize {
495 let mut count = 0usize;
496 let len = close.len();
497 let mut i = 0usize;
498 while i < len {
499 if valid_ohlc_bar(open[i], high[i], low[i], close[i]) {
500 count += 1;
501 }
502 i += 1;
503 }
504 count
505}
506
507#[inline(always)]
508fn output_warmups(first: usize, len: usize) -> [usize; 6] {
509 [
510 first
511 .saturating_add(DIFF_SLOW_PERIOD)
512 .saturating_sub(1)
513 .min(len),
514 first
515 .saturating_add(DIFF_SLOW_PERIOD + DEA_PERIOD)
516 .saturating_sub(2)
517 .min(len),
518 first
519 .saturating_add(DIFF_SLOW_PERIOD + DEA_PERIOD)
520 .saturating_sub(2)
521 .min(len),
522 first
523 .saturating_add(LINE_LONG_PERIOD)
524 .saturating_sub(1)
525 .min(len),
526 first
527 .saturating_add(DIFF_SLOW_PERIOD + DEA_PERIOD)
528 .saturating_sub(2)
529 .min(len),
530 first
531 .saturating_add(DIFF_SLOW_PERIOD + DEA_PERIOD)
532 .saturating_sub(2)
533 .min(len),
534 ]
535}
536
537#[inline(always)]
538fn max_required_valid() -> usize {
539 LINE_LONG_PERIOD.max(DIFF_SLOW_PERIOD + DEA_PERIOD - 1)
540}
541
542#[inline(always)]
543fn prepare<'a>(
544 input: &'a MacdWaveSignalProInput,
545 kernel: Kernel,
546) -> Result<((&'a [f64], &'a [f64], &'a [f64], &'a [f64]), usize, Kernel), MacdWaveSignalProError> {
547 let (open, high, low, close) = input.as_slices();
548 if open.is_empty() || high.is_empty() || low.is_empty() || close.is_empty() {
549 return Err(MacdWaveSignalProError::EmptyInputData);
550 }
551 if open.len() != high.len() || open.len() != low.len() || open.len() != close.len() {
552 return Err(MacdWaveSignalProError::InconsistentSliceLengths {
553 open_len: open.len(),
554 high_len: high.len(),
555 low_len: low.len(),
556 close_len: close.len(),
557 });
558 }
559 let first = first_valid_ohlc(open, high, low, close);
560 if first >= close.len() {
561 return Err(MacdWaveSignalProError::AllValuesNaN);
562 }
563 let valid = count_valid_ohlc(open, high, low, close);
564 let needed = max_required_valid();
565 if valid < needed {
566 return Err(MacdWaveSignalProError::NotEnoughValidData { needed, valid });
567 }
568 let chosen = match kernel {
569 Kernel::Auto => detect_best_kernel(),
570 other => other.to_non_batch(),
571 };
572 Ok(((open, high, low, close), first, chosen))
573}
574
575#[allow(clippy::too_many_arguments)]
576#[inline(always)]
577fn macd_wave_signal_pro_row_from_slices(
578 open: &[f64],
579 high: &[f64],
580 low: &[f64],
581 close: &[f64],
582 diff: &mut [f64],
583 dea: &mut [f64],
584 macd_histogram: &mut [f64],
585 line_convergence: &mut [f64],
586 buy_signal: &mut [f64],
587 sell_signal: &mut [f64],
588) {
589 let len = close.len();
590 debug_assert_eq!(open.len(), len);
591 debug_assert_eq!(high.len(), len);
592 debug_assert_eq!(low.len(), len);
593 debug_assert_eq!(diff.len(), len);
594 debug_assert_eq!(dea.len(), len);
595 debug_assert_eq!(macd_histogram.len(), len);
596 debug_assert_eq!(line_convergence.len(), len);
597 debug_assert_eq!(buy_signal.len(), len);
598 debug_assert_eq!(sell_signal.len(), len);
599
600 let mut state = CoreState::new();
601 let mut i = 0usize;
602 while i < len {
603 if valid_ohlc_bar(open[i], high[i], low[i], close[i]) {
604 let point = state.update(open[i], high[i], low[i], close[i]);
605 diff[i] = point.diff;
606 dea[i] = point.dea;
607 macd_histogram[i] = point.macd_histogram;
608 line_convergence[i] = point.line_convergence;
609 buy_signal[i] = point.buy_signal;
610 sell_signal[i] = point.sell_signal;
611 } else {
612 state.reset();
613 diff[i] = f64::NAN;
614 dea[i] = f64::NAN;
615 macd_histogram[i] = f64::NAN;
616 line_convergence[i] = f64::NAN;
617 buy_signal[i] = f64::NAN;
618 sell_signal[i] = f64::NAN;
619 }
620 i += 1;
621 }
622}
623
624#[inline]
625pub fn macd_wave_signal_pro(
626 input: &MacdWaveSignalProInput,
627) -> Result<MacdWaveSignalProOutput, MacdWaveSignalProError> {
628 macd_wave_signal_pro_with_kernel(input, Kernel::Auto)
629}
630
631#[inline]
632pub fn macd_wave_signal_pro_with_kernel(
633 input: &MacdWaveSignalProInput,
634 kernel: Kernel,
635) -> Result<MacdWaveSignalProOutput, MacdWaveSignalProError> {
636 let ((open, high, low, close), first, _chosen) = prepare(input, kernel)?;
637 let len = close.len();
638 let warmups = output_warmups(first, len);
639 let mut diff = alloc_with_nan_prefix(len, warmups[0]);
640 let mut dea = alloc_with_nan_prefix(len, warmups[1]);
641 let mut macd_histogram = alloc_with_nan_prefix(len, warmups[2]);
642 let mut line_convergence = alloc_with_nan_prefix(len, warmups[3]);
643 let mut buy_signal = alloc_with_nan_prefix(len, warmups[4]);
644 let mut sell_signal = alloc_with_nan_prefix(len, warmups[5]);
645 macd_wave_signal_pro_row_from_slices(
646 open,
647 high,
648 low,
649 close,
650 &mut diff,
651 &mut dea,
652 &mut macd_histogram,
653 &mut line_convergence,
654 &mut buy_signal,
655 &mut sell_signal,
656 );
657 Ok(MacdWaveSignalProOutput {
658 diff,
659 dea,
660 macd_histogram,
661 line_convergence,
662 buy_signal,
663 sell_signal,
664 })
665}
666
667#[allow(clippy::too_many_arguments)]
668#[inline]
669pub fn macd_wave_signal_pro_into_slices(
670 diff_out: &mut [f64],
671 dea_out: &mut [f64],
672 macd_histogram_out: &mut [f64],
673 line_convergence_out: &mut [f64],
674 buy_signal_out: &mut [f64],
675 sell_signal_out: &mut [f64],
676 input: &MacdWaveSignalProInput,
677 kernel: Kernel,
678) -> Result<(), MacdWaveSignalProError> {
679 let ((open, high, low, close), _first, _chosen) = prepare(input, kernel)?;
680 let len = close.len();
681 if diff_out.len() != len
682 || dea_out.len() != len
683 || macd_histogram_out.len() != len
684 || line_convergence_out.len() != len
685 || buy_signal_out.len() != len
686 || sell_signal_out.len() != len
687 {
688 return Err(MacdWaveSignalProError::OutputLengthMismatch {
689 expected: len,
690 diff_got: diff_out.len(),
691 dea_got: dea_out.len(),
692 macd_histogram_got: macd_histogram_out.len(),
693 line_convergence_got: line_convergence_out.len(),
694 buy_signal_got: buy_signal_out.len(),
695 sell_signal_got: sell_signal_out.len(),
696 });
697 }
698
699 macd_wave_signal_pro_row_from_slices(
700 open,
701 high,
702 low,
703 close,
704 diff_out,
705 dea_out,
706 macd_histogram_out,
707 line_convergence_out,
708 buy_signal_out,
709 sell_signal_out,
710 );
711 Ok(())
712}
713
714#[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
715#[allow(clippy::too_many_arguments)]
716#[inline]
717pub fn macd_wave_signal_pro_into(
718 input: &MacdWaveSignalProInput,
719 diff_out: &mut [f64],
720 dea_out: &mut [f64],
721 macd_histogram_out: &mut [f64],
722 line_convergence_out: &mut [f64],
723 buy_signal_out: &mut [f64],
724 sell_signal_out: &mut [f64],
725) -> Result<(), MacdWaveSignalProError> {
726 macd_wave_signal_pro_into_slices(
727 diff_out,
728 dea_out,
729 macd_histogram_out,
730 line_convergence_out,
731 buy_signal_out,
732 sell_signal_out,
733 input,
734 Kernel::Auto,
735 )
736}
737
738#[derive(Clone, Debug, Default)]
739pub struct MacdWaveSignalProBatchRange;
740
741#[derive(Clone, Debug, Default)]
742pub struct MacdWaveSignalProBatchBuilder {
743 kernel: Kernel,
744}
745
746impl MacdWaveSignalProBatchBuilder {
747 #[inline]
748 pub fn new() -> Self {
749 Self::default()
750 }
751
752 #[inline]
753 pub fn kernel(mut self, kernel: Kernel) -> Self {
754 self.kernel = kernel;
755 self
756 }
757
758 #[inline]
759 pub fn apply_slices(
760 self,
761 open: &[f64],
762 high: &[f64],
763 low: &[f64],
764 close: &[f64],
765 ) -> Result<MacdWaveSignalProBatchOutput, MacdWaveSignalProError> {
766 macd_wave_signal_pro_batch_with_kernel(
767 open,
768 high,
769 low,
770 close,
771 &MacdWaveSignalProBatchRange,
772 self.kernel,
773 )
774 }
775
776 #[inline]
777 pub fn apply(
778 self,
779 candles: &Candles,
780 ) -> Result<MacdWaveSignalProBatchOutput, MacdWaveSignalProError> {
781 self.apply_slices(&candles.open, &candles.high, &candles.low, &candles.close)
782 }
783}
784
785#[derive(Clone, Debug)]
786pub struct MacdWaveSignalProBatchOutput {
787 pub diff: Vec<f64>,
788 pub dea: Vec<f64>,
789 pub macd_histogram: Vec<f64>,
790 pub line_convergence: Vec<f64>,
791 pub buy_signal: Vec<f64>,
792 pub sell_signal: Vec<f64>,
793 pub combos: Vec<MacdWaveSignalProParams>,
794 pub rows: usize,
795 pub cols: usize,
796}
797
798impl MacdWaveSignalProBatchOutput {
799 #[inline]
800 pub fn row_for_params(&self, _params: &MacdWaveSignalProParams) -> Option<usize> {
801 if self.rows == 0 {
802 None
803 } else {
804 Some(0)
805 }
806 }
807
808 #[inline]
809 pub fn values_for(
810 &self,
811 _params: &MacdWaveSignalProParams,
812 ) -> Option<(&[f64], &[f64], &[f64], &[f64], &[f64], &[f64])> {
813 if self.rows == 0 {
814 None
815 } else {
816 Some((
817 &self.diff[..self.cols],
818 &self.dea[..self.cols],
819 &self.macd_histogram[..self.cols],
820 &self.line_convergence[..self.cols],
821 &self.buy_signal[..self.cols],
822 &self.sell_signal[..self.cols],
823 ))
824 }
825 }
826}
827
828#[inline(always)]
829fn expand_grid_macd_wave_signal_pro(
830 _range: &MacdWaveSignalProBatchRange,
831) -> Result<Vec<MacdWaveSignalProParams>, MacdWaveSignalProError> {
832 Ok(vec![MacdWaveSignalProParams])
833}
834
835#[inline]
836pub fn macd_wave_signal_pro_batch_with_kernel(
837 open: &[f64],
838 high: &[f64],
839 low: &[f64],
840 close: &[f64],
841 sweep: &MacdWaveSignalProBatchRange,
842 kernel: Kernel,
843) -> Result<MacdWaveSignalProBatchOutput, MacdWaveSignalProError> {
844 let batch_kernel = match kernel {
845 Kernel::Auto => detect_best_batch_kernel(),
846 other if other.is_batch() => other,
847 other => return Err(MacdWaveSignalProError::InvalidKernelForBatch(other)),
848 };
849 macd_wave_signal_pro_batch_par_slice(open, high, low, close, sweep, batch_kernel.to_non_batch())
850}
851
852#[inline]
853pub fn macd_wave_signal_pro_batch_slice(
854 open: &[f64],
855 high: &[f64],
856 low: &[f64],
857 close: &[f64],
858 sweep: &MacdWaveSignalProBatchRange,
859 kernel: Kernel,
860) -> Result<MacdWaveSignalProBatchOutput, MacdWaveSignalProError> {
861 macd_wave_signal_pro_batch_inner(open, high, low, close, sweep, kernel, false)
862}
863
864#[inline]
865pub fn macd_wave_signal_pro_batch_par_slice(
866 open: &[f64],
867 high: &[f64],
868 low: &[f64],
869 close: &[f64],
870 sweep: &MacdWaveSignalProBatchRange,
871 kernel: Kernel,
872) -> Result<MacdWaveSignalProBatchOutput, MacdWaveSignalProError> {
873 macd_wave_signal_pro_batch_inner(open, high, low, close, sweep, kernel, true)
874}
875
876#[allow(clippy::too_many_arguments)]
877fn macd_wave_signal_pro_batch_inner(
878 open: &[f64],
879 high: &[f64],
880 low: &[f64],
881 close: &[f64],
882 sweep: &MacdWaveSignalProBatchRange,
883 _kernel: Kernel,
884 parallel: bool,
885) -> Result<MacdWaveSignalProBatchOutput, MacdWaveSignalProError> {
886 if open.is_empty() || high.is_empty() || low.is_empty() || close.is_empty() {
887 return Err(MacdWaveSignalProError::EmptyInputData);
888 }
889 if open.len() != high.len() || open.len() != low.len() || open.len() != close.len() {
890 return Err(MacdWaveSignalProError::InconsistentSliceLengths {
891 open_len: open.len(),
892 high_len: high.len(),
893 low_len: low.len(),
894 close_len: close.len(),
895 });
896 }
897 let first = first_valid_ohlc(open, high, low, close);
898 if first >= close.len() {
899 return Err(MacdWaveSignalProError::AllValuesNaN);
900 }
901 let valid = count_valid_ohlc(open, high, low, close);
902 let needed = max_required_valid();
903 if valid < needed {
904 return Err(MacdWaveSignalProError::NotEnoughValidData { needed, valid });
905 }
906
907 let combos = expand_grid_macd_wave_signal_pro(sweep)?;
908 let rows = combos.len();
909 let cols = close.len();
910
911 let mut diff_mu = make_uninit_matrix(rows, cols);
912 let mut dea_mu = make_uninit_matrix(rows, cols);
913 let mut macd_mu = make_uninit_matrix(rows, cols);
914 let mut line_mu = make_uninit_matrix(rows, cols);
915 let mut buy_mu = make_uninit_matrix(rows, cols);
916 let mut sell_mu = make_uninit_matrix(rows, cols);
917 let warmups = output_warmups(first, cols);
918 init_matrix_prefixes(&mut diff_mu, cols, &[warmups[0]]);
919 init_matrix_prefixes(&mut dea_mu, cols, &[warmups[1]]);
920 init_matrix_prefixes(&mut macd_mu, cols, &[warmups[2]]);
921 init_matrix_prefixes(&mut line_mu, cols, &[warmups[3]]);
922 init_matrix_prefixes(&mut buy_mu, cols, &[warmups[4]]);
923 init_matrix_prefixes(&mut sell_mu, cols, &[warmups[5]]);
924
925 let mut diff_guard = ManuallyDrop::new(diff_mu);
926 let mut dea_guard = ManuallyDrop::new(dea_mu);
927 let mut macd_guard = ManuallyDrop::new(macd_mu);
928 let mut line_guard = ManuallyDrop::new(line_mu);
929 let mut buy_guard = ManuallyDrop::new(buy_mu);
930 let mut sell_guard = ManuallyDrop::new(sell_mu);
931
932 let diff_out = unsafe {
933 std::slice::from_raw_parts_mut(diff_guard.as_mut_ptr() as *mut f64, diff_guard.len())
934 };
935 let dea_out = unsafe {
936 std::slice::from_raw_parts_mut(dea_guard.as_mut_ptr() as *mut f64, dea_guard.len())
937 };
938 let macd_out = unsafe {
939 std::slice::from_raw_parts_mut(macd_guard.as_mut_ptr() as *mut f64, macd_guard.len())
940 };
941 let line_out = unsafe {
942 std::slice::from_raw_parts_mut(line_guard.as_mut_ptr() as *mut f64, line_guard.len())
943 };
944 let buy_out = unsafe {
945 std::slice::from_raw_parts_mut(buy_guard.as_mut_ptr() as *mut f64, buy_guard.len())
946 };
947 let sell_out = unsafe {
948 std::slice::from_raw_parts_mut(sell_guard.as_mut_ptr() as *mut f64, sell_guard.len())
949 };
950
951 if parallel {
952 #[cfg(not(target_arch = "wasm32"))]
953 {
954 use rayon::prelude::*;
955
956 diff_out
957 .par_chunks_mut(cols)
958 .zip(dea_out.par_chunks_mut(cols))
959 .zip(macd_out.par_chunks_mut(cols))
960 .zip(line_out.par_chunks_mut(cols))
961 .zip(buy_out.par_chunks_mut(cols))
962 .zip(sell_out.par_chunks_mut(cols))
963 .for_each(
964 |(((((dst_diff, dst_dea), dst_macd), dst_line), dst_buy), dst_sell)| {
965 macd_wave_signal_pro_row_from_slices(
966 open, high, low, close, dst_diff, dst_dea, dst_macd, dst_line, dst_buy,
967 dst_sell,
968 );
969 },
970 );
971 }
972
973 #[cfg(target_arch = "wasm32")]
974 {
975 macd_wave_signal_pro_row_from_slices(
976 open,
977 high,
978 low,
979 close,
980 &mut diff_out[..cols],
981 &mut dea_out[..cols],
982 &mut macd_out[..cols],
983 &mut line_out[..cols],
984 &mut buy_out[..cols],
985 &mut sell_out[..cols],
986 );
987 }
988 } else {
989 macd_wave_signal_pro_row_from_slices(
990 open,
991 high,
992 low,
993 close,
994 &mut diff_out[..cols],
995 &mut dea_out[..cols],
996 &mut macd_out[..cols],
997 &mut line_out[..cols],
998 &mut buy_out[..cols],
999 &mut sell_out[..cols],
1000 );
1001 }
1002
1003 let diff = unsafe {
1004 Vec::from_raw_parts(
1005 diff_guard.as_mut_ptr() as *mut f64,
1006 diff_guard.len(),
1007 diff_guard.capacity(),
1008 )
1009 };
1010 let dea = unsafe {
1011 Vec::from_raw_parts(
1012 dea_guard.as_mut_ptr() as *mut f64,
1013 dea_guard.len(),
1014 dea_guard.capacity(),
1015 )
1016 };
1017 let macd_histogram = unsafe {
1018 Vec::from_raw_parts(
1019 macd_guard.as_mut_ptr() as *mut f64,
1020 macd_guard.len(),
1021 macd_guard.capacity(),
1022 )
1023 };
1024 let line_convergence = unsafe {
1025 Vec::from_raw_parts(
1026 line_guard.as_mut_ptr() as *mut f64,
1027 line_guard.len(),
1028 line_guard.capacity(),
1029 )
1030 };
1031 let buy_signal = unsafe {
1032 Vec::from_raw_parts(
1033 buy_guard.as_mut_ptr() as *mut f64,
1034 buy_guard.len(),
1035 buy_guard.capacity(),
1036 )
1037 };
1038 let sell_signal = unsafe {
1039 Vec::from_raw_parts(
1040 sell_guard.as_mut_ptr() as *mut f64,
1041 sell_guard.len(),
1042 sell_guard.capacity(),
1043 )
1044 };
1045
1046 Ok(MacdWaveSignalProBatchOutput {
1047 diff,
1048 dea,
1049 macd_histogram,
1050 line_convergence,
1051 buy_signal,
1052 sell_signal,
1053 combos,
1054 rows,
1055 cols,
1056 })
1057}
1058
1059#[allow(clippy::too_many_arguments)]
1060#[inline]
1061pub fn macd_wave_signal_pro_batch_inner_into(
1062 open: &[f64],
1063 high: &[f64],
1064 low: &[f64],
1065 close: &[f64],
1066 sweep: &MacdWaveSignalProBatchRange,
1067 kernel: Kernel,
1068 parallel: bool,
1069 diff_out: &mut [f64],
1070 dea_out: &mut [f64],
1071 macd_histogram_out: &mut [f64],
1072 line_convergence_out: &mut [f64],
1073 buy_signal_out: &mut [f64],
1074 sell_signal_out: &mut [f64],
1075) -> Result<Vec<MacdWaveSignalProParams>, MacdWaveSignalProError> {
1076 let out = macd_wave_signal_pro_batch_inner(open, high, low, close, sweep, kernel, parallel)?;
1077 let total = out.rows * out.cols;
1078 if diff_out.len() != total
1079 || dea_out.len() != total
1080 || macd_histogram_out.len() != total
1081 || line_convergence_out.len() != total
1082 || buy_signal_out.len() != total
1083 || sell_signal_out.len() != total
1084 {
1085 return Err(MacdWaveSignalProError::OutputLengthMismatch {
1086 expected: total,
1087 diff_got: diff_out.len(),
1088 dea_got: dea_out.len(),
1089 macd_histogram_got: macd_histogram_out.len(),
1090 line_convergence_got: line_convergence_out.len(),
1091 buy_signal_got: buy_signal_out.len(),
1092 sell_signal_got: sell_signal_out.len(),
1093 });
1094 }
1095
1096 diff_out.copy_from_slice(&out.diff);
1097 dea_out.copy_from_slice(&out.dea);
1098 macd_histogram_out.copy_from_slice(&out.macd_histogram);
1099 line_convergence_out.copy_from_slice(&out.line_convergence);
1100 buy_signal_out.copy_from_slice(&out.buy_signal);
1101 sell_signal_out.copy_from_slice(&out.sell_signal);
1102 Ok(out.combos)
1103}
1104
1105#[cfg(feature = "python")]
1106#[pyfunction(name = "macd_wave_signal_pro")]
1107#[pyo3(signature = (open, high, low, close, kernel=None))]
1108pub fn macd_wave_signal_pro_py<'py>(
1109 py: Python<'py>,
1110 open: PyReadonlyArray1<'py, f64>,
1111 high: PyReadonlyArray1<'py, f64>,
1112 low: PyReadonlyArray1<'py, f64>,
1113 close: PyReadonlyArray1<'py, f64>,
1114 kernel: Option<&str>,
1115) -> PyResult<(
1116 Bound<'py, PyArray1<f64>>,
1117 Bound<'py, PyArray1<f64>>,
1118 Bound<'py, PyArray1<f64>>,
1119 Bound<'py, PyArray1<f64>>,
1120 Bound<'py, PyArray1<f64>>,
1121 Bound<'py, PyArray1<f64>>,
1122)> {
1123 let open = open.as_slice()?;
1124 let high = high.as_slice()?;
1125 let low = low.as_slice()?;
1126 let close = close.as_slice()?;
1127 let kernel = validate_kernel(kernel, false)?;
1128 let input =
1129 MacdWaveSignalProInput::from_slices(open, high, low, close, MacdWaveSignalProParams);
1130 let out = py
1131 .allow_threads(|| macd_wave_signal_pro_with_kernel(&input, kernel))
1132 .map_err(|e| PyValueError::new_err(e.to_string()))?;
1133 Ok((
1134 out.diff.into_pyarray(py),
1135 out.dea.into_pyarray(py),
1136 out.macd_histogram.into_pyarray(py),
1137 out.line_convergence.into_pyarray(py),
1138 out.buy_signal.into_pyarray(py),
1139 out.sell_signal.into_pyarray(py),
1140 ))
1141}
1142
1143#[cfg(feature = "python")]
1144#[pyclass(name = "MacdWaveSignalProStream")]
1145pub struct MacdWaveSignalProStreamPy {
1146 stream: MacdWaveSignalProStream,
1147}
1148
1149#[cfg(feature = "python")]
1150#[pymethods]
1151impl MacdWaveSignalProStreamPy {
1152 #[new]
1153 fn new() -> PyResult<Self> {
1154 Ok(Self {
1155 stream: MacdWaveSignalProStream::try_new(MacdWaveSignalProParams)
1156 .map_err(|e| PyValueError::new_err(e.to_string()))?,
1157 })
1158 }
1159
1160 fn update(
1161 &mut self,
1162 open: f64,
1163 high: f64,
1164 low: f64,
1165 close: f64,
1166 ) -> Option<(f64, f64, f64, f64, f64, f64)> {
1167 self.stream.update(open, high, low, close).map(|point| {
1168 (
1169 point.diff,
1170 point.dea,
1171 point.macd_histogram,
1172 point.line_convergence,
1173 point.buy_signal,
1174 point.sell_signal,
1175 )
1176 })
1177 }
1178
1179 fn reset(&mut self) {
1180 self.stream.reset();
1181 }
1182
1183 #[getter]
1184 fn warmup_period(&self) -> usize {
1185 self.stream.get_warmup_period()
1186 }
1187}
1188
1189#[cfg(feature = "python")]
1190#[pyfunction(name = "macd_wave_signal_pro_batch")]
1191#[pyo3(signature = (open, high, low, close, kernel=None))]
1192pub fn macd_wave_signal_pro_batch_py<'py>(
1193 py: Python<'py>,
1194 open: PyReadonlyArray1<'py, f64>,
1195 high: PyReadonlyArray1<'py, f64>,
1196 low: PyReadonlyArray1<'py, f64>,
1197 close: PyReadonlyArray1<'py, f64>,
1198 kernel: Option<&str>,
1199) -> PyResult<Bound<'py, PyDict>> {
1200 let open = open.as_slice()?;
1201 let high = high.as_slice()?;
1202 let low = low.as_slice()?;
1203 let close = close.as_slice()?;
1204 let kernel = validate_kernel(kernel, true)?;
1205
1206 let rows = 1usize;
1207 let cols = close.len();
1208 let total = rows
1209 .checked_mul(cols)
1210 .ok_or_else(|| PyValueError::new_err("rows*cols overflow"))?;
1211
1212 let diff_arr = unsafe { PyArray1::<f64>::new(py, [total], false) };
1213 let dea_arr = unsafe { PyArray1::<f64>::new(py, [total], false) };
1214 let macd_arr = unsafe { PyArray1::<f64>::new(py, [total], false) };
1215 let line_arr = unsafe { PyArray1::<f64>::new(py, [total], false) };
1216 let buy_arr = unsafe { PyArray1::<f64>::new(py, [total], false) };
1217 let sell_arr = unsafe { PyArray1::<f64>::new(py, [total], false) };
1218
1219 let diff_slice = unsafe { diff_arr.as_slice_mut()? };
1220 let dea_slice = unsafe { dea_arr.as_slice_mut()? };
1221 let macd_slice = unsafe { macd_arr.as_slice_mut()? };
1222 let line_slice = unsafe { line_arr.as_slice_mut()? };
1223 let buy_slice = unsafe { buy_arr.as_slice_mut()? };
1224 let sell_slice = unsafe { sell_arr.as_slice_mut()? };
1225
1226 py.allow_threads(|| {
1227 let kernel = match kernel {
1228 Kernel::Auto => detect_best_batch_kernel(),
1229 other => other,
1230 };
1231 macd_wave_signal_pro_batch_inner_into(
1232 open,
1233 high,
1234 low,
1235 close,
1236 &MacdWaveSignalProBatchRange,
1237 kernel.to_non_batch(),
1238 true,
1239 diff_slice,
1240 dea_slice,
1241 macd_slice,
1242 line_slice,
1243 buy_slice,
1244 sell_slice,
1245 )
1246 })
1247 .map_err(|e| PyValueError::new_err(e.to_string()))?;
1248
1249 let dict = PyDict::new(py);
1250 dict.set_item("diff", diff_arr.reshape((rows, cols))?)?;
1251 dict.set_item("dea", dea_arr.reshape((rows, cols))?)?;
1252 dict.set_item("macd_histogram", macd_arr.reshape((rows, cols))?)?;
1253 dict.set_item("line_convergence", line_arr.reshape((rows, cols))?)?;
1254 dict.set_item("buy_signal", buy_arr.reshape((rows, cols))?)?;
1255 dict.set_item("sell_signal", sell_arr.reshape((rows, cols))?)?;
1256 dict.set_item("params", Vec::<f64>::new().into_pyarray(py))?;
1257 dict.set_item("rows", rows)?;
1258 dict.set_item("cols", cols)?;
1259 Ok(dict)
1260}
1261
1262#[cfg(feature = "python")]
1263pub fn register_macd_wave_signal_pro_module(
1264 module: &Bound<'_, pyo3::types::PyModule>,
1265) -> PyResult<()> {
1266 module.add_function(wrap_pyfunction!(macd_wave_signal_pro_py, module)?)?;
1267 module.add_function(wrap_pyfunction!(macd_wave_signal_pro_batch_py, module)?)?;
1268 module.add_class::<MacdWaveSignalProStreamPy>()?;
1269 Ok(())
1270}
1271
1272#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1273#[derive(Serialize, Deserialize)]
1274pub struct MacdWaveSignalProJsOutput {
1275 pub diff: Vec<f64>,
1276 pub dea: Vec<f64>,
1277 pub macd_histogram: Vec<f64>,
1278 pub line_convergence: Vec<f64>,
1279 pub buy_signal: Vec<f64>,
1280 pub sell_signal: Vec<f64>,
1281}
1282
1283#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1284#[wasm_bindgen(js_name = "macd_wave_signal_pro_js")]
1285pub fn macd_wave_signal_pro_js(
1286 open: &[f64],
1287 high: &[f64],
1288 low: &[f64],
1289 close: &[f64],
1290) -> Result<JsValue, JsValue> {
1291 let input =
1292 MacdWaveSignalProInput::from_slices(open, high, low, close, MacdWaveSignalProParams);
1293 let out = macd_wave_signal_pro(&input).map_err(|e| JsValue::from_str(&e.to_string()))?;
1294 serde_wasm_bindgen::to_value(&MacdWaveSignalProJsOutput {
1295 diff: out.diff,
1296 dea: out.dea,
1297 macd_histogram: out.macd_histogram,
1298 line_convergence: out.line_convergence,
1299 buy_signal: out.buy_signal,
1300 sell_signal: out.sell_signal,
1301 })
1302 .map_err(|e| JsValue::from_str(&e.to_string()))
1303}
1304
1305#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1306#[wasm_bindgen]
1307pub fn macd_wave_signal_pro_alloc(len: usize) -> *mut f64 {
1308 let mut vec = Vec::<f64>::with_capacity(len);
1309 let ptr = vec.as_mut_ptr();
1310 std::mem::forget(vec);
1311 ptr
1312}
1313
1314#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1315#[wasm_bindgen]
1316pub fn macd_wave_signal_pro_free(ptr: *mut f64, len: usize) {
1317 if !ptr.is_null() {
1318 unsafe {
1319 let _ = Vec::from_raw_parts(ptr, len, len);
1320 }
1321 }
1322}
1323
1324#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1325fn has_duplicate_ptrs(ptrs: &[usize]) -> bool {
1326 for i in 0..ptrs.len() {
1327 for j in (i + 1)..ptrs.len() {
1328 if ptrs[i] == ptrs[j] {
1329 return true;
1330 }
1331 }
1332 }
1333 false
1334}
1335
1336#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1337#[allow(clippy::too_many_arguments)]
1338unsafe fn macd_wave_signal_pro_into_raw(
1339 open_ptr: *const f64,
1340 high_ptr: *const f64,
1341 low_ptr: *const f64,
1342 close_ptr: *const f64,
1343 diff_ptr: *mut f64,
1344 dea_ptr: *mut f64,
1345 macd_histogram_ptr: *mut f64,
1346 line_convergence_ptr: *mut f64,
1347 buy_signal_ptr: *mut f64,
1348 sell_signal_ptr: *mut f64,
1349 len: usize,
1350 kernel: Kernel,
1351) -> Result<(), JsValue> {
1352 let open = std::slice::from_raw_parts(open_ptr, len);
1353 let high = std::slice::from_raw_parts(high_ptr, len);
1354 let low = std::slice::from_raw_parts(low_ptr, len);
1355 let close = std::slice::from_raw_parts(close_ptr, len);
1356 let input =
1357 MacdWaveSignalProInput::from_slices(open, high, low, close, MacdWaveSignalProParams);
1358
1359 let output_ptrs = [
1360 diff_ptr as usize,
1361 dea_ptr as usize,
1362 macd_histogram_ptr as usize,
1363 line_convergence_ptr as usize,
1364 buy_signal_ptr as usize,
1365 sell_signal_ptr as usize,
1366 ];
1367 let need_temp = output_ptrs.iter().any(|&ptr| {
1368 ptr == open_ptr as usize
1369 || ptr == high_ptr as usize
1370 || ptr == low_ptr as usize
1371 || ptr == close_ptr as usize
1372 }) || has_duplicate_ptrs(&output_ptrs);
1373
1374 if need_temp {
1375 let mut diff = vec![0.0; len];
1376 let mut dea = vec![0.0; len];
1377 let mut macd_histogram = vec![0.0; len];
1378 let mut line_convergence = vec![0.0; len];
1379 let mut buy_signal = vec![0.0; len];
1380 let mut sell_signal = vec![0.0; len];
1381 macd_wave_signal_pro_into_slices(
1382 &mut diff,
1383 &mut dea,
1384 &mut macd_histogram,
1385 &mut line_convergence,
1386 &mut buy_signal,
1387 &mut sell_signal,
1388 &input,
1389 kernel,
1390 )
1391 .map_err(|e| JsValue::from_str(&e.to_string()))?;
1392 std::slice::from_raw_parts_mut(diff_ptr, len).copy_from_slice(&diff);
1393 std::slice::from_raw_parts_mut(dea_ptr, len).copy_from_slice(&dea);
1394 std::slice::from_raw_parts_mut(macd_histogram_ptr, len).copy_from_slice(&macd_histogram);
1395 std::slice::from_raw_parts_mut(line_convergence_ptr, len)
1396 .copy_from_slice(&line_convergence);
1397 std::slice::from_raw_parts_mut(buy_signal_ptr, len).copy_from_slice(&buy_signal);
1398 std::slice::from_raw_parts_mut(sell_signal_ptr, len).copy_from_slice(&sell_signal);
1399 } else {
1400 macd_wave_signal_pro_into_slices(
1401 std::slice::from_raw_parts_mut(diff_ptr, len),
1402 std::slice::from_raw_parts_mut(dea_ptr, len),
1403 std::slice::from_raw_parts_mut(macd_histogram_ptr, len),
1404 std::slice::from_raw_parts_mut(line_convergence_ptr, len),
1405 std::slice::from_raw_parts_mut(buy_signal_ptr, len),
1406 std::slice::from_raw_parts_mut(sell_signal_ptr, len),
1407 &input,
1408 kernel,
1409 )
1410 .map_err(|e| JsValue::from_str(&e.to_string()))?;
1411 }
1412
1413 Ok(())
1414}
1415
1416#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1417#[wasm_bindgen]
1418pub struct MacdWaveSignalProContext {
1419 kernel: Kernel,
1420}
1421
1422#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1423#[wasm_bindgen]
1424impl MacdWaveSignalProContext {
1425 #[wasm_bindgen(constructor)]
1426 pub fn new() -> MacdWaveSignalProContext {
1427 MacdWaveSignalProContext {
1428 kernel: detect_best_kernel(),
1429 }
1430 }
1431
1432 #[wasm_bindgen]
1433 #[allow(clippy::too_many_arguments)]
1434 pub fn update_into(
1435 &self,
1436 open_ptr: *const f64,
1437 high_ptr: *const f64,
1438 low_ptr: *const f64,
1439 close_ptr: *const f64,
1440 diff_ptr: *mut f64,
1441 dea_ptr: *mut f64,
1442 macd_histogram_ptr: *mut f64,
1443 line_convergence_ptr: *mut f64,
1444 buy_signal_ptr: *mut f64,
1445 sell_signal_ptr: *mut f64,
1446 len: usize,
1447 ) -> Result<(), JsValue> {
1448 if open_ptr.is_null()
1449 || high_ptr.is_null()
1450 || low_ptr.is_null()
1451 || close_ptr.is_null()
1452 || diff_ptr.is_null()
1453 || dea_ptr.is_null()
1454 || macd_histogram_ptr.is_null()
1455 || line_convergence_ptr.is_null()
1456 || buy_signal_ptr.is_null()
1457 || sell_signal_ptr.is_null()
1458 {
1459 return Err(JsValue::from_str("Null pointer provided"));
1460 }
1461
1462 unsafe {
1463 macd_wave_signal_pro_into_raw(
1464 open_ptr,
1465 high_ptr,
1466 low_ptr,
1467 close_ptr,
1468 diff_ptr,
1469 dea_ptr,
1470 macd_histogram_ptr,
1471 line_convergence_ptr,
1472 buy_signal_ptr,
1473 sell_signal_ptr,
1474 len,
1475 self.kernel,
1476 )?;
1477 }
1478 Ok(())
1479 }
1480
1481 pub fn get_warmup_period(&self) -> usize {
1482 LINE_LONG_PERIOD - 1
1483 }
1484}
1485
1486#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1487#[wasm_bindgen]
1488#[allow(clippy::too_many_arguments)]
1489pub fn macd_wave_signal_pro_into(
1490 open_ptr: *const f64,
1491 high_ptr: *const f64,
1492 low_ptr: *const f64,
1493 close_ptr: *const f64,
1494 diff_ptr: *mut f64,
1495 dea_ptr: *mut f64,
1496 macd_histogram_ptr: *mut f64,
1497 line_convergence_ptr: *mut f64,
1498 buy_signal_ptr: *mut f64,
1499 sell_signal_ptr: *mut f64,
1500 len: usize,
1501) -> Result<(), JsValue> {
1502 if open_ptr.is_null()
1503 || high_ptr.is_null()
1504 || low_ptr.is_null()
1505 || close_ptr.is_null()
1506 || diff_ptr.is_null()
1507 || dea_ptr.is_null()
1508 || macd_histogram_ptr.is_null()
1509 || line_convergence_ptr.is_null()
1510 || buy_signal_ptr.is_null()
1511 || sell_signal_ptr.is_null()
1512 {
1513 return Err(JsValue::from_str("Null pointer provided"));
1514 }
1515
1516 unsafe {
1517 macd_wave_signal_pro_into_raw(
1518 open_ptr,
1519 high_ptr,
1520 low_ptr,
1521 close_ptr,
1522 diff_ptr,
1523 dea_ptr,
1524 macd_histogram_ptr,
1525 line_convergence_ptr,
1526 buy_signal_ptr,
1527 sell_signal_ptr,
1528 len,
1529 Kernel::Auto,
1530 )?;
1531 }
1532
1533 Ok(())
1534}
1535
1536#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1537#[derive(Serialize, Deserialize)]
1538pub struct MacdWaveSignalProBatchConfig {}
1539
1540#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1541#[derive(Serialize, Deserialize)]
1542pub struct MacdWaveSignalProBatchJsOutput {
1543 pub diff: Vec<f64>,
1544 pub dea: Vec<f64>,
1545 pub macd_histogram: Vec<f64>,
1546 pub line_convergence: Vec<f64>,
1547 pub buy_signal: Vec<f64>,
1548 pub sell_signal: Vec<f64>,
1549 pub combos: Vec<MacdWaveSignalProParams>,
1550 pub rows: usize,
1551 pub cols: usize,
1552}
1553
1554#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1555#[wasm_bindgen(js_name = "macd_wave_signal_pro_batch_js")]
1556pub fn macd_wave_signal_pro_batch_js(
1557 open: &[f64],
1558 high: &[f64],
1559 low: &[f64],
1560 close: &[f64],
1561 config: JsValue,
1562) -> Result<JsValue, JsValue> {
1563 let _: MacdWaveSignalProBatchConfig = serde_wasm_bindgen::from_value(config)
1564 .map_err(|e| JsValue::from_str(&format!("Invalid config: {e}")))?;
1565 let out = macd_wave_signal_pro_batch_with_kernel(
1566 open,
1567 high,
1568 low,
1569 close,
1570 &MacdWaveSignalProBatchRange,
1571 Kernel::Auto,
1572 )
1573 .map_err(|e| JsValue::from_str(&e.to_string()))?;
1574 serde_wasm_bindgen::to_value(&MacdWaveSignalProBatchJsOutput {
1575 diff: out.diff,
1576 dea: out.dea,
1577 macd_histogram: out.macd_histogram,
1578 line_convergence: out.line_convergence,
1579 buy_signal: out.buy_signal,
1580 sell_signal: out.sell_signal,
1581 combos: out.combos,
1582 rows: out.rows,
1583 cols: out.cols,
1584 })
1585 .map_err(|e| JsValue::from_str(&e.to_string()))
1586}
1587
1588#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
1589#[wasm_bindgen]
1590#[allow(clippy::too_many_arguments)]
1591pub fn macd_wave_signal_pro_batch_into(
1592 open_ptr: *const f64,
1593 high_ptr: *const f64,
1594 low_ptr: *const f64,
1595 close_ptr: *const f64,
1596 diff_ptr: *mut f64,
1597 dea_ptr: *mut f64,
1598 macd_histogram_ptr: *mut f64,
1599 line_convergence_ptr: *mut f64,
1600 buy_signal_ptr: *mut f64,
1601 sell_signal_ptr: *mut f64,
1602 len: usize,
1603) -> Result<usize, JsValue> {
1604 if open_ptr.is_null()
1605 || high_ptr.is_null()
1606 || low_ptr.is_null()
1607 || close_ptr.is_null()
1608 || diff_ptr.is_null()
1609 || dea_ptr.is_null()
1610 || macd_histogram_ptr.is_null()
1611 || line_convergence_ptr.is_null()
1612 || buy_signal_ptr.is_null()
1613 || sell_signal_ptr.is_null()
1614 {
1615 return Err(JsValue::from_str("Null pointer provided"));
1616 }
1617
1618 unsafe {
1619 let open = std::slice::from_raw_parts(open_ptr, len);
1620 let high = std::slice::from_raw_parts(high_ptr, len);
1621 let low = std::slice::from_raw_parts(low_ptr, len);
1622 let close = std::slice::from_raw_parts(close_ptr, len);
1623 macd_wave_signal_pro_batch_inner_into(
1624 open,
1625 high,
1626 low,
1627 close,
1628 &MacdWaveSignalProBatchRange,
1629 Kernel::Auto,
1630 false,
1631 std::slice::from_raw_parts_mut(diff_ptr, len),
1632 std::slice::from_raw_parts_mut(dea_ptr, len),
1633 std::slice::from_raw_parts_mut(macd_histogram_ptr, len),
1634 std::slice::from_raw_parts_mut(line_convergence_ptr, len),
1635 std::slice::from_raw_parts_mut(buy_signal_ptr, len),
1636 std::slice::from_raw_parts_mut(sell_signal_ptr, len),
1637 )
1638 .map_err(|e| JsValue::from_str(&e.to_string()))?;
1639 }
1640 Ok(1)
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645 use super::*;
1646 use std::error::Error;
1647
1648 fn assert_series_eq(actual: &[f64], expected: &[f64]) {
1649 assert_eq!(actual.len(), expected.len());
1650 for (&a, &b) in actual.iter().zip(expected.iter()) {
1651 if a.is_nan() && b.is_nan() {
1652 continue;
1653 }
1654 assert!(
1655 (a - b).abs() <= 1e-12,
1656 "series mismatch: expected {b}, got {a}"
1657 );
1658 }
1659 }
1660
1661 fn sample_ohlc(length: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
1662 let mut open = Vec::with_capacity(length);
1663 let mut high = Vec::with_capacity(length);
1664 let mut low = Vec::with_capacity(length);
1665 let mut close = Vec::with_capacity(length);
1666 for i in 0..length {
1667 let x = i as f64;
1668 let o = 100.0 + x * 0.09 + (x * 0.07).sin() * 0.8;
1669 let c = o + (x * 0.11).cos() * 1.1;
1670 let h = o.max(c) + 0.6 + (x * 0.03).sin().abs() * 0.2;
1671 let l = o.min(c) - 0.6 - (x * 0.05).cos().abs() * 0.2;
1672 open.push(o);
1673 high.push(h);
1674 low.push(l);
1675 close.push(c);
1676 }
1677 (open, high, low, close)
1678 }
1679
1680 #[test]
1681 fn macd_wave_signal_pro_output_contract() -> Result<(), Box<dyn Error>> {
1682 let (open, high, low, close) = sample_ohlc(256);
1683 let input = MacdWaveSignalProInput::from_slices(
1684 &open,
1685 &high,
1686 &low,
1687 &close,
1688 MacdWaveSignalProParams,
1689 );
1690 let out = macd_wave_signal_pro_with_kernel(&input, Kernel::Scalar)?;
1691 assert_eq!(out.diff.len(), close.len());
1692 assert_eq!(out.dea.len(), close.len());
1693 assert_eq!(out.macd_histogram.len(), close.len());
1694 assert_eq!(out.line_convergence.len(), close.len());
1695 assert_eq!(out.buy_signal.len(), close.len());
1696 assert_eq!(out.sell_signal.len(), close.len());
1697 for value in out.buy_signal.iter().copied().filter(|v| v.is_finite()) {
1698 assert!(value == 0.0 || value == 1.0);
1699 }
1700 for value in out.sell_signal.iter().copied().filter(|v| v.is_finite()) {
1701 assert!(value == 0.0 || value == 1.0);
1702 }
1703 Ok(())
1704 }
1705
1706 #[test]
1707 fn macd_wave_signal_pro_into_matches_api() -> Result<(), Box<dyn Error>> {
1708 let (open, high, low, close) = sample_ohlc(192);
1709 let input = MacdWaveSignalProInput::from_slices(
1710 &open,
1711 &high,
1712 &low,
1713 &close,
1714 MacdWaveSignalProParams,
1715 );
1716 let out = macd_wave_signal_pro(&input)?;
1717 let len = close.len();
1718 let mut diff = vec![0.0; len];
1719 let mut dea = vec![0.0; len];
1720 let mut macd = vec![0.0; len];
1721 let mut line = vec![0.0; len];
1722 let mut buy = vec![0.0; len];
1723 let mut sell = vec![0.0; len];
1724 macd_wave_signal_pro_into(
1725 &input, &mut diff, &mut dea, &mut macd, &mut line, &mut buy, &mut sell,
1726 )?;
1727 assert_series_eq(&diff, &out.diff);
1728 assert_series_eq(&dea, &out.dea);
1729 assert_series_eq(&macd, &out.macd_histogram);
1730 assert_series_eq(&line, &out.line_convergence);
1731 assert_series_eq(&buy, &out.buy_signal);
1732 assert_series_eq(&sell, &out.sell_signal);
1733 Ok(())
1734 }
1735
1736 #[test]
1737 fn macd_wave_signal_pro_stream_matches_batch_with_reset() -> Result<(), Box<dyn Error>> {
1738 let (mut open, mut high, mut low, mut close) = sample_ohlc(220);
1739 open[90] = f64::NAN;
1740 high[90] = f64::NAN;
1741 low[90] = f64::NAN;
1742 close[90] = f64::NAN;
1743
1744 let input = MacdWaveSignalProInput::from_slices(
1745 &open,
1746 &high,
1747 &low,
1748 &close,
1749 MacdWaveSignalProParams,
1750 );
1751 let batch = macd_wave_signal_pro(&input)?;
1752 let mut stream = MacdWaveSignalProStream::try_new(MacdWaveSignalProParams)?;
1753
1754 let mut diff = Vec::with_capacity(close.len());
1755 let mut dea = Vec::with_capacity(close.len());
1756 let mut macd = Vec::with_capacity(close.len());
1757 let mut line = Vec::with_capacity(close.len());
1758 let mut buy = Vec::with_capacity(close.len());
1759 let mut sell = Vec::with_capacity(close.len());
1760
1761 for i in 0..close.len() {
1762 if let Some(point) = stream.update(open[i], high[i], low[i], close[i]) {
1763 diff.push(point.diff);
1764 dea.push(point.dea);
1765 macd.push(point.macd_histogram);
1766 line.push(point.line_convergence);
1767 buy.push(point.buy_signal);
1768 sell.push(point.sell_signal);
1769 } else {
1770 diff.push(f64::NAN);
1771 dea.push(f64::NAN);
1772 macd.push(f64::NAN);
1773 line.push(f64::NAN);
1774 buy.push(f64::NAN);
1775 sell.push(f64::NAN);
1776 }
1777 }
1778
1779 assert_series_eq(&diff, &batch.diff);
1780 assert_series_eq(&dea, &batch.dea);
1781 assert_series_eq(&macd, &batch.macd_histogram);
1782 assert_series_eq(&line, &batch.line_convergence);
1783 assert_series_eq(&buy, &batch.buy_signal);
1784 assert_series_eq(&sell, &batch.sell_signal);
1785 Ok(())
1786 }
1787
1788 #[test]
1789 fn macd_wave_signal_pro_batch_matches_single() -> Result<(), Box<dyn Error>> {
1790 let (open, high, low, close) = sample_ohlc(160);
1791 let batch = macd_wave_signal_pro_batch_with_kernel(
1792 &open,
1793 &high,
1794 &low,
1795 &close,
1796 &MacdWaveSignalProBatchRange,
1797 Kernel::ScalarBatch,
1798 )?;
1799 let single = macd_wave_signal_pro(&MacdWaveSignalProInput::from_slices(
1800 &open,
1801 &high,
1802 &low,
1803 &close,
1804 MacdWaveSignalProParams,
1805 ))?;
1806 assert_eq!(batch.rows, 1);
1807 assert_eq!(batch.cols, close.len());
1808 assert_series_eq(&batch.diff, &single.diff);
1809 assert_series_eq(&batch.dea, &single.dea);
1810 assert_series_eq(&batch.macd_histogram, &single.macd_histogram);
1811 assert_series_eq(&batch.line_convergence, &single.line_convergence);
1812 assert_series_eq(&batch.buy_signal, &single.buy_signal);
1813 assert_series_eq(&batch.sell_signal, &single.sell_signal);
1814 Ok(())
1815 }
1816
1817 #[test]
1818 fn macd_wave_signal_pro_rejects_short_valid_history() {
1819 let (open, high, low, close) = sample_ohlc(32);
1820 let input = MacdWaveSignalProInput::from_slices(
1821 &open,
1822 &high,
1823 &low,
1824 &close,
1825 MacdWaveSignalProParams,
1826 );
1827 let err = macd_wave_signal_pro(&input).unwrap_err();
1828 match err {
1829 MacdWaveSignalProError::NotEnoughValidData { needed, valid } => {
1830 assert_eq!(needed, 40);
1831 assert_eq!(valid, 32);
1832 }
1833 other => panic!("unexpected error: {other:?}"),
1834 }
1835 }
1836}