1#[cfg(feature = "python")]
2use crate::utilities::kernel_validation::validate_kernel;
3#[cfg(feature = "python")]
4use numpy::{IntoPyArray, PyArray1, PyArrayMethods, PyReadonlyArray1};
5#[cfg(feature = "python")]
6use pyo3::exceptions::PyValueError;
7#[cfg(feature = "python")]
8use pyo3::prelude::*;
9
10#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
11use serde::{Deserialize, Serialize};
12#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
13use wasm_bindgen::prelude::*;
14
15use crate::indicators::cci::{cci_with_kernel, CciInput, CciParams};
16use crate::indicators::ema::{ema_into_slice, ema_with_kernel, EmaInput, EmaParams};
17use crate::indicators::mfi::{mfi_with_kernel, MfiInput, MfiParams};
18use crate::indicators::moving_averages::sma::{
19 sma_into_slice, sma_with_kernel, SmaInput, SmaParams,
20};
21use crate::indicators::rsi::{rsi_with_kernel, RsiInput, RsiParams};
22use crate::indicators::tsi::{tsi_with_kernel, TsiInput, TsiParams};
23use crate::indicators::willr::{willr_with_kernel, WillrInput, WillrParams};
24use crate::utilities::data_loader::Candles;
25use crate::utilities::enums::Kernel;
26use crate::utilities::helpers::{
27 alloc_with_nan_prefix, detect_best_batch_kernel, detect_best_kernel, init_matrix_prefixes,
28 make_uninit_matrix,
29};
30#[cfg(not(target_arch = "wasm32"))]
31use rayon::prelude::*;
32use std::error::Error;
33use std::mem::MaybeUninit;
34use thiserror::Error;
35
36#[derive(Debug, Clone, Copy, PartialEq)]
37#[cfg_attr(
38 all(target_arch = "wasm32", feature = "wasm"),
39 derive(Serialize, Deserialize)
40)]
41pub enum ModGodModeMode {
42 Godmode,
43 Tradition,
44 GodmodeMg,
45 TraditionMg,
46}
47
48impl Default for ModGodModeMode {
49 fn default() -> Self {
50 Self::TraditionMg
51 }
52}
53
54impl std::str::FromStr for ModGodModeMode {
55 type Err = String;
56
57 fn from_str(s: &str) -> Result<Self, Self::Err> {
58 match s.to_lowercase().as_str() {
59 "godmode" => Ok(Self::Godmode),
60 "tradition" => Ok(Self::Tradition),
61 "godmode_mg" => Ok(Self::GodmodeMg),
62 "tradition_mg" => Ok(Self::TraditionMg),
63 _ => Err(format!("Unknown mode: {}", s)),
64 }
65 }
66}
67
68#[derive(Debug, Clone)]
69pub enum ModGodModeData<'a> {
70 Candles {
71 candles: &'a Candles,
72 },
73 Slices {
74 high: &'a [f64],
75 low: &'a [f64],
76 close: &'a [f64],
77 volume: Option<&'a [f64]>,
78 },
79}
80
81#[derive(Debug, Clone)]
82#[cfg_attr(
83 all(target_arch = "wasm32", feature = "wasm"),
84 derive(Serialize, Deserialize)
85)]
86pub struct ModGodModeOutput {
87 pub wavetrend: Vec<f64>,
88 pub signal: Vec<f64>,
89 pub histogram: Vec<f64>,
90}
91
92#[derive(Debug, Clone)]
93#[cfg_attr(
94 all(target_arch = "wasm32", feature = "wasm"),
95 derive(Serialize, Deserialize)
96)]
97pub struct ModGodModeParams {
98 pub n1: Option<usize>,
99 pub n2: Option<usize>,
100 pub n3: Option<usize>,
101 pub mode: Option<ModGodModeMode>,
102 pub use_volume: Option<bool>,
103}
104
105impl Default for ModGodModeParams {
106 fn default() -> Self {
107 Self {
108 n1: Some(17),
109 n2: Some(6),
110 n3: Some(4),
111 mode: Some(ModGodModeMode::TraditionMg),
112 use_volume: Some(true),
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
118pub struct ModGodModeInput<'a> {
119 pub data: ModGodModeData<'a>,
120 pub params: ModGodModeParams,
121}
122
123impl<'a> ModGodModeInput<'a> {
124 #[inline]
125 pub fn from_candles(candles: &'a Candles, params: ModGodModeParams) -> Self {
126 Self {
127 data: ModGodModeData::Candles { candles },
128 params,
129 }
130 }
131
132 #[inline]
133 pub fn from_slices(
134 high: &'a [f64],
135 low: &'a [f64],
136 close: &'a [f64],
137 volume: Option<&'a [f64]>,
138 params: ModGodModeParams,
139 ) -> Self {
140 Self {
141 data: ModGodModeData::Slices {
142 high,
143 low,
144 close,
145 volume,
146 },
147 params,
148 }
149 }
150
151 #[inline]
152 pub fn with_default_candles(candles: &'a Candles) -> Self {
153 Self::from_candles(candles, ModGodModeParams::default())
154 }
155
156 #[inline]
157 pub fn get_n1(&self) -> usize {
158 self.params.n1.unwrap_or(17)
159 }
160
161 #[inline]
162 pub fn get_n2(&self) -> usize {
163 self.params.n2.unwrap_or(6)
164 }
165
166 #[inline]
167 pub fn get_n3(&self) -> usize {
168 self.params.n3.unwrap_or(4)
169 }
170
171 #[inline]
172 pub fn get_mode(&self) -> ModGodModeMode {
173 self.params.mode.unwrap_or_default()
174 }
175
176 #[inline]
177 pub fn get_use_volume(&self) -> bool {
178 self.params.use_volume.unwrap_or(false)
179 }
180}
181
182#[derive(Debug, Error)]
183pub enum ModGodModeError {
184 #[error("mod_god_mode: Input data slice is empty.")]
185 EmptyInputData,
186
187 #[error("mod_god_mode: All values are NaN.")]
188 AllValuesNaN,
189
190 #[error("mod_god_mode: invalid periods: n1={n1}, n2={n2}, n3={n3}, data_len={data_len}")]
191 InvalidPeriod {
192 n1: usize,
193 n2: usize,
194 n3: usize,
195 data_len: usize,
196 },
197
198 #[error("mod_god_mode: Not enough valid data: needed={needed}, valid={valid}")]
199 NotEnoughValidData { needed: usize, valid: usize },
200
201 #[error("mod_god_mode: output slice length mismatch: expected={expected}, got={got}")]
202 OutputLengthMismatch { expected: usize, got: usize },
203
204 #[error("mod_god_mode: invalid range expansion: start={start}, end={end}, step={step}")]
205 InvalidRange {
206 start: usize,
207 end: usize,
208 step: usize,
209 },
210
211 #[error("mod_god_mode: invalid kernel for batch path: {0:?}")]
212 InvalidKernelForBatch(Kernel),
213
214 #[error("mod_god_mode: invalid input: {0}")]
215 InvalidInput(String),
216
217 #[error("mod_god_mode: calculation error: {0}")]
218 CalculationError(String),
219}
220
221fn calculate_tci(
222 close: &[f64],
223 n1: usize,
224 n2: usize,
225 kernel: Kernel,
226) -> Result<Vec<f64>, ModGodModeError> {
227 let ema1_params = EmaParams { period: Some(n1) };
228 let ema1_input = EmaInput::from_slice(close, ema1_params);
229 let ema1 = ema_with_kernel(&ema1_input, kernel)
230 .map_err(|e| ModGodModeError::CalculationError(format!("TCI EMA1: {}", e)))?;
231
232 let mut deviations = vec![f64::NAN; close.len()];
233 let mut abs_deviations = vec![f64::NAN; close.len()];
234
235 for i in 0..close.len() {
236 if !ema1.values[i].is_nan() {
237 deviations[i] = close[i] - ema1.values[i];
238 abs_deviations[i] = (close[i] - ema1.values[i]).abs();
239 }
240 }
241
242 let ema2_params = EmaParams { period: Some(n1) };
243 let ema2_input = EmaInput::from_slice(&abs_deviations, ema2_params);
244 let ema2 = ema_with_kernel(&ema2_input, kernel)
245 .map_err(|e| ModGodModeError::CalculationError(format!("TCI EMA2: {}", e)))?;
246
247 let mut normalized = vec![f64::NAN; close.len()];
248 for i in 0..close.len() {
249 if !deviations[i].is_nan() && !ema2.values[i].is_nan() && ema2.values[i] != 0.0 {
250 normalized[i] = deviations[i] / (0.025 * ema2.values[i]);
251 }
252 }
253
254 let ema3_params = EmaParams { period: Some(n2) };
255 let ema3_input = EmaInput::from_slice(&normalized, ema3_params);
256 let ema3 = ema_with_kernel(&ema3_input, kernel)
257 .map_err(|e| ModGodModeError::CalculationError(format!("TCI EMA3: {}", e)))?;
258
259 let mut tci = ema3.values;
260 for i in 0..tci.len() {
261 if !tci[i].is_nan() {
262 tci[i] += 50.0;
263 }
264 }
265
266 Ok(tci)
267}
268
269fn calculate_csi(
270 close: &[f64],
271 n1: usize,
272 n2: usize,
273 n3: usize,
274 kernel: Kernel,
275) -> Result<Vec<f64>, ModGodModeError> {
276 let rsi_params = RsiParams { period: Some(n3) };
277 let rsi_input = RsiInput::from_slice(close, rsi_params);
278 let rsi = rsi_with_kernel(&rsi_input, kernel)
279 .map_err(|e| ModGodModeError::CalculationError(format!("CSI RSI: {}", e)))?;
280
281 let tsi_params = TsiParams {
282 short_period: Some(n1),
283 long_period: Some(n2),
284 };
285 let tsi_input = TsiInput::from_slice(close, tsi_params);
286 let tsi = tsi_with_kernel(&tsi_input, kernel)
287 .map_err(|e| ModGodModeError::CalculationError(format!("CSI TSI: {}", e)))?;
288
289 let mut csi = vec![f64::NAN; close.len()];
290 for i in 0..close.len() {
291 if !rsi.values[i].is_nan() && !tsi.values[i].is_nan() {
292 csi[i] = (rsi.values[i] + (tsi.values[i] * 0.5 + 50.0)) / 2.0;
293 }
294 }
295
296 Ok(csi)
297}
298
299fn calculate_csi_mg(
300 close: &[f64],
301 n1: usize,
302 n2: usize,
303 n3: usize,
304 kernel: Kernel,
305) -> Result<Vec<f64>, ModGodModeError> {
306 let rsi_params = RsiParams { period: Some(n3) };
307 let rsi_input = RsiInput::from_slice(close, rsi_params);
308 let rsi = rsi_with_kernel(&rsi_input, kernel)
309 .map_err(|e| ModGodModeError::CalculationError(format!("CSI_MG RSI: {}", e)))?;
310
311 let mut pc_norm = vec![f64::NAN; close.len()];
312 for i in 1..close.len() {
313 let a = close[i - 1];
314 let b = close[i];
315 if a.is_nan() || b.is_nan() {
316 continue;
317 }
318 let avg = (a + b) * 0.5;
319 if avg != 0.0 {
320 pc_norm[i] = (b - a) / avg;
321 }
322 }
323
324 let e_num = ema_with_kernel(
325 &EmaInput::from_slice(&pc_norm, EmaParams { period: Some(n1) }),
326 kernel,
327 )
328 .map_err(|e| ModGodModeError::CalculationError(format!("CSI_MG EMA num1: {}", e)))?;
329 let e_num2 = ema_with_kernel(
330 &EmaInput::from_slice(&e_num.values, EmaParams { period: Some(n2) }),
331 kernel,
332 )
333 .map_err(|e| ModGodModeError::CalculationError(format!("CSI_MG EMA num2: {}", e)))?;
334
335 let mut apc = vec![f64::NAN; close.len()];
336 for i in 1..close.len() {
337 let a = close[i - 1];
338 let b = close[i];
339 if a.is_nan() || b.is_nan() {
340 continue;
341 }
342 apc[i] = (b - a).abs();
343 }
344
345 let e_den = ema_with_kernel(
346 &EmaInput::from_slice(&apc, EmaParams { period: Some(n1) }),
347 kernel,
348 )
349 .map_err(|e| ModGodModeError::CalculationError(format!("CSI_MG EMA den1: {}", e)))?;
350 let e_den2 = ema_with_kernel(
351 &EmaInput::from_slice(&e_den.values, EmaParams { period: Some(n2) }),
352 kernel,
353 )
354 .map_err(|e| ModGodModeError::CalculationError(format!("CSI_MG EMA den2: {}", e)))?;
355
356 let mut ttsi = vec![f64::NAN; close.len()];
357 for i in 0..close.len() {
358 let den = e_den2.values[i];
359 let num = e_num2.values[i];
360 if !num.is_nan() && !den.is_nan() && den != 0.0 {
361 ttsi[i] = 50.0 * (num / den) + 50.0;
362 }
363 }
364
365 let mut out = vec![f64::NAN; close.len()];
366 for i in 0..close.len() {
367 if !rsi.values[i].is_nan() && !ttsi[i].is_nan() {
368 out[i] = 0.5 * (rsi.values[i] + ttsi[i]);
369 }
370 }
371
372 Ok(out)
373}
374
375fn calculate_mf(
376 high: &[f64],
377 low: &[f64],
378 close: &[f64],
379 volume: Option<&[f64]>,
380 n: usize,
381 kernel: Kernel,
382) -> Result<Vec<f64>, ModGodModeError> {
383 let len = close.len();
384
385 if let Some(vol) = volume {
386 let mut typical_price = vec![0.0; len];
387 for i in 0..len {
388 typical_price[i] = (high[i] + low[i] + close[i]) / 3.0;
389 }
390
391 let mfi_params = MfiParams { period: Some(n) };
392 let mfi_input = MfiInput::from_slices(&typical_price, vol, mfi_params);
393 let mfi = mfi_with_kernel(&mfi_input, kernel)
394 .map_err(|e| ModGodModeError::CalculationError(format!("MF: {}", e)))?;
395
396 Ok(mfi.values)
397 } else {
398 let rsi_params = RsiParams { period: Some(n) };
399 let rsi_input = RsiInput::from_slice(close, rsi_params);
400 let rsi = rsi_with_kernel(&rsi_input, kernel)
401 .map_err(|e| ModGodModeError::CalculationError(format!("MF RSI: {}", e)))?;
402
403 Ok(rsi.values)
404 }
405}
406
407fn calculate_willy_pine(close: &[f64], n2: usize) -> Vec<f64> {
408 let len = close.len();
409 let mut out = vec![f64::NAN; len];
410 if len == 0 || n2 == 0 {
411 return out;
412 }
413
414 for i in 0..len {
415 if i + 1 < n2 {
416 continue;
417 }
418 let start = i + 1 - n2;
419 let mut hi = f64::NEG_INFINITY;
420 let mut lo = f64::INFINITY;
421 let mut ok = true;
422 for j in start..=i {
423 let v = close[j];
424 if v.is_nan() {
425 ok = false;
426 break;
427 }
428 if v > hi {
429 hi = v;
430 }
431 if v < lo {
432 lo = v;
433 }
434 }
435 if !ok {
436 continue;
437 }
438 let range = hi - lo;
439 if range != 0.0 && !close[i].is_nan() {
440 out[i] = 60.0 * (close[i] - hi) / range + 80.0;
441 }
442 }
443 out
444}
445
446fn calculate_cbci_pine(
447 close: &[f64],
448 n2: usize,
449 n3: usize,
450 kernel: Kernel,
451) -> Result<Vec<f64>, ModGodModeError> {
452 let r = rsi_with_kernel(
453 &RsiInput::from_slice(close, RsiParams { period: Some(n3) }),
454 kernel,
455 )
456 .map_err(|e| ModGodModeError::CalculationError(format!("CBCI RSI: {}", e)))?;
457
458 let len = close.len();
459 let mut mom = vec![f64::NAN; len];
460 for i in 0..len {
461 if i >= n2 {
462 let a = r.values[i];
463 let b = r.values[i - n2];
464 if !a.is_nan() && !b.is_nan() {
465 mom[i] = a - b;
466 }
467 }
468 }
469
470 let rsisma = ema_with_kernel(
471 &EmaInput::from_slice(&r.values, EmaParams { period: Some(n3) }),
472 kernel,
473 )
474 .map_err(|e| ModGodModeError::CalculationError(format!("CBCI EMA(RSI): {}", e)))?;
475
476 let mut out = vec![f64::NAN; len];
477 for i in 0..len {
478 let a = mom[i];
479 let b = rsisma.values[i];
480 if !a.is_nan() && !b.is_nan() {
481 out[i] = a + b;
482 }
483 }
484 Ok(out)
485}
486
487fn calculate_lrsi_pine(close: &[f64]) -> Vec<f64> {
488 let len = close.len();
489 let mut out = vec![f64::NAN; len];
490 let alpha = 0.7;
491 let one_minus = 1.0 - alpha;
492 let mut l0 = 0.0;
493 let mut l1 = 0.0;
494 let mut l2 = 0.0;
495 let mut l3 = 0.0;
496
497 for i in 0..len {
498 let x = close[i];
499 if x.is_nan() {
500 continue;
501 }
502
503 let prev_l0 = l0;
504 l0 = alpha * x + one_minus * prev_l0;
505
506 let prev_l1 = l1;
507 l1 = -(one_minus) * l0 + prev_l0 + one_minus * prev_l1;
508
509 let prev_l2 = l2;
510 l2 = -(one_minus) * l1 + prev_l1 + one_minus * prev_l2;
511
512 l3 = -(one_minus) * l2 + prev_l2 + one_minus * l3;
513
514 let cu = (l0 - l1).max(0.0) + (l1 - l2).max(0.0) + (l2 - l3).max(0.0);
515 let cd = (l1 - l0).max(0.0) + (l2 - l1).max(0.0) + (l3 - l2).max(0.0);
516 if cu + cd != 0.0 {
517 out[i] = 100.0 * cu / (cu + cd);
518 }
519 }
520 out
521}
522
523fn smooth_signal_sma6(wt1: &[f64], kernel: Kernel) -> Result<Vec<f64>, ModGodModeError> {
524 let sig = sma_with_kernel(
525 &SmaInput::from_slice(wt1, SmaParams { period: Some(6) }),
526 kernel,
527 )
528 .map_err(|e| ModGodModeError::CalculationError(format!("Signal SMA(6): {}", e)))?;
529 Ok(sig.values)
530}
531
532fn histogram_component_pine(
533 wt1: &[f64],
534 wt2: &[f64],
535 n3: usize,
536 kernel: Kernel,
537) -> Result<Vec<f64>, ModGodModeError> {
538 let len = wt1.len();
539 let mut tmp = vec![f64::NAN; len];
540 for i in 0..len {
541 let a = wt1[i];
542 let b = wt2[i];
543 if !a.is_nan() && !b.is_nan() {
544 tmp[i] = (a - b) * 2.0 + 50.0;
545 }
546 }
547 let out = ema_with_kernel(
548 &EmaInput::from_slice(&tmp, EmaParams { period: Some(n3) }),
549 kernel,
550 )
551 .map_err(|e| ModGodModeError::CalculationError(format!("Hist EMA: {}", e)))?;
552 Ok(out.values)
553}
554
555pub fn mod_god_mode(input: &ModGodModeInput) -> Result<ModGodModeOutput, ModGodModeError> {
556 mod_god_mode_with_kernel(input, Kernel::Auto)
557}
558
559#[inline]
560pub fn mod_god_mode_auto(input: &ModGodModeInput) -> Result<ModGodModeOutput, ModGodModeError> {
561 mod_god_mode_with_kernel(input, Kernel::Auto)
562}
563
564pub fn mod_god_mode_with_kernel(
565 input: &ModGodModeInput,
566 kernel: Kernel,
567) -> Result<ModGodModeOutput, ModGodModeError> {
568 let (high, low, close, volume) = match &input.data {
569 ModGodModeData::Candles { candles } => {
570 let vol = if input.get_use_volume() {
571 Some(candles.volume.as_slice())
572 } else {
573 None
574 };
575 (
576 candles.high.as_slice(),
577 candles.low.as_slice(),
578 candles.close.as_slice(),
579 vol,
580 )
581 }
582 ModGodModeData::Slices {
583 high,
584 low,
585 close,
586 volume,
587 } => {
588 let vol = if input.get_use_volume() {
589 *volume
590 } else {
591 None
592 };
593 (*high, *low, *close, vol)
594 }
595 };
596
597 let len = close.len();
598 if len == 0 {
599 return Err(ModGodModeError::EmptyInputData);
600 }
601 let first = close
602 .iter()
603 .position(|x| !x.is_nan())
604 .ok_or(ModGodModeError::AllValuesNaN)?;
605 let need = input.get_n1().max(input.get_n2()).max(input.get_n3());
606 if len - first < need {
607 return Err(ModGodModeError::NotEnoughValidData {
608 needed: need,
609 valid: len - first,
610 });
611 }
612 let warm = first + need - 1;
613
614 let mut wt = alloc_with_nan_prefix(len, warm);
615 let mut sig = alloc_with_nan_prefix(len, warm);
616 let mut hist = alloc_with_nan_prefix(len, warm);
617
618 let kern = match kernel {
619 Kernel::Auto => Kernel::Scalar,
620 k => k,
621 };
622
623 if false
624 && kern == Kernel::Scalar
625 && input.get_n1() == 17
626 && input.get_n2() == 6
627 && input.get_n3() == 4
628 && input.get_mode() == ModGodModeMode::TraditionMg
629 {
630 unsafe {
631 mod_god_mode_scalar_classic_tradition_mg(
632 high,
633 low,
634 close,
635 volume,
636 17,
637 6,
638 4,
639 first,
640 warm,
641 input.get_use_volume(),
642 &mut wt,
643 &mut sig,
644 &mut hist,
645 )?;
646 }
647 return Ok(ModGodModeOutput {
648 wavetrend: wt,
649 signal: sig,
650 histogram: hist,
651 });
652 }
653
654 mod_god_mode_into_slices(&mut wt, &mut sig, &mut hist, input, kern)?;
655
656 Ok(ModGodModeOutput {
657 wavetrend: wt,
658 signal: sig,
659 histogram: hist,
660 })
661}
662
663#[inline]
664pub fn mod_god_mode_into_slices(
665 dst_wavetrend: &mut [f64],
666 dst_signal: &mut [f64],
667 dst_hist: &mut [f64],
668 input: &ModGodModeInput,
669 kern: Kernel,
670) -> Result<(), ModGodModeError> {
671 let (high, low, close, volume) = match &input.data {
672 ModGodModeData::Candles { candles } => {
673 let vol = if input.get_use_volume() {
674 Some(candles.volume.as_slice())
675 } else {
676 None
677 };
678 (
679 candles.high.as_slice(),
680 candles.low.as_slice(),
681 candles.close.as_slice(),
682 vol,
683 )
684 }
685 ModGodModeData::Slices {
686 high,
687 low,
688 close,
689 volume,
690 } => {
691 let vol = if input.get_use_volume() {
692 *volume
693 } else {
694 None
695 };
696 (*high, *low, *close, vol)
697 }
698 };
699
700 let len = close.len();
701 if dst_wavetrend.len() != len || dst_signal.len() != len || dst_hist.len() != len {
702 let dst_len = dst_wavetrend
703 .len()
704 .min(dst_signal.len())
705 .min(dst_hist.len());
706 return Err(ModGodModeError::OutputLengthMismatch {
707 expected: len,
708 got: dst_len,
709 });
710 }
711
712 if len == 0 {
713 return Err(ModGodModeError::EmptyInputData);
714 }
715 let first = close
716 .iter()
717 .position(|x| !x.is_nan())
718 .ok_or(ModGodModeError::AllValuesNaN)?;
719 let n1 = input.get_n1();
720 let n2 = input.get_n2();
721 let n3 = input.get_n3();
722
723 if n1 == 0 || n2 == 0 || n3 == 0 {
724 return Err(ModGodModeError::InvalidPeriod {
725 n1,
726 n2,
727 n3,
728 data_len: len,
729 });
730 }
731
732 let need = n1.max(n2).max(n3);
733 if len - first < need {
734 return Err(ModGodModeError::NotEnoughValidData {
735 needed: need,
736 valid: len - first,
737 });
738 }
739
740 let warm = first + need - 1;
741 let actual = match kern {
742 Kernel::Auto => Kernel::Scalar,
743 k => k,
744 };
745
746 if actual == Kernel::Scalar {
747 let warm = first + need - 1;
748 unsafe {
749 mod_god_mode_scalar_fused_into_slices(
750 dst_wavetrend,
751 dst_signal,
752 dst_hist,
753 high,
754 low,
755 close,
756 volume,
757 n1,
758 n2,
759 n3,
760 input.get_mode(),
761 input.get_use_volume(),
762 first,
763 warm,
764 )?;
765 }
766
767 for v in &mut dst_wavetrend[..warm] {
768 *v = f64::NAN;
769 }
770 let sig_start = warm.saturating_add(6 - 1).min(len);
771 for v in &mut dst_signal[..sig_start] {
772 *v = f64::NAN;
773 }
774 for v in &mut dst_hist[..sig_start] {
775 *v = f64::NAN;
776 }
777 return Ok(());
778 }
779
780 #[cfg(all(
781 feature = "nightly-avx",
782 target_arch = "x86_64",
783 target_feature = "avx512f"
784 ))]
785 if actual == Kernel::Avx512 {
786 let warm = first + need - 1;
787 unsafe {
788 mod_god_mode_avx512_fused_into_slices(
789 dst_wavetrend,
790 dst_signal,
791 dst_hist,
792 high,
793 low,
794 close,
795 volume,
796 n1,
797 n2,
798 n3,
799 input.get_mode(),
800 input.get_use_volume(),
801 first,
802 warm,
803 )?;
804 }
805 let sig_start = warm.saturating_add(6 - 1).min(len);
806 for v in &mut dst_wavetrend[..warm] {
807 *v = f64::NAN;
808 }
809 for v in &mut dst_signal[..sig_start] {
810 *v = f64::NAN;
811 }
812 for v in &mut dst_hist[..sig_start] {
813 *v = f64::NAN;
814 }
815 return Ok(());
816 }
817
818 #[cfg(all(
819 feature = "nightly-avx",
820 target_arch = "x86_64",
821 target_feature = "avx2"
822 ))]
823 if actual == Kernel::Avx2 {
824 let warm = first + need - 1;
825 unsafe {
826 mod_god_mode_avx2_fused_into_slices(
827 dst_wavetrend,
828 dst_signal,
829 dst_hist,
830 high,
831 low,
832 close,
833 volume,
834 n1,
835 n2,
836 n3,
837 input.get_mode(),
838 input.get_use_volume(),
839 first,
840 warm,
841 )?;
842 }
843 let sig_start = warm.saturating_add(6 - 1).min(len);
844 for v in &mut dst_wavetrend[..warm] {
845 *v = f64::NAN;
846 }
847 for v in &mut dst_signal[..sig_start] {
848 *v = f64::NAN;
849 }
850 for v in &mut dst_hist[..sig_start] {
851 *v = f64::NAN;
852 }
853 return Ok(());
854 }
855
856 let tci = calculate_tci(close, n1, n2, actual)?;
857 let mf = calculate_mf(high, low, close, volume, n3, actual)?;
858
859 match input.get_mode() {
860 ModGodModeMode::Godmode => {
861 let csi = calculate_csi(close, n1, n2, n3, actual)?;
862 let willy = calculate_willy_pine(close, n2);
863
864 for i in warm..len {
865 let mut sum = 0.0;
866 let mut count = 0;
867 if !tci[i].is_nan() {
868 sum += tci[i];
869 count += 1;
870 }
871 if !csi[i].is_nan() {
872 sum += csi[i];
873 count += 1;
874 }
875 if !mf[i].is_nan() {
876 sum += mf[i];
877 count += 1;
878 }
879 if !willy[i].is_nan() {
880 sum += willy[i];
881 count += 1;
882 }
883 if count > 0 {
884 dst_wavetrend[i] = sum / count as f64;
885 }
886 }
887 }
888 ModGodModeMode::Tradition => {
889 let rsi = rsi_with_kernel(
890 &RsiInput::from_slice(close, RsiParams { period: Some(n3) }),
891 actual,
892 )
893 .map_err(|e| ModGodModeError::CalculationError(format!("RSI: {}", e)))?;
894
895 for i in warm..len {
896 let mut sum = 0.0;
897 let mut count = 0;
898 if !tci[i].is_nan() {
899 sum += tci[i];
900 count += 1;
901 }
902 if !mf[i].is_nan() {
903 sum += mf[i];
904 count += 1;
905 }
906 if !rsi.values[i].is_nan() {
907 sum += rsi.values[i];
908 count += 1;
909 }
910 if count > 0 {
911 dst_wavetrend[i] = sum / count as f64;
912 }
913 }
914 }
915 ModGodModeMode::GodmodeMg => {
916 let csi_mg = calculate_csi_mg(close, n1, n2, n3, actual)?;
917 let willy = calculate_willy_pine(close, n2);
918 let cbci = calculate_cbci_pine(close, n2, n3, actual)?;
919 let lrsi = calculate_lrsi_pine(close);
920
921 for i in warm..len {
922 let mut sum = 0.0;
923 let mut count = 0;
924 if !tci[i].is_nan() {
925 sum += tci[i];
926 count += 1;
927 }
928 if !csi_mg[i].is_nan() {
929 sum += csi_mg[i];
930 count += 1;
931 }
932 if !mf[i].is_nan() {
933 sum += mf[i];
934 count += 1;
935 }
936 if !willy[i].is_nan() {
937 sum += willy[i];
938 count += 1;
939 }
940 if !cbci[i].is_nan() {
941 sum += cbci[i];
942 count += 1;
943 }
944 if !lrsi[i].is_nan() {
945 sum += lrsi[i];
946 count += 1;
947 }
948 if count > 0 {
949 dst_wavetrend[i] = sum / count as f64;
950 }
951 }
952 }
953 ModGodModeMode::TraditionMg => {
954 let rsi = rsi_with_kernel(
955 &RsiInput::from_slice(close, RsiParams { period: Some(n3) }),
956 actual,
957 )
958 .map_err(|e| ModGodModeError::CalculationError(format!("RSI: {}", e)))?;
959 let cbci = calculate_cbci_pine(close, n2, n3, actual)?;
960 let lrsi = calculate_lrsi_pine(close);
961
962 for i in warm..len {
963 let mut sum = 0.0;
964 let mut count = 0;
965 if !tci[i].is_nan() {
966 sum += tci[i];
967 count += 1;
968 }
969 if !mf[i].is_nan() {
970 sum += mf[i];
971 count += 1;
972 }
973 if !rsi.values[i].is_nan() {
974 sum += rsi.values[i];
975 count += 1;
976 }
977 if !cbci[i].is_nan() {
978 sum += cbci[i];
979 count += 1;
980 }
981 if !lrsi[i].is_nan() {
982 sum += lrsi[i];
983 count += 1;
984 }
985 if count > 0 {
986 dst_wavetrend[i] = sum / count as f64;
987 }
988 }
989 }
990 }
991
992 let wt_valid = dst_wavetrend[warm..].len();
993 if wt_valid >= 6 {
994 sma_into_slice(
995 dst_signal,
996 &SmaInput::from_slice(dst_wavetrend, SmaParams { period: Some(6) }),
997 actual,
998 )
999 .map_err(|e| ModGodModeError::CalculationError(format!("Signal SMA(6): {}", e)))?;
1000 } else {
1001 dst_signal.fill(f64::NAN);
1002 }
1003
1004 let len = dst_wavetrend.len();
1005
1006 let sig_valid_start = dst_signal.iter().position(|x| !x.is_nan()).unwrap_or(len);
1007 let sig_valid = if sig_valid_start < len {
1008 len - sig_valid_start
1009 } else {
1010 0
1011 };
1012
1013 if sig_valid >= n3 {
1014 let mut tmp_mu = make_uninit_matrix(1, len);
1015 init_matrix_prefixes(&mut tmp_mu, len, &[sig_valid_start]);
1016 let tmp = unsafe { core::slice::from_raw_parts_mut(tmp_mu.as_mut_ptr() as *mut f64, len) };
1017
1018 for i in sig_valid_start..len {
1019 tmp[i] = (dst_wavetrend[i] - dst_signal[i]) * 2.0 + 50.0;
1020 }
1021
1022 ema_into_slice(
1023 dst_hist,
1024 &EmaInput::from_slice(tmp, EmaParams { period: Some(n3) }),
1025 actual,
1026 )
1027 .map_err(|e| ModGodModeError::CalculationError(format!("Hist EMA: {}", e)))?;
1028 } else {
1029 dst_hist.fill(f64::NAN);
1030 }
1031
1032 for v in &mut dst_wavetrend[..warm] {
1033 *v = f64::NAN;
1034 }
1035 for v in &mut dst_signal[..warm] {
1036 *v = f64::NAN;
1037 }
1038 for v in &mut dst_hist[..warm] {
1039 *v = f64::NAN;
1040 }
1041
1042 Ok(())
1043}
1044
1045#[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
1046#[inline]
1047pub fn mod_god_mode_into(
1048 input: &ModGodModeInput,
1049 out_wavetrend: &mut [f64],
1050 out_signal: &mut [f64],
1051 out_histogram: &mut [f64],
1052) -> Result<(), ModGodModeError> {
1053 let (high, low, close, volume) = match &input.data {
1054 ModGodModeData::Candles { candles } => {
1055 let vol = if input.get_use_volume() {
1056 Some(candles.volume.as_slice())
1057 } else {
1058 None
1059 };
1060 (
1061 candles.high.as_slice(),
1062 candles.low.as_slice(),
1063 candles.close.as_slice(),
1064 vol,
1065 )
1066 }
1067 ModGodModeData::Slices {
1068 high,
1069 low,
1070 close,
1071 volume,
1072 } => {
1073 let vol = if input.get_use_volume() {
1074 *volume
1075 } else {
1076 None
1077 };
1078 (*high, *low, *close, vol)
1079 }
1080 };
1081
1082 let len = close.len();
1083 if out_wavetrend.len() != len || out_signal.len() != len || out_histogram.len() != len {
1084 let dst_len = out_wavetrend
1085 .len()
1086 .min(out_signal.len())
1087 .min(out_histogram.len());
1088 return Err(ModGodModeError::OutputLengthMismatch {
1089 expected: len,
1090 got: dst_len,
1091 });
1092 }
1093
1094 if len == 0 {
1095 return Err(ModGodModeError::EmptyInputData);
1096 }
1097
1098 let first = close
1099 .iter()
1100 .position(|x| !x.is_nan())
1101 .ok_or(ModGodModeError::AllValuesNaN)?;
1102
1103 let n1 = input.get_n1();
1104 let n2 = input.get_n2();
1105 let n3 = input.get_n3();
1106 if n1 == 0 || n2 == 0 || n3 == 0 {
1107 return Err(ModGodModeError::InvalidPeriod {
1108 n1,
1109 n2,
1110 n3,
1111 data_len: len,
1112 });
1113 }
1114
1115 let need = n1.max(n2).max(n3);
1116 if len - first < need {
1117 return Err(ModGodModeError::NotEnoughValidData {
1118 needed: need,
1119 valid: len - first,
1120 });
1121 }
1122
1123 let warm = first + need - 1;
1124 let qnan = f64::from_bits(0x7ff8_0000_0000_0000);
1125 for v in &mut out_wavetrend[..warm] {
1126 *v = qnan;
1127 }
1128 for v in &mut out_signal[..warm] {
1129 *v = qnan;
1130 }
1131 for v in &mut out_histogram[..warm] {
1132 *v = qnan;
1133 }
1134
1135 let _ = (high, low, volume);
1136 mod_god_mode_into_slices(
1137 out_wavetrend,
1138 out_signal,
1139 out_histogram,
1140 input,
1141 Kernel::Auto,
1142 )
1143}
1144
1145#[inline]
1146#[allow(clippy::too_many_arguments)]
1147pub unsafe fn mod_god_mode_scalar_fused_into_slices(
1148 dst_wavetrend: &mut [f64],
1149 dst_signal: &mut [f64],
1150 dst_hist: &mut [f64],
1151 high: &[f64],
1152 low: &[f64],
1153 close: &[f64],
1154 volume: Option<&[f64]>,
1155 n1: usize,
1156 n2: usize,
1157 n3: usize,
1158 mode: ModGodModeMode,
1159 use_volume: bool,
1160 first: usize,
1161 warm: usize,
1162) -> Result<(), ModGodModeError> {
1163 let len = close.len();
1164 if len == 0 {
1165 return Err(ModGodModeError::EmptyInputData);
1166 }
1167 if n1 == 0 || n2 == 0 || n3 == 0 {
1168 return Err(ModGodModeError::InvalidPeriod {
1169 n1,
1170 n2,
1171 n3,
1172 data_len: len,
1173 });
1174 }
1175 if dst_wavetrend.len() != len || dst_signal.len() != len || dst_hist.len() != len {
1176 let dst_len = dst_wavetrend
1177 .len()
1178 .min(dst_signal.len())
1179 .min(dst_hist.len());
1180 return Err(ModGodModeError::OutputLengthMismatch {
1181 expected: len,
1182 got: dst_len,
1183 });
1184 }
1185
1186 #[inline(always)]
1187 fn ema_step(x: f64, prev: f64, alpha: f64, beta: f64) -> f64 {
1188 beta.mul_add(prev, alpha * x)
1189 }
1190 #[inline(always)]
1191 fn nonzero(v: f64) -> bool {
1192 v != 0.0 && v.is_finite()
1193 }
1194
1195 let alpha1 = 2.0 / (n1 as f64 + 1.0);
1196 let beta1 = 1.0 - alpha1;
1197 let alpha2 = 2.0 / (n2 as f64 + 1.0);
1198 let beta2 = 1.0 - alpha2;
1199 let alpha3 = 2.0 / (n3 as f64 + 1.0);
1200 let beta3 = 1.0 - alpha3;
1201
1202 let mut ema1_c = 0.0_f64;
1203 let mut ema2_abs = 0.0_f64;
1204 let mut ema3_ci = 0.0_f64;
1205 let mut seed_ema1 = false;
1206 let mut seed_ema2 = false;
1207 let mut seed_ema3 = false;
1208
1209 let mut rs_avg_gain = 0.0_f64;
1210 let mut rs_avg_loss = 0.0_f64;
1211 let mut rsi_seeded = false;
1212 let mut rs_init_cnt = 0usize;
1213 let mut prev_close = 0.0_f64;
1214
1215 let mut rsi_ring: Vec<f64> = vec![f64::NAN; n2.max(1)];
1216 let rsi_len = rsi_ring.len();
1217 let mut rsi_ring_head: usize = 0;
1218 let mut rsi_ema = 0.0_f64;
1219 let mut rsi_ema_seed = false;
1220
1221 let alpha_l = 0.7_f64;
1222 let one_m_l = 1.0_f64 - alpha_l;
1223 let mut l0 = 0.0_f64;
1224 let mut l1 = 0.0_f64;
1225 let mut l2 = 0.0_f64;
1226 let mut l3 = 0.0_f64;
1227
1228 #[inline(always)]
1229 fn willr_close_only(c: &[f64], idx: usize, win: usize) -> f64 {
1230 if win == 0 || idx + 1 < win {
1231 return f64::NAN;
1232 }
1233 let s = idx + 1 - win;
1234 let mut hi = f64::NEG_INFINITY;
1235 let mut lo = f64::INFINITY;
1236 for j in s..=idx {
1237 let v = c[j];
1238 if v > hi {
1239 hi = v;
1240 }
1241 if v < lo {
1242 lo = v;
1243 }
1244 }
1245 let rng = hi - lo;
1246 if rng == 0.0 {
1247 f64::NAN
1248 } else {
1249 60.0 * (c[idx] - hi) / rng + 80.0
1250 }
1251 }
1252
1253 let has_vol = use_volume && volume.is_some();
1254 let vol = if has_vol { volume.unwrap() } else { &[][..] };
1255
1256 let mut mf_pos_sum = 0.0_f64;
1257 let mut mf_neg_sum = 0.0_f64;
1258 let mut mf_ring_mf: Vec<f64> = vec![0.0; n3.max(1)];
1259 let mut mf_ring_sgn: Vec<i8> = vec![0; n3.max(1)];
1260 let mf_len = mf_ring_mf.len();
1261 let mut mf_head: usize = 0;
1262 let mut tp_prev: f64 = 0.0_f64;
1263 let mut tp_has_prev = false;
1264
1265 let mut tsi_ema_m_s = 0.0_f64;
1266 let mut tsi_ema_m_l = 0.0_f64;
1267 let mut tsi_ema_a_s = 0.0_f64;
1268 let mut tsi_ema_a_l = 0.0_f64;
1269 let mut tsi_seed_s = false;
1270 let mut tsi_seed_l = false;
1271
1272 let mut csi_num_e1 = 0.0_f64;
1273 let mut csi_num_e2 = 0.0_f64;
1274 let mut csi_den_e1 = 0.0_f64;
1275 let mut csi_den_e2 = 0.0_f64;
1276 let mut csi_seed_e1 = false;
1277 let mut csi_seed_e2 = false;
1278
1279 const SIGP: usize = 6;
1280 let mut sig_ring = [0.0_f64; SIGP];
1281 let mut sig_head = 0usize;
1282 let mut sig_sum = 0.0_f64;
1283 let sig_start = warm + SIGP - 1;
1284 let mut have_sig = false;
1285
1286 let mut hist_seeded = false;
1287
1288 let need = n1.max(n2).max(n3);
1289 if len - first < need {
1290 return Err(ModGodModeError::NotEnoughValidData {
1291 needed: need,
1292 valid: len - first,
1293 });
1294 }
1295
1296 prev_close = close[first];
1297 if !prev_close.is_finite() {
1298 return Ok(());
1299 }
1300
1301 for i in first..len {
1302 let c_i = close[i];
1303 if !seed_ema1 {
1304 ema1_c = c_i;
1305 seed_ema1 = true;
1306 } else {
1307 ema1_c = ema_step(c_i, ema1_c, alpha1, beta1);
1308 }
1309 let abs_dev = (c_i - ema1_c).abs();
1310 if !seed_ema2 {
1311 ema2_abs = abs_dev;
1312 seed_ema2 = true;
1313 } else {
1314 ema2_abs = ema_step(abs_dev, ema2_abs, alpha1, beta1);
1315 }
1316 let mut tci_val = f64::NAN;
1317 if nonzero(ema2_abs) {
1318 let ci = (c_i - ema1_c) / (0.025 * ema2_abs);
1319 if !seed_ema3 {
1320 ema3_ci = ci;
1321 seed_ema3 = true;
1322 } else {
1323 ema3_ci = ema_step(ci, ema3_ci, alpha2, beta2);
1324 }
1325 tci_val = ema3_ci + 50.0;
1326 }
1327
1328 let mut rsi_val = f64::NAN;
1329 if i == first {
1330 rs_avg_gain = 0.0;
1331 rs_avg_loss = 0.0;
1332 rs_init_cnt = 0;
1333 } else {
1334 let ch = c_i - prev_close;
1335 let gain = if ch > 0.0 { ch } else { 0.0 };
1336 let loss = if ch < 0.0 { -ch } else { 0.0 };
1337 if !rsi_seeded {
1338 rs_init_cnt += 1;
1339 rs_avg_gain += gain;
1340 rs_avg_loss += loss;
1341 if rs_init_cnt >= n3 {
1342 rs_avg_gain /= n3 as f64;
1343 rs_avg_loss /= n3 as f64;
1344 rsi_seeded = true;
1345 let rs = if rs_avg_loss == 0.0 {
1346 f64::INFINITY
1347 } else {
1348 rs_avg_gain / rs_avg_loss
1349 };
1350 rsi_val = 100.0 - 100.0 / (1.0 + rs);
1351 }
1352 } else {
1353 rs_avg_gain = ((rs_avg_gain * ((n3 - 1) as f64)) + gain) / (n3 as f64);
1354 rs_avg_loss = ((rs_avg_loss * ((n3 - 1) as f64)) + loss) / (n3 as f64);
1355 let rs = if rs_avg_loss == 0.0 {
1356 f64::INFINITY
1357 } else {
1358 rs_avg_gain / rs_avg_loss
1359 };
1360 rsi_val = 100.0 - 100.0 / (1.0 + rs);
1361 }
1362 }
1363
1364 {
1365 let prev_l0 = l0;
1366 l0 = alpha_l * c_i + one_m_l * prev_l0;
1367 let prev_l1 = l1;
1368 l1 = -one_m_l * l0 + prev_l0 + one_m_l * prev_l1;
1369 let prev_l2 = l2;
1370 l2 = -one_m_l * l1 + prev_l1 + one_m_l * prev_l2;
1371 let _l3p = l3;
1372 l3 = -one_m_l * l2 + prev_l2 + one_m_l * l3;
1373 }
1374 let cu = (l0 - l1).max(0.0) + (l1 - l2).max(0.0) + (l2 - l3).max(0.0);
1375 let cd = (l1 - l0).max(0.0) + (l2 - l1).max(0.0) + (l3 - l2).max(0.0);
1376 let lrsi_val = if nonzero(cu + cd) {
1377 100.0 * cu / (cu + cd)
1378 } else {
1379 f64::NAN
1380 };
1381
1382 let mut mf_val = f64::NAN;
1383 if has_vol {
1384 let tp = (high[i] + low[i] + c_i) / 3.0;
1385 if tp_has_prev {
1386 let sign: i8 = if tp > tp_prev {
1387 1
1388 } else if tp < tp_prev {
1389 -1
1390 } else {
1391 0
1392 };
1393 let mf_raw = tp * vol[i];
1394 if rsi_seeded {
1395 let old_mf = mf_ring_mf[mf_head];
1396 let old_sign = mf_ring_sgn[mf_head];
1397 if old_sign > 0 {
1398 mf_pos_sum -= old_mf;
1399 } else if old_sign < 0 {
1400 mf_neg_sum -= old_mf;
1401 }
1402 }
1403 mf_ring_mf[mf_head] = mf_raw;
1404 mf_ring_sgn[mf_head] = sign;
1405 if sign > 0 {
1406 mf_pos_sum += mf_raw;
1407 } else if sign < 0 {
1408 mf_neg_sum += mf_raw;
1409 }
1410 mf_head += 1;
1411 if mf_head == mf_len {
1412 mf_head = 0;
1413 }
1414 if rsi_seeded {
1415 mf_val = if mf_neg_sum == 0.0 {
1416 100.0
1417 } else {
1418 100.0 - 100.0 / (1.0 + (mf_pos_sum / mf_neg_sum))
1419 };
1420 }
1421 }
1422 tp_prev = tp;
1423 tp_has_prev = true;
1424 } else {
1425 mf_val = rsi_val;
1426 }
1427
1428 let mut cbci_val = f64::NAN;
1429 if rsi_seeded {
1430 let old = rsi_ring[rsi_ring_head];
1431 rsi_ring[rsi_ring_head] = rsi_val;
1432 rsi_ring_head += 1;
1433 if rsi_ring_head == rsi_len {
1434 rsi_ring_head = 0;
1435 }
1436 let mom = if old.is_finite() && rsi_val.is_finite() {
1437 rsi_val - old
1438 } else {
1439 f64::NAN
1440 };
1441 if !rsi_ema_seed && rsi_val.is_finite() {
1442 rsi_ema = rsi_val;
1443 rsi_ema_seed = true;
1444 } else if rsi_val.is_finite() {
1445 rsi_ema = ema_step(rsi_val, rsi_ema, alpha3, beta3);
1446 }
1447 if mom.is_finite() && rsi_ema_seed {
1448 cbci_val = mom + rsi_ema;
1449 }
1450 }
1451
1452 let mut csi_val = f64::NAN;
1453 let mut csi_mg_val = f64::NAN;
1454 if matches!(mode, ModGodModeMode::Godmode) {
1455 if i > first {
1456 let mom = c_i - prev_close;
1457 let am = mom.abs();
1458 if !tsi_seed_s {
1459 tsi_ema_m_s = mom;
1460 tsi_ema_a_s = am;
1461 tsi_seed_s = true;
1462 } else {
1463 tsi_ema_m_s = ema_step(mom, tsi_ema_m_s, alpha1, beta1);
1464 tsi_ema_a_s = ema_step(am, tsi_ema_a_s, alpha1, beta1);
1465 }
1466 if !tsi_seed_l && tsi_seed_s {
1467 tsi_ema_m_l = tsi_ema_m_s;
1468 tsi_ema_a_l = tsi_ema_a_s;
1469 tsi_seed_l = true;
1470 } else if tsi_seed_l {
1471 tsi_ema_m_l = ema_step(tsi_ema_m_s, tsi_ema_m_l, alpha2, beta2);
1472 tsi_ema_a_l = ema_step(tsi_ema_a_s, tsi_ema_a_l, alpha2, beta2);
1473 }
1474 if tsi_seed_l && nonzero(tsi_ema_a_l) {
1475 let tsi = 100.0 * (tsi_ema_m_l / tsi_ema_a_l);
1476 if rsi_val.is_finite() {
1477 csi_val = (rsi_val + (0.5 * tsi + 50.0)) * 0.5;
1478 }
1479 }
1480 }
1481 } else if matches!(mode, ModGodModeMode::GodmodeMg) {
1482 if i > first {
1483 let a = prev_close;
1484 let b = c_i;
1485 let avg = 0.5 * (a + b);
1486 let pc_norm = if avg != 0.0 { (b - a) / avg } else { 0.0 };
1487 let apc = (b - a).abs();
1488 if !csi_seed_e1 {
1489 csi_num_e1 = pc_norm;
1490 csi_den_e1 = apc;
1491 csi_seed_e1 = true;
1492 } else {
1493 csi_num_e1 = ema_step(pc_norm, csi_num_e1, alpha1, beta1);
1494 csi_den_e1 = ema_step(apc, csi_den_e1, alpha1, beta1);
1495 }
1496 if !csi_seed_e2 && csi_seed_e1 {
1497 csi_num_e2 = csi_num_e1;
1498 csi_den_e2 = csi_den_e1;
1499 csi_seed_e2 = true;
1500 } else if csi_seed_e2 {
1501 csi_num_e2 = ema_step(csi_num_e1, csi_num_e2, alpha2, beta2);
1502 csi_den_e2 = ema_step(csi_den_e1, csi_den_e2, alpha2, beta2);
1503 }
1504 if csi_seed_e2 && nonzero(csi_den_e2) && rsi_val.is_finite() {
1505 let ttsi = 50.0 * (csi_num_e2 / csi_den_e2) + 50.0;
1506 csi_mg_val = 0.5 * (rsi_val + ttsi);
1507 }
1508 }
1509 }
1510
1511 if i >= warm {
1512 let mut sum = 0.0_f64;
1513 let mut cnt = 0i32;
1514 match mode {
1515 ModGodModeMode::Godmode => {
1516 if tci_val.is_finite() {
1517 sum += tci_val;
1518 cnt += 1;
1519 }
1520 if csi_val.is_finite() {
1521 sum += csi_val;
1522 cnt += 1;
1523 }
1524 if mf_val.is_finite() {
1525 sum += mf_val;
1526 cnt += 1;
1527 }
1528 let wil = willr_close_only(close, i, n2);
1529 if wil.is_finite() {
1530 sum += wil;
1531 cnt += 1;
1532 }
1533 }
1534 ModGodModeMode::Tradition => {
1535 if tci_val.is_finite() {
1536 sum += tci_val;
1537 cnt += 1;
1538 }
1539 if mf_val.is_finite() {
1540 sum += mf_val;
1541 cnt += 1;
1542 }
1543 if rsi_val.is_finite() {
1544 sum += rsi_val;
1545 cnt += 1;
1546 }
1547 }
1548 ModGodModeMode::GodmodeMg => {
1549 if tci_val.is_finite() {
1550 sum += tci_val;
1551 cnt += 1;
1552 }
1553 if csi_mg_val.is_finite() {
1554 sum += csi_mg_val;
1555 cnt += 1;
1556 }
1557 if mf_val.is_finite() {
1558 sum += mf_val;
1559 cnt += 1;
1560 }
1561 let wil = willr_close_only(close, i, n2);
1562 if wil.is_finite() {
1563 sum += wil;
1564 cnt += 1;
1565 }
1566 if cbci_val.is_finite() {
1567 sum += cbci_val;
1568 cnt += 1;
1569 }
1570 if lrsi_val.is_finite() {
1571 sum += lrsi_val;
1572 cnt += 1;
1573 }
1574 }
1575 ModGodModeMode::TraditionMg => {
1576 if tci_val.is_finite() {
1577 sum += tci_val;
1578 cnt += 1;
1579 }
1580 if mf_val.is_finite() {
1581 sum += mf_val;
1582 cnt += 1;
1583 }
1584 if rsi_val.is_finite() {
1585 sum += rsi_val;
1586 cnt += 1;
1587 }
1588 if cbci_val.is_finite() {
1589 sum += cbci_val;
1590 cnt += 1;
1591 }
1592 if lrsi_val.is_finite() {
1593 sum += lrsi_val;
1594 cnt += 1;
1595 }
1596 }
1597 }
1598 if cnt > 0 {
1599 let wt = sum / (cnt as f64);
1600 dst_wavetrend[i] = wt;
1601
1602 if i >= sig_start {
1603 if !have_sig {
1604 let mut s = 0.0;
1605 for k in 0..SIGP {
1606 let x = dst_wavetrend[i + 1 - SIGP + k];
1607 sig_ring[k] = x;
1608 s += x;
1609 }
1610 sig_sum = s;
1611 have_sig = true;
1612 sig_head = 0;
1613 dst_signal[i] = s / (SIGP as f64);
1614 } else {
1615 let old = sig_ring[sig_head];
1616 sig_ring[sig_head] = wt;
1617 sig_head += 1;
1618 if sig_head == SIGP {
1619 sig_head = 0;
1620 }
1621 sig_sum += wt - old;
1622 dst_signal[i] = sig_sum / (SIGP as f64);
1623 }
1624
1625 let d = (dst_wavetrend[i] - dst_signal[i]) * 2.0 + 50.0;
1626 if !hist_seeded {
1627 dst_hist[i] = d;
1628 hist_seeded = true;
1629 } else {
1630 dst_hist[i] = ema_step(d, dst_hist[i - 1], alpha3, beta3);
1631 }
1632 }
1633 }
1634 }
1635 prev_close = c_i;
1636 }
1637 Ok(())
1638}
1639
1640#[cfg(all(
1641 feature = "nightly-avx",
1642 target_arch = "x86_64",
1643 target_feature = "avx2"
1644))]
1645#[inline]
1646#[allow(clippy::too_many_arguments)]
1647pub unsafe fn mod_god_mode_avx2_fused_into_slices(
1648 dst_wavetrend: &mut [f64],
1649 dst_signal: &mut [f64],
1650 dst_hist: &mut [f64],
1651 high: &[f64],
1652 low: &[f64],
1653 close: &[f64],
1654 volume: Option<&[f64]>,
1655 n1: usize,
1656 n2: usize,
1657 n3: usize,
1658 mode: ModGodModeMode,
1659 use_volume: bool,
1660 first: usize,
1661 warm: usize,
1662) -> Result<(), ModGodModeError> {
1663 mod_god_mode_scalar_fused_into_slices(
1664 dst_wavetrend,
1665 dst_signal,
1666 dst_hist,
1667 high,
1668 low,
1669 close,
1670 volume,
1671 n1,
1672 n2,
1673 n3,
1674 mode,
1675 use_volume,
1676 first,
1677 warm,
1678 )
1679}
1680
1681#[cfg(all(
1682 feature = "nightly-avx",
1683 target_arch = "x86_64",
1684 target_feature = "avx512f"
1685))]
1686#[inline]
1687#[allow(clippy::too_many_arguments)]
1688pub unsafe fn mod_god_mode_avx512_fused_into_slices(
1689 dst_wavetrend: &mut [f64],
1690 dst_signal: &mut [f64],
1691 dst_hist: &mut [f64],
1692 high: &[f64],
1693 low: &[f64],
1694 close: &[f64],
1695 volume: Option<&[f64]>,
1696 n1: usize,
1697 n2: usize,
1698 n3: usize,
1699 mode: ModGodModeMode,
1700 use_volume: bool,
1701 first: usize,
1702 warm: usize,
1703) -> Result<(), ModGodModeError> {
1704 mod_god_mode_scalar_fused_into_slices(
1705 dst_wavetrend,
1706 dst_signal,
1707 dst_hist,
1708 high,
1709 low,
1710 close,
1711 volume,
1712 n1,
1713 n2,
1714 n3,
1715 mode,
1716 use_volume,
1717 first,
1718 warm,
1719 )
1720}
1721
1722#[inline]
1723pub unsafe fn mod_god_mode_scalar_classic_tradition_mg(
1724 high: &[f64],
1725 low: &[f64],
1726 close: &[f64],
1727 volume: Option<&[f64]>,
1728 n1: usize,
1729 n2: usize,
1730 n3: usize,
1731 first: usize,
1732 _simple_warmup: usize,
1733 use_volume: bool,
1734 wavetrend: &mut [f64],
1735 signal: &mut [f64],
1736 histogram: &mut [f64],
1737) -> Result<(), ModGodModeError> {
1738 let len = close.len();
1739 if len == 0 {
1740 return Err(ModGodModeError::EmptyInputData);
1741 }
1742
1743 let tci_warmup = first + n1 + n1 + n2 - 2;
1744
1745 let mf_warmup = first + n3;
1746
1747 let cbci_warmup = first + n3 + n2.max(n3);
1748
1749 let lrsi_warmup = first + n3;
1750
1751 let actual_warmup = tci_warmup.max(mf_warmup).max(cbci_warmup).max(lrsi_warmup);
1752
1753 let mut ema1 = vec![f64::NAN; len];
1754 let alpha1 = 2.0 / (n1 as f64 + 1.0);
1755 let beta1 = 1.0 - alpha1;
1756 if first < len {
1757 ema1[first] = close[first];
1758 for i in (first + 1)..len {
1759 if close[i].is_finite() {
1760 ema1[i] = alpha1 * close[i] + beta1 * ema1[i - 1];
1761 } else {
1762 ema1[i] = ema1[i - 1];
1763 }
1764 }
1765 }
1766
1767 let mut abs_dev = vec![f64::NAN; len];
1768 for i in first..len {
1769 if ema1[i].is_finite() {
1770 abs_dev[i] = (close[i] - ema1[i]).abs();
1771 }
1772 }
1773
1774 let mut ema2 = vec![f64::NAN; len];
1775 let ema2_start = (first + n1 - 1).min(len - 1);
1776 if ema2_start < len && abs_dev[ema2_start].is_finite() {
1777 ema2[ema2_start] = abs_dev[ema2_start];
1778 for i in (ema2_start + 1)..len {
1779 if abs_dev[i].is_finite() {
1780 ema2[i] = alpha1 * abs_dev[i] + beta1 * ema2[i - 1];
1781 } else {
1782 ema2[i] = ema2[i - 1];
1783 }
1784 }
1785 }
1786
1787 let mut ci = vec![f64::NAN; len];
1788 let ci_start = (first + n1 + n1 - 2).min(len - 1);
1789 for i in ci_start..len {
1790 if ema2[i].is_finite() && ema2[i] != 0.0 {
1791 ci[i] = (close[i] - ema1[i]) / (0.025 * ema2[i]);
1792 }
1793 }
1794
1795 let mut tci = vec![f64::NAN; len];
1796 let alpha2 = 2.0 / (n2 as f64 + 1.0);
1797 let beta2 = 1.0 - alpha2;
1798 if tci_warmup < len && ci[tci_warmup].is_finite() {
1799 tci[tci_warmup] = ci[tci_warmup] + 50.0;
1800 for i in (tci_warmup + 1)..len {
1801 if ci[i].is_finite() {
1802 tci[i] = alpha2 * ci[i] + beta2 * (tci[i - 1] - 50.0) + 50.0;
1803 } else {
1804 tci[i] = tci[i - 1];
1805 }
1806 }
1807 }
1808
1809 let mut mf = vec![f64::NAN; len];
1810 if use_volume && volume.is_some() {
1811 let vol = volume.unwrap();
1812 if first + n3 <= len {
1813 let mut typical_price = vec![0.0; len];
1814 for i in first..len {
1815 typical_price[i] = (high[i] + low[i] + close[i]) / 3.0;
1816 }
1817
1818 let mut pos_flow = 0.0;
1819 let mut neg_flow = 0.0;
1820
1821 for i in (first + 1)..=(first + n3).min(len - 1) {
1822 let mf_raw = typical_price[i] * vol[i];
1823 if typical_price[i] > typical_price[i - 1] {
1824 pos_flow += mf_raw;
1825 } else if typical_price[i] < typical_price[i - 1] {
1826 neg_flow += mf_raw;
1827 }
1828 }
1829
1830 for i in (first + n3)..len {
1831 if i > first + n3 {
1832 let old_idx = i - n3;
1833 let old_mf = typical_price[old_idx] * vol[old_idx];
1834 if old_idx > first && typical_price[old_idx] > typical_price[old_idx - 1] {
1835 pos_flow -= old_mf;
1836 } else if old_idx > first && typical_price[old_idx] < typical_price[old_idx - 1]
1837 {
1838 neg_flow -= old_mf;
1839 }
1840
1841 let new_mf = typical_price[i] * vol[i];
1842 if typical_price[i] > typical_price[i - 1] {
1843 pos_flow += new_mf;
1844 } else if typical_price[i] < typical_price[i - 1] {
1845 neg_flow += new_mf;
1846 }
1847 }
1848
1849 mf[i] = if neg_flow == 0.0 {
1850 100.0
1851 } else {
1852 100.0 - (100.0 / (1.0 + pos_flow / neg_flow))
1853 };
1854 }
1855 }
1856 } else {
1857 if first + n3 <= len {
1858 let mut avg_gain = 0.0;
1859 let mut avg_loss = 0.0;
1860
1861 for i in (first + 1)..=(first + n3) {
1862 let change = close[i] - close[i - 1];
1863 if change > 0.0 {
1864 avg_gain += change;
1865 } else {
1866 avg_loss -= change;
1867 }
1868 }
1869 avg_gain /= n3 as f64;
1870 avg_loss /= n3 as f64;
1871
1872 for i in (first + n3)..len {
1873 let change = close[i] - close[i - 1];
1874 let (gain, loss) = if change > 0.0 {
1875 (change, 0.0)
1876 } else {
1877 (0.0, -change)
1878 };
1879
1880 avg_gain = (avg_gain * (n3 - 1) as f64 + gain) / n3 as f64;
1881 avg_loss = (avg_loss * (n3 - 1) as f64 + loss) / n3 as f64;
1882
1883 mf[i] = if avg_loss == 0.0 {
1884 100.0
1885 } else {
1886 100.0 - (100.0 / (1.0 + avg_gain / avg_loss))
1887 };
1888 }
1889 }
1890 }
1891
1892 let rsi = if use_volume && volume.is_some() {
1893 let mut rsi_vals = vec![f64::NAN; len];
1894 if mf_warmup < len {
1895 let mut avg_gain = 0.0;
1896 let mut avg_loss = 0.0;
1897
1898 for i in (first + 1)..(first + n3 + 1).min(len) {
1899 let change = close[i] - close[i - 1];
1900 if change > 0.0 {
1901 avg_gain += change;
1902 } else {
1903 avg_loss -= change;
1904 }
1905 }
1906 avg_gain /= n3 as f64;
1907 avg_loss /= n3 as f64;
1908
1909 for i in mf_warmup..len {
1910 if i > mf_warmup {
1911 let change = close[i] - close[i - 1];
1912 let (gain, loss) = if change > 0.0 {
1913 (change, 0.0)
1914 } else {
1915 (0.0, -change)
1916 };
1917
1918 avg_gain = (avg_gain * (n3 - 1) as f64 + gain) / n3 as f64;
1919 avg_loss = (avg_loss * (n3 - 1) as f64 + loss) / n3 as f64;
1920 }
1921
1922 rsi_vals[i] = if avg_loss == 0.0 {
1923 100.0
1924 } else {
1925 100.0 - (100.0 / (1.0 + avg_gain / avg_loss))
1926 };
1927 }
1928 }
1929 rsi_vals
1930 } else {
1931 mf.clone()
1932 };
1933
1934 let mut cbci = vec![f64::NAN; len];
1935
1936 let rsi_mom_start = mf_warmup + n2;
1937 let mut rsi_mom = vec![f64::NAN; len];
1938 if rsi_mom_start < len {
1939 for i in rsi_mom_start..len {
1940 if rsi[i].is_finite() && rsi[i - n2].is_finite() {
1941 rsi_mom[i] = rsi[i] - rsi[i - n2];
1942 }
1943 }
1944 }
1945
1946 let alpha3 = 2.0 / (n3 as f64 + 1.0);
1947 let beta3 = 1.0 - alpha3;
1948 let mut rsi_ema = vec![f64::NAN; len];
1949 let rsi_ema_start = mf_warmup;
1950 if rsi_ema_start < len && rsi[rsi_ema_start].is_finite() {
1951 rsi_ema[rsi_ema_start] = rsi[rsi_ema_start];
1952 for i in (rsi_ema_start + 1)..len {
1953 if rsi[i].is_finite() {
1954 rsi_ema[i] = alpha3 * rsi[i] + beta3 * rsi_ema[i - 1];
1955 } else {
1956 rsi_ema[i] = rsi_ema[i - 1];
1957 }
1958 }
1959 }
1960
1961 for i in cbci_warmup..len {
1962 if rsi_mom[i].is_finite() && rsi_ema[i].is_finite() {
1963 cbci[i] = rsi_mom[i] + rsi_ema[i];
1964 }
1965 }
1966
1967 let lrsi = rsi.clone();
1968
1969 for i in actual_warmup..len {
1970 let mut sum = 0.0;
1971 let mut count = 0;
1972
1973 if tci[i].is_finite() {
1974 sum += tci[i];
1975 count += 1;
1976 }
1977 if mf[i].is_finite() {
1978 sum += mf[i];
1979 count += 1;
1980 }
1981 if rsi[i].is_finite() {
1982 sum += rsi[i];
1983 count += 1;
1984 }
1985 if cbci[i].is_finite() {
1986 sum += cbci[i];
1987 count += 1;
1988 }
1989 if lrsi[i].is_finite() {
1990 sum += lrsi[i];
1991 count += 1;
1992 }
1993
1994 if count > 0 {
1995 wavetrend[i] = sum / count as f64;
1996 }
1997 }
1998
1999 let signal_start = actual_warmup + 5;
2000 if signal_start < len {
2001 let mut sum = 0.0;
2002 for i in actual_warmup..(actual_warmup + 6).min(len) {
2003 if wavetrend[i].is_finite() {
2004 sum += wavetrend[i];
2005 }
2006 }
2007 signal[signal_start] = sum / 6.0;
2008
2009 for i in (signal_start + 1)..len {
2010 if wavetrend[i].is_finite() && wavetrend[i - 6].is_finite() {
2011 sum += wavetrend[i] - wavetrend[i - 6];
2012 signal[i] = sum / 6.0;
2013 } else {
2014 signal[i] = signal[i - 1];
2015 }
2016 }
2017 }
2018
2019 let hist_start = signal_start;
2020 if hist_start < len && signal[hist_start].is_finite() {
2021 let alpha3 = 2.0 / (n3 as f64 + 1.0);
2022 let beta3 = 1.0 - alpha3;
2023
2024 let diff = (wavetrend[hist_start] - signal[hist_start]) * 2.0 + 50.0;
2025 histogram[hist_start] = diff;
2026
2027 for i in (hist_start + 1)..len {
2028 if signal[i].is_finite() && wavetrend[i].is_finite() {
2029 let diff = (wavetrend[i] - signal[i]) * 2.0 + 50.0;
2030 histogram[i] = alpha3 * diff + beta3 * histogram[i - 1];
2031 } else if i > 0 {
2032 histogram[i] = histogram[i - 1];
2033 }
2034 }
2035 }
2036
2037 Ok(())
2038}
2039
2040pub struct ModGodModeBuilder {
2041 n1: usize,
2042 n2: usize,
2043 n3: usize,
2044 mode: ModGodModeMode,
2045 use_volume: bool,
2046 kernel: Kernel,
2047}
2048
2049impl Default for ModGodModeBuilder {
2050 fn default() -> Self {
2051 Self {
2052 n1: 17,
2053 n2: 6,
2054 n3: 4,
2055 mode: ModGodModeMode::TraditionMg,
2056 use_volume: true,
2057 kernel: Kernel::Auto,
2058 }
2059 }
2060}
2061
2062impl ModGodModeBuilder {
2063 pub fn new() -> Self {
2064 Self::default()
2065 }
2066
2067 pub fn n1(mut self, n1: usize) -> Self {
2068 self.n1 = n1;
2069 self
2070 }
2071
2072 pub fn n2(mut self, n2: usize) -> Self {
2073 self.n2 = n2;
2074 self
2075 }
2076
2077 pub fn n3(mut self, n3: usize) -> Self {
2078 self.n3 = n3;
2079 self
2080 }
2081
2082 pub fn mode(mut self, mode: ModGodModeMode) -> Self {
2083 self.mode = mode;
2084 self
2085 }
2086
2087 pub fn use_volume(mut self, use_volume: bool) -> Self {
2088 self.use_volume = use_volume;
2089 self
2090 }
2091
2092 pub fn kernel(mut self, k: Kernel) -> Self {
2093 self.kernel = k;
2094 self
2095 }
2096
2097 #[inline]
2098 pub fn apply(self, c: &Candles) -> Result<ModGodModeOutput, ModGodModeError> {
2099 let kernel = self.kernel;
2100 let input = self.build(ModGodModeData::Candles { candles: c });
2101 mod_god_mode_with_kernel(&input, kernel)
2102 }
2103
2104 #[inline]
2105 pub fn apply_slices(
2106 self,
2107 high: &[f64],
2108 low: &[f64],
2109 close: &[f64],
2110 volume: Option<&[f64]>,
2111 ) -> Result<ModGodModeOutput, ModGodModeError> {
2112 let kernel = self.kernel;
2113 let input = self.build(ModGodModeData::Slices {
2114 high,
2115 low,
2116 close,
2117 volume,
2118 });
2119 mod_god_mode_with_kernel(&input, kernel)
2120 }
2121
2122 #[inline]
2123 pub fn into_stream(self) -> Result<ModGodModeStream, ModGodModeError> {
2124 ModGodModeStream::try_new(ModGodModeParams {
2125 n1: Some(self.n1),
2126 n2: Some(self.n2),
2127 n3: Some(self.n3),
2128 mode: Some(self.mode),
2129 use_volume: Some(self.use_volume),
2130 })
2131 }
2132
2133 pub fn build<'a>(self, data: ModGodModeData<'a>) -> ModGodModeInput<'a> {
2134 ModGodModeInput {
2135 data,
2136 params: ModGodModeParams {
2137 n1: Some(self.n1),
2138 n2: Some(self.n2),
2139 n3: Some(self.n3),
2140 mode: Some(self.mode),
2141 use_volume: Some(self.use_volume),
2142 },
2143 }
2144 }
2145
2146 pub fn calculate<'a>(
2147 self,
2148 data: ModGodModeData<'a>,
2149 ) -> Result<ModGodModeOutput, ModGodModeError> {
2150 let input = self.build(data);
2151 mod_god_mode(&input)
2152 }
2153
2154 pub fn calculate_with_kernel<'a>(
2155 self,
2156 data: ModGodModeData<'a>,
2157 kernel: Kernel,
2158 ) -> Result<ModGodModeOutput, ModGodModeError> {
2159 let input = self.build(data);
2160 mod_god_mode_with_kernel(&input, kernel)
2161 }
2162}
2163
2164use std::collections::VecDeque;
2165
2166#[inline(always)]
2167fn ema_step(x: f64, prev: f64, alpha: f64, beta: f64) -> f64 {
2168 beta.mul_add(prev, alpha * x)
2169}
2170
2171#[inline(always)]
2172fn nonzero(v: f64) -> bool {
2173 v != 0.0 && v.is_finite()
2174}
2175
2176#[derive(Default)]
2177struct MonoMax {
2178 dq: VecDeque<(usize, f64)>,
2179}
2180impl MonoMax {
2181 #[inline(always)]
2182 fn push(&mut self, idx: usize, val: f64, win: usize) {
2183 while let Some(&(j, _)) = self.dq.front() {
2184 if idx >= j + win {
2185 self.dq.pop_front();
2186 } else {
2187 break;
2188 }
2189 }
2190 while let Some(&(_, v)) = self.dq.back() {
2191 if v <= val {
2192 self.dq.pop_back();
2193 } else {
2194 break;
2195 }
2196 }
2197 self.dq.push_back((idx, val));
2198 }
2199 #[inline(always)]
2200 fn get(&self) -> Option<f64> {
2201 self.dq.front().map(|x| x.1)
2202 }
2203 #[inline(always)]
2204 fn clear(&mut self) {
2205 self.dq.clear();
2206 }
2207}
2208
2209#[derive(Default)]
2210struct MonoMin {
2211 dq: VecDeque<(usize, f64)>,
2212}
2213impl MonoMin {
2214 #[inline(always)]
2215 fn push(&mut self, idx: usize, val: f64, win: usize) {
2216 while let Some(&(j, _)) = self.dq.front() {
2217 if idx >= j + win {
2218 self.dq.pop_front();
2219 } else {
2220 break;
2221 }
2222 }
2223 while let Some(&(_, v)) = self.dq.back() {
2224 if v >= val {
2225 self.dq.pop_back();
2226 } else {
2227 break;
2228 }
2229 }
2230 self.dq.push_back((idx, val));
2231 }
2232 #[inline(always)]
2233 fn get(&self) -> Option<f64> {
2234 self.dq.front().map(|x| x.1)
2235 }
2236 #[inline(always)]
2237 fn clear(&mut self) {
2238 self.dq.clear();
2239 }
2240}
2241
2242pub struct ModGodModeStream {
2243 n1: usize,
2244 n2: usize,
2245 n3: usize,
2246 mode: ModGodModeMode,
2247 use_volume: bool,
2248
2249 alpha1: f64,
2250 beta1: f64,
2251 alpha2: f64,
2252 beta2: f64,
2253 alpha3: f64,
2254 beta3: f64,
2255
2256 warm_wt: usize,
2257 warm_sig: usize,
2258
2259 idx: usize,
2260
2261 ema1_c: f64,
2262 seed_ema1: bool,
2263 ema2_abs: f64,
2264 seed_ema2: bool,
2265 ema3_ci: f64,
2266 seed_ema3: bool,
2267
2268 rs_avg_gain: f64,
2269 rs_avg_loss: f64,
2270 rsi_seeded: bool,
2271 rs_init_cnt: usize,
2272 prev_close: f64,
2273 have_prev_close: bool,
2274
2275 alpha_l: f64,
2276 one_m_l: f64,
2277 l0: f64,
2278 l1: f64,
2279 l2: f64,
2280 l3: f64,
2281
2282 has_vol: bool,
2283 mf_pos_sum: f64,
2284 mf_neg_sum: f64,
2285 mf_ring_mf: Vec<f64>,
2286 mf_ring_sgn: Vec<i8>,
2287 mf_head: usize,
2288 tp_prev: f64,
2289 tp_has_prev: bool,
2290
2291 tsi_ema_m_s: f64,
2292 tsi_ema_a_s: f64,
2293 tsi_seed_s: bool,
2294 tsi_ema_m_l: f64,
2295 tsi_ema_a_l: f64,
2296 tsi_seed_l: bool,
2297
2298 csi_num_e1: f64,
2299 csi_num_e2: f64,
2300 csi_seed_e1: bool,
2301 csi_seed_e2: bool,
2302 csi_den_e1: f64,
2303 csi_den_e2: f64,
2304
2305 rsi_ring: Vec<f64>,
2306 rsi_ring_head: usize,
2307 rsi_ema: f64,
2308 rsi_ema_seed: bool,
2309
2310 w_max: MonoMax,
2311 w_min: MonoMin,
2312
2313 sig_ring: [f64; 6],
2314 sig_head: usize,
2315 sig_sum: f64,
2316 sig_seeded: bool,
2317 sig_count: usize,
2318
2319 hist_prev: f64,
2320 hist_seeded: bool,
2321}
2322
2323impl ModGodModeStream {
2324 pub fn try_new(p: ModGodModeParams) -> Result<Self, ModGodModeError> {
2325 let n1 = p.n1.unwrap_or(17);
2326 let n2 = p.n2.unwrap_or(6);
2327 let n3 = p.n3.unwrap_or(4);
2328 if n1 == 0 || n2 == 0 || n3 == 0 {
2329 return Err(ModGodModeError::InvalidPeriod {
2330 n1,
2331 n2,
2332 n3,
2333 data_len: 0,
2334 });
2335 }
2336 let mode = p.mode.unwrap_or_default();
2337 let use_volume = p.use_volume.unwrap_or(false);
2338 Ok(Self::new(n1, n2, n3, mode, use_volume))
2339 }
2340
2341 pub fn new(n1: usize, n2: usize, n3: usize, mode: ModGodModeMode, use_volume: bool) -> Self {
2342 let alpha1 = 2.0 / (n1 as f64 + 1.0);
2343 let beta1 = 1.0 - alpha1;
2344 let alpha2 = 2.0 / (n2 as f64 + 1.0);
2345 let beta2 = 1.0 - alpha2;
2346 let alpha3 = 2.0 / (n3 as f64 + 1.0);
2347 let beta3 = 1.0 - alpha3;
2348
2349 let warm_wt = n1.max(n2).max(n3) - 1;
2350 let warm_sig = warm_wt + (6 - 1);
2351
2352 Self {
2353 n1,
2354 n2,
2355 n3,
2356 mode,
2357 use_volume,
2358 alpha1,
2359 beta1,
2360 alpha2,
2361 beta2,
2362 alpha3,
2363 beta3,
2364 warm_wt,
2365 warm_sig,
2366 idx: 0,
2367
2368 ema1_c: 0.0,
2369 seed_ema1: false,
2370 ema2_abs: 0.0,
2371 seed_ema2: false,
2372 ema3_ci: 0.0,
2373 seed_ema3: false,
2374
2375 rs_avg_gain: 0.0,
2376 rs_avg_loss: 0.0,
2377 rsi_seeded: false,
2378 rs_init_cnt: 0,
2379 prev_close: 0.0,
2380 have_prev_close: false,
2381
2382 alpha_l: 0.7,
2383 one_m_l: 1.0 - 0.7,
2384 l0: 0.0,
2385 l1: 0.0,
2386 l2: 0.0,
2387 l3: 0.0,
2388
2389 has_vol: use_volume,
2390 mf_pos_sum: 0.0,
2391 mf_neg_sum: 0.0,
2392 mf_ring_mf: vec![0.0; n3.max(1)],
2393 mf_ring_sgn: vec![0; n3.max(1)],
2394 mf_head: 0,
2395 tp_prev: 0.0,
2396 tp_has_prev: false,
2397
2398 tsi_ema_m_s: 0.0,
2399 tsi_ema_a_s: 0.0,
2400 tsi_seed_s: false,
2401 tsi_ema_m_l: 0.0,
2402 tsi_ema_a_l: 0.0,
2403 tsi_seed_l: false,
2404
2405 csi_num_e1: 0.0,
2406 csi_num_e2: 0.0,
2407 csi_seed_e1: false,
2408 csi_seed_e2: false,
2409 csi_den_e1: 0.0,
2410 csi_den_e2: 0.0,
2411
2412 rsi_ring: vec![f64::NAN; n2.max(1)],
2413 rsi_ring_head: 0,
2414 rsi_ema: 0.0,
2415 rsi_ema_seed: false,
2416
2417 w_max: MonoMax::default(),
2418 w_min: MonoMin::default(),
2419
2420 sig_ring: [0.0; 6],
2421 sig_head: 0,
2422 sig_sum: 0.0,
2423 sig_seeded: false,
2424 sig_count: 0,
2425
2426 hist_prev: 0.0,
2427 hist_seeded: false,
2428 }
2429 }
2430
2431 #[inline]
2432 pub fn update(
2433 &mut self,
2434 high: f64,
2435 low: f64,
2436 close: f64,
2437 volume: Option<f64>,
2438 ) -> Option<(f64, f64, f64)> {
2439 if !(high.is_finite() && low.is_finite() && close.is_finite()) {
2440 self.idx += 1;
2441 return None;
2442 }
2443 let i = self.idx;
2444
2445 if !self.seed_ema1 {
2446 self.ema1_c = close;
2447 self.seed_ema1 = true;
2448 } else {
2449 self.ema1_c = ema_step(close, self.ema1_c, self.alpha1, self.beta1);
2450 }
2451
2452 let abs_dev = (close - self.ema1_c).abs();
2453 if !self.seed_ema2 {
2454 self.ema2_abs = abs_dev;
2455 self.seed_ema2 = true;
2456 } else {
2457 self.ema2_abs = ema_step(abs_dev, self.ema2_abs, self.alpha1, self.beta1);
2458 }
2459
2460 let mut tci_val = f64::NAN;
2461 if nonzero(self.ema2_abs) {
2462 let inv = (0.025 * self.ema2_abs).recip();
2463 let ci = (close - self.ema1_c) * inv;
2464 if !self.seed_ema3 {
2465 self.ema3_ci = ci;
2466 self.seed_ema3 = true;
2467 } else {
2468 self.ema3_ci = ema_step(ci, self.ema3_ci, self.alpha2, self.beta2);
2469 }
2470 tci_val = self.ema3_ci + 50.0;
2471 }
2472
2473 let mut rsi_val = f64::NAN;
2474 if !self.have_prev_close {
2475 self.prev_close = close;
2476 self.have_prev_close = true;
2477 } else {
2478 let ch = close - self.prev_close;
2479 let gain = if ch > 0.0 { ch } else { 0.0 };
2480 let loss = if ch < 0.0 { -ch } else { 0.0 };
2481
2482 if !self.rsi_seeded {
2483 self.rs_init_cnt += 1;
2484 self.rs_avg_gain += gain;
2485 self.rs_avg_loss += loss;
2486 if self.rs_init_cnt >= self.n3 {
2487 self.rs_avg_gain /= self.n3 as f64;
2488 self.rs_avg_loss /= self.n3 as f64;
2489 self.rsi_seeded = true;
2490 let rs = if self.rs_avg_loss == 0.0 {
2491 f64::INFINITY
2492 } else {
2493 self.rs_avg_gain / self.rs_avg_loss
2494 };
2495 rsi_val = 100.0 * (rs / (1.0 + rs));
2496 }
2497 } else {
2498 let n3m1 = (self.n3 - 1) as f64;
2499 self.rs_avg_gain = (self.rs_avg_gain * n3m1 + gain) / self.n3 as f64;
2500 self.rs_avg_loss = (self.rs_avg_loss * n3m1 + loss) / self.n3 as f64;
2501 let rs = if self.rs_avg_loss == 0.0 {
2502 f64::INFINITY
2503 } else {
2504 self.rs_avg_gain / self.rs_avg_loss
2505 };
2506 rsi_val = 100.0 * (rs / (1.0 + rs));
2507 }
2508 }
2509
2510 {
2511 let p_l0 = self.l0;
2512 self.l0 = self.alpha_l * close + self.one_m_l * p_l0;
2513 let p_l1 = self.l1;
2514 self.l1 = -self.one_m_l * self.l0 + p_l0 + self.one_m_l * p_l1;
2515 let p_l2 = self.l2;
2516 self.l2 = -self.one_m_l * self.l1 + p_l1 + self.one_m_l * p_l2;
2517 let p_l3 = self.l3;
2518 self.l3 = -self.one_m_l * self.l2 + p_l2 + self.one_m_l * p_l3;
2519 }
2520 let cu = (self.l0 - self.l1).max(0.0)
2521 + (self.l1 - self.l2).max(0.0)
2522 + (self.l2 - self.l3).max(0.0);
2523 let cd = (self.l1 - self.l0).max(0.0)
2524 + (self.l2 - self.l1).max(0.0)
2525 + (self.l3 - self.l2).max(0.0);
2526 let lrsi_val = if nonzero(cu + cd) {
2527 100.0 * cu / (cu + cd)
2528 } else {
2529 f64::NAN
2530 };
2531
2532 let mut mf_val = f64::NAN;
2533 if self.has_vol {
2534 let v = volume.unwrap_or(0.0);
2535 let tp = (high + low + close) * (1.0 / 3.0);
2536 if self.tp_has_prev {
2537 let sign: i8 = if tp > self.tp_prev {
2538 1
2539 } else if tp < self.tp_prev {
2540 -1
2541 } else {
2542 0
2543 };
2544 let mf_raw = tp * v;
2545
2546 if self.rsi_seeded {
2547 let old_mf = self.mf_ring_mf[self.mf_head];
2548 let old_sg = self.mf_ring_sgn[self.mf_head];
2549 if old_sg > 0 {
2550 self.mf_pos_sum -= old_mf;
2551 } else if old_sg < 0 {
2552 self.mf_neg_sum -= old_mf;
2553 }
2554
2555 self.mf_ring_mf[self.mf_head] = mf_raw;
2556 self.mf_ring_sgn[self.mf_head] = sign;
2557 if sign > 0 {
2558 self.mf_pos_sum += mf_raw;
2559 } else if sign < 0 {
2560 self.mf_neg_sum += mf_raw;
2561 }
2562 self.mf_head = (self.mf_head + 1) % self.n3.max(1);
2563
2564 let denom = self.mf_pos_sum + self.mf_neg_sum;
2565 if denom > 0.0 {
2566 mf_val = 100.0 * (self.mf_pos_sum / denom);
2567 } else if self.mf_neg_sum == 0.0 {
2568 mf_val = 100.0;
2569 }
2570 } else {
2571 self.mf_ring_mf[self.mf_head] = mf_raw;
2572 self.mf_ring_sgn[self.mf_head] = sign;
2573 self.mf_head = (self.mf_head + 1) % self.n3.max(1);
2574 }
2575 }
2576 self.tp_prev = tp;
2577 self.tp_has_prev = true;
2578 } else {
2579 mf_val = rsi_val;
2580 }
2581
2582 let mut cbci_val = f64::NAN;
2583 if self.rsi_seeded {
2584 let old = self.rsi_ring[self.rsi_ring_head];
2585 self.rsi_ring[self.rsi_ring_head] = rsi_val;
2586 self.rsi_ring_head = (self.rsi_ring_head + 1) % self.n2.max(1);
2587 let mom = if old.is_finite() && rsi_val.is_finite() {
2588 rsi_val - old
2589 } else {
2590 f64::NAN
2591 };
2592
2593 if !self.rsi_ema_seed && rsi_val.is_finite() {
2594 self.rsi_ema = rsi_val;
2595 self.rsi_ema_seed = true;
2596 } else if rsi_val.is_finite() {
2597 self.rsi_ema = ema_step(rsi_val, self.rsi_ema, self.alpha3, self.beta3);
2598 }
2599
2600 if mom.is_finite() && self.rsi_ema_seed {
2601 cbci_val = mom + self.rsi_ema;
2602 }
2603 }
2604
2605 let mut csi_val = f64::NAN;
2606 let mut csi_mg_val = f64::NAN;
2607
2608 if matches!(self.mode, ModGodModeMode::Godmode) && self.have_prev_close {
2609 let mom = close - self.prev_close;
2610 let am = mom.abs();
2611
2612 if !self.tsi_seed_s {
2613 self.tsi_ema_m_s = mom;
2614 self.tsi_ema_a_s = am;
2615 self.tsi_seed_s = true;
2616 } else {
2617 self.tsi_ema_m_s = ema_step(mom, self.tsi_ema_m_s, self.alpha1, self.beta1);
2618 self.tsi_ema_a_s = ema_step(am, self.tsi_ema_a_s, self.alpha1, self.beta1);
2619 }
2620
2621 if !self.tsi_seed_l && self.tsi_seed_s {
2622 self.tsi_ema_m_l = self.tsi_ema_m_s;
2623 self.tsi_ema_a_l = self.tsi_ema_a_s;
2624 self.tsi_seed_l = true;
2625 } else if self.tsi_seed_l {
2626 self.tsi_ema_m_l =
2627 ema_step(self.tsi_ema_m_s, self.tsi_ema_m_l, self.alpha2, self.beta2);
2628 self.tsi_ema_a_l =
2629 ema_step(self.tsi_ema_a_s, self.tsi_ema_a_l, self.alpha2, self.beta2);
2630 }
2631
2632 if self.tsi_seed_l && nonzero(self.tsi_ema_a_l) && rsi_val.is_finite() {
2633 let tsi = 100.0 * (self.tsi_ema_m_l / self.tsi_ema_a_l);
2634 csi_val = 0.5 * (rsi_val + (0.5 * tsi + 50.0));
2635 }
2636 }
2637
2638 if matches!(self.mode, ModGodModeMode::GodmodeMg) && self.have_prev_close {
2639 let a = self.prev_close;
2640 let b = close;
2641 let avg = 0.5 * (a + b);
2642 let pc_norm = if avg != 0.0 {
2643 (b - a) * avg.recip()
2644 } else {
2645 0.0
2646 };
2647 let apc = (b - a).abs();
2648
2649 if !self.csi_seed_e1 {
2650 self.csi_num_e1 = pc_norm;
2651 self.csi_den_e1 = apc;
2652 self.csi_seed_e1 = true;
2653 } else {
2654 self.csi_num_e1 = ema_step(pc_norm, self.csi_num_e1, self.alpha1, self.beta1);
2655 self.csi_den_e1 = ema_step(apc, self.csi_den_e1, self.alpha1, self.beta1);
2656 }
2657
2658 if !self.csi_seed_e2 && self.csi_seed_e1 {
2659 self.csi_num_e2 = self.csi_num_e1;
2660 self.csi_den_e2 = self.csi_den_e1;
2661 self.csi_seed_e2 = true;
2662 } else if self.csi_seed_e2 {
2663 self.csi_num_e2 =
2664 ema_step(self.csi_num_e1, self.csi_num_e2, self.alpha2, self.beta2);
2665 self.csi_den_e2 =
2666 ema_step(self.csi_den_e1, self.csi_den_e2, self.alpha2, self.beta2);
2667 }
2668
2669 if self.csi_seed_e2 && nonzero(self.csi_den_e2) && rsi_val.is_finite() {
2670 let ttsi = 50.0 * (self.csi_num_e2 / self.csi_den_e2) + 50.0;
2671 csi_mg_val = 0.5 * (rsi_val + ttsi);
2672 }
2673 }
2674
2675 self.w_max.push(i, close, self.n2);
2676 self.w_min.push(i, close, self.n2);
2677 let mut willy_val = f64::NAN;
2678 if i + 1 >= self.n2 {
2679 if let (Some(hi), Some(lo)) = (self.w_max.get(), self.w_min.get()) {
2680 let rng = hi - lo;
2681 if rng != 0.0 {
2682 willy_val = 60.0 * (close - hi) / rng + 80.0;
2683 }
2684 }
2685 }
2686
2687 let ready_wt = i >= self.warm_wt;
2688 let mut wt = f64::NAN;
2689 if ready_wt {
2690 let mut sum = 0.0;
2691 let mut cnt = 0i32;
2692 match self.mode {
2693 ModGodModeMode::Godmode => {
2694 if tci_val.is_finite() {
2695 sum += tci_val;
2696 cnt += 1;
2697 }
2698 if csi_val.is_finite() {
2699 sum += csi_val;
2700 cnt += 1;
2701 }
2702 if mf_val.is_finite() {
2703 sum += mf_val;
2704 cnt += 1;
2705 }
2706 if willy_val.is_finite() {
2707 sum += willy_val;
2708 cnt += 1;
2709 }
2710 }
2711 ModGodModeMode::Tradition => {
2712 if tci_val.is_finite() {
2713 sum += tci_val;
2714 cnt += 1;
2715 }
2716 if mf_val.is_finite() {
2717 sum += mf_val;
2718 cnt += 1;
2719 }
2720 if rsi_val.is_finite() {
2721 sum += rsi_val;
2722 cnt += 1;
2723 }
2724 }
2725 ModGodModeMode::GodmodeMg => {
2726 if tci_val.is_finite() {
2727 sum += tci_val;
2728 cnt += 1;
2729 }
2730 if csi_mg_val.is_finite() {
2731 sum += csi_mg_val;
2732 cnt += 1;
2733 }
2734 if mf_val.is_finite() {
2735 sum += mf_val;
2736 cnt += 1;
2737 }
2738 if willy_val.is_finite() {
2739 sum += willy_val;
2740 cnt += 1;
2741 }
2742 if cbci_val.is_finite() {
2743 sum += cbci_val;
2744 cnt += 1;
2745 }
2746 if lrsi_val.is_finite() {
2747 sum += lrsi_val;
2748 cnt += 1;
2749 }
2750 }
2751 ModGodModeMode::TraditionMg => {
2752 if tci_val.is_finite() {
2753 sum += tci_val;
2754 cnt += 1;
2755 }
2756 if mf_val.is_finite() {
2757 sum += mf_val;
2758 cnt += 1;
2759 }
2760 if rsi_val.is_finite() {
2761 sum += rsi_val;
2762 cnt += 1;
2763 }
2764 if cbci_val.is_finite() {
2765 sum += cbci_val;
2766 cnt += 1;
2767 }
2768 if lrsi_val.is_finite() {
2769 sum += lrsi_val;
2770 cnt += 1;
2771 }
2772 }
2773 }
2774 if cnt > 0 {
2775 wt = sum / (cnt as f64);
2776 }
2777 }
2778
2779 let mut sig = f64::NAN;
2780 if i >= self.warm_wt && wt.is_finite() {
2781 if !self.sig_seeded {
2782 self.sig_ring[self.sig_head] = wt;
2783 self.sig_head = (self.sig_head + 1) % 6;
2784 self.sig_sum += wt;
2785 self.sig_count += 1;
2786 if self.sig_count == 6 {
2787 self.sig_seeded = true;
2788 sig = self.sig_sum / 6.0;
2789 }
2790 } else {
2791 let old = self.sig_ring[self.sig_head];
2792 self.sig_ring[self.sig_head] = wt;
2793 self.sig_head = (self.sig_head + 1) % 6;
2794 self.sig_sum += wt - old;
2795 sig = self.sig_sum / 6.0;
2796 }
2797 }
2798
2799 let mut hist = f64::NAN;
2800 if self.sig_seeded && sig.is_finite() && wt.is_finite() {
2801 let d = (wt - sig) * 2.0 + 50.0;
2802 if !self.hist_seeded {
2803 self.hist_prev = d;
2804 self.hist_seeded = true;
2805 hist = d;
2806 } else {
2807 self.hist_prev = ema_step(d, self.hist_prev, self.alpha3, self.beta3);
2808 hist = self.hist_prev;
2809 }
2810 }
2811
2812 self.prev_close = close;
2813 self.idx += 1;
2814
2815 if self.sig_seeded && hist.is_finite() {
2816 Some((wt, sig, hist))
2817 } else {
2818 None
2819 }
2820 }
2821
2822 pub fn reset(&mut self) {
2823 self.idx = 0;
2824
2825 self.ema1_c = 0.0;
2826 self.seed_ema1 = false;
2827 self.ema2_abs = 0.0;
2828 self.seed_ema2 = false;
2829 self.ema3_ci = 0.0;
2830 self.seed_ema3 = false;
2831
2832 self.rs_avg_gain = 0.0;
2833 self.rs_avg_loss = 0.0;
2834 self.rsi_seeded = false;
2835 self.rs_init_cnt = 0;
2836 self.prev_close = 0.0;
2837 self.have_prev_close = false;
2838
2839 self.l0 = 0.0;
2840 self.l1 = 0.0;
2841 self.l2 = 0.0;
2842 self.l3 = 0.0;
2843
2844 self.mf_pos_sum = 0.0;
2845 self.mf_neg_sum = 0.0;
2846 self.mf_ring_mf.fill(0.0);
2847 self.mf_ring_sgn.fill(0);
2848 self.mf_head = 0;
2849 self.tp_prev = 0.0;
2850 self.tp_has_prev = false;
2851
2852 self.tsi_ema_m_s = 0.0;
2853 self.tsi_ema_a_s = 0.0;
2854 self.tsi_seed_s = false;
2855 self.tsi_ema_m_l = 0.0;
2856 self.tsi_ema_a_l = 0.0;
2857 self.tsi_seed_l = false;
2858
2859 self.csi_num_e1 = 0.0;
2860 self.csi_num_e2 = 0.0;
2861 self.csi_seed_e1 = false;
2862 self.csi_seed_e2 = false;
2863 self.csi_den_e1 = 0.0;
2864 self.csi_den_e2 = 0.0;
2865
2866 self.rsi_ring.fill(f64::NAN);
2867 self.rsi_ring_head = 0;
2868 self.rsi_ema = 0.0;
2869 self.rsi_ema_seed = false;
2870
2871 self.w_max.clear();
2872 self.w_min.clear();
2873
2874 self.sig_ring = [0.0; 6];
2875 self.sig_head = 0;
2876 self.sig_sum = 0.0;
2877 self.sig_seeded = false;
2878 self.sig_count = 0;
2879
2880 self.hist_prev = 0.0;
2881 self.hist_seeded = false;
2882 }
2883}
2884
2885#[derive(Clone, Debug)]
2886pub struct ModGodModeBatchRange {
2887 pub n1: (usize, usize, usize),
2888 pub n2: (usize, usize, usize),
2889 pub n3: (usize, usize, usize),
2890 pub mode: ModGodModeMode,
2891}
2892
2893impl Default for ModGodModeBatchRange {
2894 fn default() -> Self {
2895 Self {
2896 n1: (17, 266, 1),
2897 n2: (6, 6, 0),
2898 n3: (4, 4, 0),
2899 mode: ModGodModeMode::TraditionMg,
2900 }
2901 }
2902}
2903
2904#[derive(Clone, Debug)]
2905pub struct ModGodModeBatchOutput {
2906 pub wavetrend: Vec<f64>,
2907 pub signal: Vec<f64>,
2908 pub histogram: Vec<f64>,
2909 pub combos: Vec<ModGodModeParams>,
2910 pub rows: usize,
2911 pub cols: usize,
2912}
2913
2914impl ModGodModeBatchOutput {
2915 #[inline]
2916 pub fn row_for_params(&self, p: &ModGodModeParams) -> Option<usize> {
2917 self.combos.iter().position(|c| {
2918 c.n1.unwrap() == p.n1.unwrap()
2919 && c.n2.unwrap() == p.n2.unwrap()
2920 && c.n3.unwrap() == p.n3.unwrap()
2921 && c.mode.unwrap() == p.mode.unwrap()
2922 })
2923 }
2924
2925 #[inline]
2926 pub fn values_for(&self, p: &ModGodModeParams) -> Option<(&[f64], &[f64], &[f64])> {
2927 self.row_for_params(p).map(|row| {
2928 let s = row * self.cols;
2929 (
2930 &self.wavetrend[s..s + self.cols],
2931 &self.signal[s..s + self.cols],
2932 &self.histogram[s..s + self.cols],
2933 )
2934 })
2935 }
2936}
2937
2938#[inline]
2939fn axis_usize_mod(
2940 (start, end, step): (usize, usize, usize),
2941) -> Result<Vec<usize>, ModGodModeError> {
2942 if step == 0 || start == end {
2943 return Ok(vec![start]);
2944 }
2945 if start < end {
2946 let v: Vec<_> = (start..=end).step_by(step).collect();
2947 if v.is_empty() {
2948 return Err(ModGodModeError::InvalidRange { start, end, step });
2949 }
2950 Ok(v)
2951 } else {
2952 let mut v = Vec::new();
2953 let mut cur = start;
2954 while cur >= end {
2955 v.push(cur);
2956 if cur - end < step {
2957 break;
2958 }
2959 cur -= step;
2960 }
2961 if v.is_empty() {
2962 return Err(ModGodModeError::InvalidRange { start, end, step });
2963 }
2964 Ok(v)
2965 }
2966}
2967
2968#[inline]
2969fn expand_grid_mod(r: &ModGodModeBatchRange) -> Result<Vec<ModGodModeParams>, ModGodModeError> {
2970 let n1s = axis_usize_mod(r.n1)?;
2971 let n2s = axis_usize_mod(r.n2)?;
2972 let n3s = axis_usize_mod(r.n3)?;
2973 let cap = n1s
2974 .len()
2975 .checked_mul(n2s.len())
2976 .and_then(|v| v.checked_mul(n3s.len()))
2977 .ok_or_else(|| ModGodModeError::InvalidInput("batch grid size overflow".into()))?;
2978 let mut v = Vec::with_capacity(cap);
2979 for &a in &n1s {
2980 for &b in &n2s {
2981 for &c in &n3s {
2982 v.push(ModGodModeParams {
2983 n1: Some(a),
2984 n2: Some(b),
2985 n3: Some(c),
2986 mode: Some(r.mode),
2987 use_volume: Some(false),
2988 });
2989 }
2990 }
2991 }
2992 if v.is_empty() {
2993 return Err(ModGodModeError::InvalidRange {
2994 start: r.n1.0,
2995 end: r.n3.1,
2996 step: r.n1.2.max(r.n2.2).max(r.n3.2),
2997 });
2998 }
2999 Ok(v)
3000}
3001
3002pub fn mod_god_mode_batch_with_kernel(
3003 high: &[f64],
3004 low: &[f64],
3005 close: &[f64],
3006 volume: Option<&[f64]>,
3007 sweep: &ModGodModeBatchRange,
3008 k: Kernel,
3009) -> Result<ModGodModeBatchOutput, ModGodModeError> {
3010 let combos = expand_grid_mod(sweep)?;
3011 let rows = combos.len();
3012 let cols = close.len();
3013 if cols == 0 {
3014 return Err(ModGodModeError::EmptyInputData);
3015 }
3016 let _ = rows
3017 .checked_mul(cols)
3018 .ok_or_else(|| ModGodModeError::InvalidInput("rows*cols overflow".into()))?;
3019
3020 let mut mu_w = make_uninit_matrix(rows, cols);
3021 let mut mu_s = make_uninit_matrix(rows, cols);
3022 let mut mu_h = make_uninit_matrix(rows, cols);
3023
3024 let first = close
3025 .iter()
3026 .position(|x| !x.is_nan())
3027 .ok_or(ModGodModeError::AllValuesNaN)?;
3028 let warms: Vec<usize> = combos
3029 .iter()
3030 .map(|p| first + p.n1.unwrap().max(p.n2.unwrap()).max(p.n3.unwrap()) - 1)
3031 .collect();
3032 init_matrix_prefixes(&mut mu_w, cols, &warms);
3033 init_matrix_prefixes(&mut mu_s, cols, &warms);
3034 init_matrix_prefixes(&mut mu_h, cols, &warms);
3035
3036 let mut guard_w = core::mem::ManuallyDrop::new(mu_w);
3037 let mut guard_s = core::mem::ManuallyDrop::new(mu_s);
3038 let mut guard_h = core::mem::ManuallyDrop::new(mu_h);
3039 let out_w: &mut [f64] =
3040 unsafe { core::slice::from_raw_parts_mut(guard_w.as_mut_ptr() as *mut f64, guard_w.len()) };
3041 let out_s: &mut [f64] =
3042 unsafe { core::slice::from_raw_parts_mut(guard_s.as_mut_ptr() as *mut f64, guard_s.len()) };
3043 let out_h: &mut [f64] =
3044 unsafe { core::slice::from_raw_parts_mut(guard_h.as_mut_ptr() as *mut f64, guard_h.len()) };
3045
3046 let batch_kern = match k {
3047 Kernel::Auto => detect_best_batch_kernel(),
3048 other if other.is_batch() => other,
3049 _ => return Err(ModGodModeError::InvalidKernelForBatch(k)),
3050 };
3051
3052 let row_kern = match batch_kern {
3053 Kernel::Avx512Batch => Kernel::Avx512,
3054 Kernel::Avx2Batch => Kernel::Avx2,
3055 Kernel::ScalarBatch => Kernel::Scalar,
3056 _ => unreachable!("Invalid batch kernel"),
3057 };
3058
3059 for (row, p) in combos.iter().enumerate() {
3060 let start = row * cols;
3061 let end = start + cols;
3062 let dst_w = &mut out_w[start..end];
3063 let dst_s = &mut out_s[start..end];
3064 let dst_h = &mut out_h[start..end];
3065
3066 let inp = ModGodModeInput::from_slices(high, low, close, volume, p.clone());
3067 mod_god_mode_into_slices(dst_w, dst_s, dst_h, &inp, row_kern)?;
3068 }
3069
3070 let wavetrend = unsafe {
3071 let ptr = out_w.as_mut_ptr();
3072 let len = out_w.len();
3073 core::mem::forget(guard_w);
3074 Vec::from_raw_parts(ptr, len, len)
3075 };
3076 let signal = unsafe {
3077 let ptr = out_s.as_mut_ptr();
3078 let len = out_s.len();
3079 core::mem::forget(guard_s);
3080 Vec::from_raw_parts(ptr, len, len)
3081 };
3082 let histogram = unsafe {
3083 let ptr = out_h.as_mut_ptr();
3084 let len = out_h.len();
3085 core::mem::forget(guard_h);
3086 Vec::from_raw_parts(ptr, len, len)
3087 };
3088
3089 Ok(ModGodModeBatchOutput {
3090 wavetrend,
3091 signal,
3092 histogram,
3093 combos,
3094 rows,
3095 cols,
3096 })
3097}
3098
3099pub struct ModGodModeBatchBuilder {
3100 n1: usize,
3101 n2: usize,
3102 n3: usize,
3103 mode: ModGodModeMode,
3104 use_volume: bool,
3105 parallel: bool,
3106}
3107
3108impl Default for ModGodModeBatchBuilder {
3109 fn default() -> Self {
3110 Self {
3111 n1: 17,
3112 n2: 6,
3113 n3: 4,
3114 mode: ModGodModeMode::TraditionMg,
3115 use_volume: false,
3116 parallel: true,
3117 }
3118 }
3119}
3120
3121impl ModGodModeBatchBuilder {
3122 pub fn new() -> Self {
3123 Self::default()
3124 }
3125
3126 pub fn n1(mut self, n1: usize) -> Self {
3127 self.n1 = n1;
3128 self
3129 }
3130
3131 pub fn n2(mut self, n2: usize) -> Self {
3132 self.n2 = n2;
3133 self
3134 }
3135
3136 pub fn n3(mut self, n3: usize) -> Self {
3137 self.n3 = n3;
3138 self
3139 }
3140
3141 pub fn mode(mut self, mode: ModGodModeMode) -> Self {
3142 self.mode = mode;
3143 self
3144 }
3145
3146 pub fn use_volume(mut self, use_volume: bool) -> Self {
3147 self.use_volume = use_volume;
3148 self
3149 }
3150
3151 pub fn parallel(mut self, parallel: bool) -> Self {
3152 self.parallel = parallel;
3153 self
3154 }
3155
3156 pub fn calculate_batch(
3157 &self,
3158 datasets: &[Candles],
3159 ) -> Vec<Result<ModGodModeOutput, ModGodModeError>> {
3160 let params = ModGodModeParams {
3161 n1: Some(self.n1),
3162 n2: Some(self.n2),
3163 n3: Some(self.n3),
3164 mode: Some(self.mode),
3165 use_volume: Some(self.use_volume),
3166 };
3167
3168 let kernel = detect_best_batch_kernel();
3169
3170 if self.parallel {
3171 #[cfg(not(target_arch = "wasm32"))]
3172 {
3173 datasets
3174 .par_iter()
3175 .map(|candles| {
3176 let input = ModGodModeInput::from_candles(candles, params.clone());
3177 mod_god_mode_with_kernel(&input, kernel)
3178 })
3179 .collect()
3180 }
3181 #[cfg(target_arch = "wasm32")]
3182 {
3183 datasets
3184 .iter()
3185 .map(|candles| {
3186 let input = ModGodModeInput::from_candles(candles, params.clone());
3187 mod_god_mode_with_kernel(&input, kernel)
3188 })
3189 .collect()
3190 }
3191 } else {
3192 datasets
3193 .iter()
3194 .map(|candles| {
3195 let input = ModGodModeInput::from_candles(candles, params.clone());
3196 mod_god_mode_with_kernel(&input, kernel)
3197 })
3198 .collect()
3199 }
3200 }
3201}
3202
3203#[cfg(feature = "python")]
3204#[pyfunction(name = "mod_god_mode")]
3205#[pyo3(signature=(high, low, close, volume=None, n1=None, n2=None, n3=None, mode=None, use_volume=None, kernel=None))]
3206pub fn mod_god_mode_py<'py>(
3207 py: Python<'py>,
3208 high: PyReadonlyArray1<'py, f64>,
3209 low: PyReadonlyArray1<'py, f64>,
3210 close: PyReadonlyArray1<'py, f64>,
3211 volume: Option<PyReadonlyArray1<'py, f64>>,
3212 n1: Option<usize>,
3213 n2: Option<usize>,
3214 n3: Option<usize>,
3215 mode: Option<String>,
3216 use_volume: Option<bool>,
3217 kernel: Option<&str>,
3218) -> PyResult<(
3219 Bound<'py, PyArray1<f64>>,
3220 Bound<'py, PyArray1<f64>>,
3221 Bound<'py, PyArray1<f64>>,
3222)> {
3223 let h = high.as_slice()?;
3224 let l = low.as_slice()?;
3225 let c = close.as_slice()?;
3226 let v_opt = volume.as_ref().map(|v| v.as_slice()).transpose()?;
3227 let mode_enum = match mode {
3228 Some(m) => Some(
3229 m.parse::<ModGodModeMode>()
3230 .map_err(|e| PyValueError::new_err(e))?,
3231 ),
3232 None => None,
3233 };
3234 let params = ModGodModeParams {
3235 n1,
3236 n2,
3237 n3,
3238 mode: mode_enum,
3239 use_volume,
3240 };
3241 let kern = validate_kernel(kernel, false).map_err(|e| PyValueError::new_err(e.to_string()))?;
3242 let out = py
3243 .allow_threads(|| {
3244 mod_god_mode_with_kernel(&ModGodModeInput::from_slices(h, l, c, v_opt, params), kern)
3245 })
3246 .map_err(|e| PyValueError::new_err(e.to_string()))?;
3247 Ok((
3248 out.wavetrend.into_pyarray(py),
3249 out.signal.into_pyarray(py),
3250 out.histogram.into_pyarray(py),
3251 ))
3252}
3253
3254#[cfg(feature = "python")]
3255#[pyfunction(name = "mod_god_mode_batch")]
3256#[pyo3(signature=(high, low, close, volume, n1_range, n2_range, n3_range, mode="tradition_mg", kernel=None))]
3257pub fn mod_god_mode_batch_py<'py>(
3258 py: Python<'py>,
3259 high: PyReadonlyArray1<'py, f64>,
3260 low: PyReadonlyArray1<'py, f64>,
3261 close: PyReadonlyArray1<'py, f64>,
3262 volume: Option<PyReadonlyArray1<'py, f64>>,
3263 n1_range: (usize, usize, usize),
3264 n2_range: (usize, usize, usize),
3265 n3_range: (usize, usize, usize),
3266 mode: &str,
3267 kernel: Option<&str>,
3268) -> PyResult<Bound<'py, pyo3::types::PyDict>> {
3269 let h = high.as_slice()?;
3270 let l = low.as_slice()?;
3271 let c = close.as_slice()?;
3272 let v = volume.as_ref().map(|v| v.as_slice()).transpose()?;
3273 let m = mode
3274 .parse::<ModGodModeMode>()
3275 .map_err(|e| PyValueError::new_err(e))?;
3276 let sweep = ModGodModeBatchRange {
3277 n1: n1_range,
3278 n2: n2_range,
3279 n3: n3_range,
3280 mode: m,
3281 };
3282 let kern = validate_kernel(kernel, true).map_err(|e| PyValueError::new_err(e.to_string()))?;
3283 let o = py
3284 .allow_threads(|| mod_god_mode_batch_with_kernel(h, l, c, v, &sweep, kern))
3285 .map_err(|e| PyValueError::new_err(e.to_string()))?;
3286 let d = pyo3::types::PyDict::new(py);
3287 use numpy::IntoPyArray;
3288 d.set_item(
3289 "wavetrend",
3290 o.wavetrend.into_pyarray(py).reshape((o.rows, o.cols))?,
3291 )?;
3292 d.set_item(
3293 "signal",
3294 o.signal.into_pyarray(py).reshape((o.rows, o.cols))?,
3295 )?;
3296 d.set_item(
3297 "histogram",
3298 o.histogram.into_pyarray(py).reshape((o.rows, o.cols))?,
3299 )?;
3300 d.set_item(
3301 "n1s",
3302 o.combos
3303 .iter()
3304 .map(|p| p.n1.unwrap() as u64)
3305 .collect::<Vec<_>>()
3306 .into_pyarray(py),
3307 )?;
3308 d.set_item(
3309 "n2s",
3310 o.combos
3311 .iter()
3312 .map(|p| p.n2.unwrap() as u64)
3313 .collect::<Vec<_>>()
3314 .into_pyarray(py),
3315 )?;
3316 d.set_item(
3317 "n3s",
3318 o.combos
3319 .iter()
3320 .map(|p| p.n3.unwrap() as u64)
3321 .collect::<Vec<_>>()
3322 .into_pyarray(py),
3323 )?;
3324 d.set_item(
3325 "modes",
3326 o.combos
3327 .iter()
3328 .map(|p| format!("{:?}", p.mode.unwrap()))
3329 .collect::<Vec<_>>(),
3330 )?;
3331 d.set_item("rows", o.rows)?;
3332 d.set_item("cols", o.cols)?;
3333 Ok(d.into())
3334}
3335
3336#[cfg(all(feature = "python", feature = "cuda"))]
3337use crate::cuda::{cuda_available, CudaModGodMode};
3338#[cfg(all(feature = "python", feature = "cuda"))]
3339use crate::indicators::moving_averages::alma::DeviceArrayF32Py;
3340#[cfg(all(feature = "python", feature = "cuda"))]
3341use numpy::PyReadonlyArray2;
3342#[cfg(all(feature = "python", feature = "cuda"))]
3343use pyo3::types::PyDict;
3344#[cfg(all(feature = "python", feature = "cuda"))]
3345use pyo3::{pyfunction, PyResult, Python};
3346
3347#[cfg(all(feature = "python", feature = "cuda"))]
3348#[pyfunction(name = "mod_god_mode_cuda_batch_dev")]
3349#[pyo3(signature = (high_f32, low_f32, close_f32, n1_range, n2_range, n3_range, mode="tradition_mg", use_volume=false, volume_f32=None, device_id=0))]
3350pub fn mod_god_mode_cuda_batch_dev_py<'py>(
3351 py: Python<'py>,
3352 high_f32: PyReadonlyArray1<'py, f32>,
3353 low_f32: PyReadonlyArray1<'py, f32>,
3354 close_f32: PyReadonlyArray1<'py, f32>,
3355 n1_range: (usize, usize, usize),
3356 n2_range: (usize, usize, usize),
3357 n3_range: (usize, usize, usize),
3358 mode: &str,
3359 use_volume: bool,
3360 volume_f32: Option<PyReadonlyArray1<'py, f32>>,
3361 device_id: usize,
3362) -> PyResult<Bound<'py, PyDict>> {
3363 use numpy::IntoPyArray;
3364 if !cuda_available() {
3365 return Err(PyValueError::new_err("CUDA not available"));
3366 }
3367 let h = high_f32.as_slice()?;
3368 let l = low_f32.as_slice()?;
3369 let c = close_f32.as_slice()?;
3370 let vol = if use_volume {
3371 Some(
3372 volume_f32
3373 .as_ref()
3374 .ok_or_else(|| PyValueError::new_err("volume required when use_volume=true"))?
3375 .as_slice()?,
3376 )
3377 } else {
3378 None
3379 };
3380 let m = mode
3381 .parse::<ModGodModeMode>()
3382 .map_err(|e| PyValueError::new_err(e))?;
3383 let sweep = ModGodModeBatchRange {
3384 n1: n1_range,
3385 n2: n2_range,
3386 n3: n3_range,
3387 mode: m,
3388 };
3389 let (wt, sig, hist, combos, rows, cols, ctx, dev_id) = py.allow_threads(|| {
3390 let cuda =
3391 CudaModGodMode::new(device_id).map_err(|e| PyValueError::new_err(e.to_string()))?;
3392 let res = cuda
3393 .mod_god_mode_batch_dev(h, l, c, vol, &sweep)
3394 .map_err(|e| PyValueError::new_err(e.to_string()))?;
3395 let out = res.outputs;
3396 let rows = out.rows();
3397 let cols = out.cols();
3398 let ctx = cuda.context_arc();
3399 let dev_id = cuda.device_id();
3400 Ok::<_, PyErr>((
3401 out.wt1, out.wt2, out.hist, res.combos, rows, cols, ctx, dev_id,
3402 ))
3403 })?;
3404 let dict = PyDict::new(py);
3405 dict.set_item(
3406 "wavetrend",
3407 Py::new(
3408 py,
3409 DeviceArrayF32Py {
3410 inner: wt,
3411 _ctx: Some(ctx.clone()),
3412 device_id: Some(dev_id),
3413 },
3414 )?,
3415 )?;
3416 dict.set_item(
3417 "signal",
3418 Py::new(
3419 py,
3420 DeviceArrayF32Py {
3421 inner: sig,
3422 _ctx: Some(ctx.clone()),
3423 device_id: Some(dev_id),
3424 },
3425 )?,
3426 )?;
3427 dict.set_item(
3428 "histogram",
3429 Py::new(
3430 py,
3431 DeviceArrayF32Py {
3432 inner: hist,
3433 _ctx: Some(ctx),
3434 device_id: Some(dev_id),
3435 },
3436 )?,
3437 )?;
3438 dict.set_item(
3439 "n1s",
3440 combos
3441 .iter()
3442 .map(|p| p.n1.unwrap() as u64)
3443 .collect::<Vec<_>>()
3444 .into_pyarray(py),
3445 )?;
3446 dict.set_item(
3447 "n2s",
3448 combos
3449 .iter()
3450 .map(|p| p.n2.unwrap() as u64)
3451 .collect::<Vec<_>>()
3452 .into_pyarray(py),
3453 )?;
3454 dict.set_item(
3455 "n3s",
3456 combos
3457 .iter()
3458 .map(|p| p.n3.unwrap() as u64)
3459 .collect::<Vec<_>>()
3460 .into_pyarray(py),
3461 )?;
3462 dict.set_item(
3463 "modes",
3464 combos
3465 .iter()
3466 .map(|p| format!("{:?}", p.mode.unwrap()))
3467 .collect::<Vec<_>>(),
3468 )?;
3469 dict.set_item("rows", rows)?;
3470 dict.set_item("cols", cols)?;
3471 Ok(dict)
3472}
3473
3474#[cfg(all(feature = "python", feature = "cuda"))]
3475#[pyfunction(name = "mod_god_mode_cuda_many_series_one_param_dev")]
3476#[pyo3(signature = (high_tm_f32, low_tm_f32, close_tm_f32, cols, rows, n1=17, n2=6, n3=4, mode="tradition_mg", use_volume=false, volume_tm_f32=None, device_id=0))]
3477pub fn mod_god_mode_cuda_many_series_one_param_dev_py<'py>(
3478 py: Python<'py>,
3479 high_tm_f32: PyReadonlyArray1<'py, f32>,
3480 low_tm_f32: PyReadonlyArray1<'py, f32>,
3481 close_tm_f32: PyReadonlyArray1<'py, f32>,
3482 cols: usize,
3483 rows: usize,
3484 n1: usize,
3485 n2: usize,
3486 n3: usize,
3487 mode: &str,
3488 use_volume: bool,
3489 volume_tm_f32: Option<PyReadonlyArray1<'py, f32>>,
3490 device_id: usize,
3491) -> PyResult<Bound<'py, PyDict>> {
3492 if !cuda_available() {
3493 return Err(PyValueError::new_err("CUDA not available"));
3494 }
3495 let h = high_tm_f32.as_slice()?;
3496 let l = low_tm_f32.as_slice()?;
3497 let c = close_tm_f32.as_slice()?;
3498 let vol = if use_volume {
3499 Some(
3500 volume_tm_f32
3501 .as_ref()
3502 .ok_or_else(|| PyValueError::new_err("volume required when use_volume=true"))?
3503 .as_slice()?,
3504 )
3505 } else {
3506 None
3507 };
3508 let m = mode
3509 .parse::<ModGodModeMode>()
3510 .map_err(|e| PyValueError::new_err(e))?;
3511 let params = ModGodModeParams {
3512 n1: Some(n1),
3513 n2: Some(n2),
3514 n3: Some(n3),
3515 mode: Some(m),
3516 use_volume: Some(use_volume),
3517 };
3518 let (wt, sig, hist, ctx, dev_id) = py.allow_threads(|| {
3519 let cuda =
3520 CudaModGodMode::new(device_id).map_err(|e| PyValueError::new_err(e.to_string()))?;
3521 cuda.mod_god_mode_many_series_one_param_time_major_dev(h, l, c, vol, cols, rows, ¶ms)
3522 .map(|tr| {
3523 (
3524 tr.wt1,
3525 tr.wt2,
3526 tr.hist,
3527 cuda.context_arc(),
3528 cuda.device_id(),
3529 )
3530 })
3531 .map_err(|e| PyValueError::new_err(e.to_string()))
3532 })?;
3533 let dict = PyDict::new(py);
3534 dict.set_item(
3535 "wavetrend",
3536 Py::new(
3537 py,
3538 DeviceArrayF32Py {
3539 inner: wt,
3540 _ctx: Some(ctx.clone()),
3541 device_id: Some(dev_id),
3542 },
3543 )?,
3544 )?;
3545 dict.set_item(
3546 "signal",
3547 Py::new(
3548 py,
3549 DeviceArrayF32Py {
3550 inner: sig,
3551 _ctx: Some(ctx.clone()),
3552 device_id: Some(dev_id),
3553 },
3554 )?,
3555 )?;
3556 dict.set_item(
3557 "histogram",
3558 Py::new(
3559 py,
3560 DeviceArrayF32Py {
3561 inner: hist,
3562 _ctx: Some(ctx),
3563 device_id: Some(dev_id),
3564 },
3565 )?,
3566 )?;
3567 dict.set_item("rows", rows)?;
3568 dict.set_item("cols", cols)?;
3569 dict.set_item("n1", n1)?;
3570 dict.set_item("n2", n2)?;
3571 dict.set_item("n3", n3)?;
3572 dict.set_item("mode", mode)?;
3573 Ok(dict)
3574}
3575
3576#[cfg(feature = "python")]
3577#[pyclass]
3578pub struct ModGodModeStreamPy {
3579 stream: ModGodModeStream,
3580}
3581
3582#[cfg(feature = "python")]
3583#[pymethods]
3584impl ModGodModeStreamPy {
3585 #[new]
3586 #[pyo3(signature = (n1=17, n2=6, n3=4, mode="tradition_mg", use_volume=false))]
3587 pub fn new(n1: usize, n2: usize, n3: usize, mode: &str, use_volume: bool) -> PyResult<Self> {
3588 let mode_enum = mode
3589 .parse::<ModGodModeMode>()
3590 .map_err(|e| PyValueError::new_err(format!("Invalid mode: {}", e)))?;
3591
3592 Ok(Self {
3593 stream: ModGodModeStream::new(n1, n2, n3, mode_enum, use_volume),
3594 })
3595 }
3596
3597 pub fn update(
3598 &mut self,
3599 high: f64,
3600 low: f64,
3601 close: f64,
3602 volume: Option<f64>,
3603 ) -> Option<(f64, f64, f64)> {
3604 self.stream.update(high, low, close, volume)
3605 }
3606
3607 pub fn reset(&mut self) {
3608 self.stream.reset()
3609 }
3610}
3611
3612#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3613#[wasm_bindgen(js_name = mod_god_mode)]
3614pub fn mod_god_mode_wasm(
3615 high: &[f64],
3616 low: &[f64],
3617 close: &[f64],
3618 volume: Option<Vec<f64>>,
3619 n1: Option<usize>,
3620 n2: Option<usize>,
3621 n3: Option<usize>,
3622 mode: Option<String>,
3623 use_volume: Option<bool>,
3624) -> Result<JsValue, JsValue> {
3625 let mode_enum = if let Some(m) = mode {
3626 match m.parse::<ModGodModeMode>() {
3627 Ok(mode) => Some(mode),
3628 Err(e) => return Err(JsValue::from_str(&format!("Invalid mode: {}", e))),
3629 }
3630 } else {
3631 None
3632 };
3633
3634 let params = ModGodModeParams {
3635 n1,
3636 n2,
3637 n3,
3638 mode: mode_enum,
3639 use_volume,
3640 };
3641
3642 let input = ModGodModeInput::from_slices(high, low, close, volume.as_deref(), params);
3643
3644 match mod_god_mode(&input) {
3645 Ok(output) => {
3646 let result = serde_wasm_bindgen::to_value(&output)
3647 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3648 Ok(result)
3649 }
3650 Err(e) => Err(JsValue::from_str(&e.to_string())),
3651 }
3652}
3653
3654#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3655#[wasm_bindgen]
3656pub fn mod_god_mode_alloc(size: usize) -> *mut f64 {
3657 let mut buf = Vec::<f64>::with_capacity(size);
3658 let ptr = buf.as_mut_ptr();
3659 std::mem::forget(buf);
3660 ptr
3661}
3662
3663#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3664#[wasm_bindgen]
3665pub fn mod_god_mode_free(ptr: *mut f64, size: usize) {
3666 unsafe {
3667 Vec::from_raw_parts(ptr, size, size);
3668 }
3669}
3670
3671#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3672#[wasm_bindgen]
3673pub fn mod_god_mode_into(
3674 high_ptr: *const f64,
3675 low_ptr: *const f64,
3676 close_ptr: *const f64,
3677 vol_ptr: *const f64,
3678 len: usize,
3679 has_volume: bool,
3680 n1: usize,
3681 n2: usize,
3682 n3: usize,
3683 mode: &str,
3684 out_w_ptr: *mut f64,
3685 out_s_ptr: *mut f64,
3686 out_h_ptr: *mut f64,
3687) -> Result<(), JsValue> {
3688 if [
3689 high_ptr as usize,
3690 low_ptr as usize,
3691 close_ptr as usize,
3692 out_w_ptr as usize,
3693 out_s_ptr as usize,
3694 out_h_ptr as usize,
3695 ]
3696 .iter()
3697 .any(|&p| p == 0)
3698 {
3699 return Err(JsValue::from_str("null pointer"));
3700 }
3701 let m = mode
3702 .parse::<ModGodModeMode>()
3703 .map_err(|e| JsValue::from_str(&e))?;
3704 unsafe {
3705 let h = core::slice::from_raw_parts(high_ptr, len);
3706 let l = core::slice::from_raw_parts(low_ptr, len);
3707 let c = core::slice::from_raw_parts(close_ptr, len);
3708 let v = if has_volume {
3709 Some(core::slice::from_raw_parts(vol_ptr, len))
3710 } else {
3711 None
3712 };
3713 let params = ModGodModeParams {
3714 n1: Some(n1),
3715 n2: Some(n2),
3716 n3: Some(n3),
3717 mode: Some(m),
3718 use_volume: Some(has_volume),
3719 };
3720 let out = mod_god_mode_with_kernel(
3721 &ModGodModeInput::from_slices(h, l, c, v, params),
3722 detect_best_kernel(),
3723 )
3724 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3725 core::slice::from_raw_parts_mut(out_w_ptr, len).copy_from_slice(&out.wavetrend);
3726 core::slice::from_raw_parts_mut(out_s_ptr, len).copy_from_slice(&out.signal);
3727 core::slice::from_raw_parts_mut(out_h_ptr, len).copy_from_slice(&out.histogram);
3728 }
3729 Ok(())
3730}
3731
3732#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3733#[derive(Serialize, Deserialize)]
3734pub struct ModGodModeJsFlat {
3735 pub values: Vec<f64>,
3736 pub rows: usize,
3737 pub cols: usize,
3738}
3739
3740#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3741#[wasm_bindgen]
3742pub fn mod_god_mode_into_flat(
3743 high_ptr: *const f64,
3744 low_ptr: *const f64,
3745 close_ptr: *const f64,
3746 vol_ptr: *const f64,
3747 len: usize,
3748 has_volume: bool,
3749 n1: usize,
3750 n2: usize,
3751 n3: usize,
3752 mode: &str,
3753 out_ptr: *mut f64,
3754) -> Result<(), JsValue> {
3755 if [
3756 high_ptr as usize,
3757 low_ptr as usize,
3758 close_ptr as usize,
3759 out_ptr as usize,
3760 ]
3761 .iter()
3762 .any(|&p| p == 0)
3763 {
3764 return Err(JsValue::from_str("null pointer"));
3765 }
3766 let m = mode
3767 .parse::<ModGodModeMode>()
3768 .map_err(|e| JsValue::from_str(&e))?;
3769 unsafe {
3770 let h = core::slice::from_raw_parts(high_ptr, len);
3771 let l = core::slice::from_raw_parts(low_ptr, len);
3772 let c = core::slice::from_raw_parts(close_ptr, len);
3773 let v = if has_volume {
3774 Some(core::slice::from_raw_parts(vol_ptr, len))
3775 } else {
3776 None
3777 };
3778
3779 let wt = core::slice::from_raw_parts_mut(out_ptr, len);
3780 let sig = core::slice::from_raw_parts_mut(out_ptr.add(len), len);
3781 let hist = core::slice::from_raw_parts_mut(out_ptr.add(2 * len), len);
3782
3783 let params = ModGodModeParams {
3784 n1: Some(n1),
3785 n2: Some(n2),
3786 n3: Some(n3),
3787 mode: Some(m),
3788 use_volume: Some(has_volume),
3789 };
3790 let inp = ModGodModeInput::from_slices(h, l, c, v, params);
3791 mod_god_mode_into_slices(wt, sig, hist, &inp, detect_best_kernel())
3792 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3793 }
3794 Ok(())
3795}
3796
3797#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3798#[wasm_bindgen(js_name = mod_god_mode_js_flat)]
3799pub fn mod_god_mode_js_flat(
3800 high: &[f64],
3801 low: &[f64],
3802 close: &[f64],
3803 volume: Option<Vec<f64>>,
3804 n1: usize,
3805 n2: usize,
3806 n3: usize,
3807 mode: &str,
3808 use_volume: bool,
3809) -> Result<JsValue, JsValue> {
3810 let m = mode
3811 .parse::<ModGodModeMode>()
3812 .map_err(|e| JsValue::from_str(&e))?;
3813 let params = ModGodModeParams {
3814 n1: Some(n1),
3815 n2: Some(n2),
3816 n3: Some(n3),
3817 mode: Some(m),
3818 use_volume: Some(use_volume),
3819 };
3820 let out = mod_god_mode_with_kernel(
3821 &ModGodModeInput::from_slices(high, low, close, volume.as_deref(), params),
3822 detect_best_kernel(),
3823 )
3824 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3825
3826 let cols = close.len();
3827 let mut values = Vec::with_capacity(3 * cols);
3828 values.extend_from_slice(&out.wavetrend);
3829 values.extend_from_slice(&out.signal);
3830 values.extend_from_slice(&out.histogram);
3831
3832 serde_wasm_bindgen::to_value(&ModGodModeJsFlat {
3833 values,
3834 rows: 3,
3835 cols,
3836 })
3837 .map_err(|e| JsValue::from_str(&e.to_string()))
3838}
3839
3840#[cfg(test)]
3841mod tests {
3842 use super::*;
3843 use crate::utilities::data_loader::{read_candles_from_csv, Candles};
3844
3845 fn generate_test_candles(len: usize) -> Candles {
3846 let mut open = vec![0.0; len];
3847 let mut high = vec![0.0; len];
3848 let mut low = vec![0.0; len];
3849 let mut close = vec![0.0; len];
3850 let mut volume = vec![1000.0; len];
3851
3852 for i in 0..len {
3853 let base = 100.0 + (i as f64) * 0.1;
3854 close[i] = base + ((i % 10) as f64 - 5.0) * 0.5;
3855 open[i] = if i == 0 { base } else { close[i - 1] };
3856 high[i] = close[i].max(open[i]) + 0.5;
3857 low[i] = close[i].min(open[i]) - 0.5;
3858 volume[i] = 1000.0 + (i as f64) * 10.0;
3859 }
3860
3861 Candles::new(vec![0; len], open, high, low, close, volume)
3862 }
3863
3864 macro_rules! generate_all_mod_god_mode_tests {
3865 ($($test_fn:ident),*) => {
3866 $(
3867 paste::paste! {
3868 #[test]
3869 fn [<$test_fn _scalar>]() {
3870 $test_fn(Kernel::Scalar);
3871 }
3872
3873
3874
3875 #[test]
3876 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64", target_feature = "avx2"))]
3877 fn [<$test_fn _avx2>]() {
3878 $test_fn(Kernel::Avx2);
3879 }
3880
3881 #[test]
3882 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64", target_feature = "avx512f"))]
3883 fn [<$test_fn _avx512>]() {
3884 $test_fn(Kernel::Avx512);
3885 }
3886 }
3887 )*
3888 };
3889 }
3890
3891 fn check_mod_god_mode_basic(kernel: Kernel) {
3892 let close = vec![10.0, 11.0, 12.0, 11.5, 10.5, 11.0, 12.5, 13.0, 12.0, 11.0];
3893 let high = vec![10.5, 11.5, 12.5, 12.0, 11.0, 11.5, 13.0, 13.5, 12.5, 11.5];
3894 let low = vec![9.5, 10.5, 11.5, 11.0, 10.0, 10.5, 12.0, 12.5, 11.5, 10.5];
3895
3896 let params = ModGodModeParams {
3897 n1: Some(3),
3898 n2: Some(2),
3899 n3: Some(2),
3900 mode: Some(ModGodModeMode::TraditionMg),
3901 use_volume: Some(false),
3902 };
3903
3904 let input = ModGodModeInput::from_slices(&high, &low, &close, None, params);
3905 let result = mod_god_mode_with_kernel(&input, kernel);
3906
3907 assert!(result.is_ok());
3908 let output = result.unwrap();
3909 assert_eq!(output.wavetrend.len(), close.len());
3910 assert_eq!(output.signal.len(), close.len());
3911 assert_eq!(output.histogram.len(), close.len());
3912 }
3913
3914 fn check_mod_god_mode_empty_data(kernel: Kernel) {
3915 let params = ModGodModeParams::default();
3916 let input = ModGodModeInput::from_slices(&[], &[], &[], None, params);
3917 let result = mod_god_mode_with_kernel(&input, kernel);
3918
3919 assert!(matches!(result, Err(ModGodModeError::EmptyInputData)));
3920 }
3921
3922 fn check_mod_god_mode_all_nan(kernel: Kernel) {
3923 let nan_data = vec![f64::NAN; 20];
3924 let params = ModGodModeParams {
3925 n1: Some(3),
3926 n2: Some(2),
3927 n3: Some(2),
3928 mode: Some(ModGodModeMode::TraditionMg),
3929 use_volume: Some(false),
3930 };
3931 let input = ModGodModeInput::from_slices(&nan_data, &nan_data, &nan_data, None, params);
3932 let result = mod_god_mode_with_kernel(&input, kernel);
3933
3934 match result {
3935 Ok(output) => {
3936 assert!(output.wavetrend.iter().all(|v| v.is_nan()));
3937 }
3938 Err(e) => {
3939 println!("All NaN test returned error: {:?}", e);
3940 }
3941 }
3942 }
3943
3944 fn check_mod_god_mode_insufficient_data(kernel: Kernel) {
3945 let close = vec![10.0, 11.0];
3946 let high = vec![10.5, 11.5];
3947 let low = vec![9.5, 10.5];
3948
3949 let params = ModGodModeParams {
3950 n1: Some(17),
3951 n2: Some(6),
3952 n3: Some(4),
3953 mode: Some(ModGodModeMode::TraditionMg),
3954 use_volume: Some(false),
3955 };
3956
3957 let input = ModGodModeInput::from_slices(&high, &low, &close, None, params);
3958 let result = mod_god_mode_with_kernel(&input, kernel);
3959
3960 assert!(matches!(
3961 result,
3962 Err(ModGodModeError::NotEnoughValidData { .. })
3963 ));
3964 }
3965
3966 fn check_mod_god_mode_with_volume(kernel: Kernel) {
3967 let close = vec![10.0, 11.0, 12.0, 11.5, 10.5, 11.0, 12.5, 13.0, 12.0, 11.0];
3968 let high = vec![10.5, 11.5, 12.5, 12.0, 11.0, 11.5, 13.0, 13.5, 12.5, 11.5];
3969 let low = vec![9.5, 10.5, 11.5, 11.0, 10.0, 10.5, 12.0, 12.5, 11.5, 10.5];
3970 let volume = vec![
3971 1000.0, 1100.0, 900.0, 1200.0, 800.0, 1300.0, 1500.0, 1400.0, 1100.0, 1000.0,
3972 ];
3973
3974 let params = ModGodModeParams {
3975 n1: Some(3),
3976 n2: Some(2),
3977 n3: Some(2),
3978 mode: Some(ModGodModeMode::GodmodeMg),
3979 use_volume: Some(true),
3980 };
3981
3982 let input = ModGodModeInput::from_slices(&high, &low, &close, Some(&volume), params);
3983 let result = mod_god_mode_with_kernel(&input, kernel);
3984
3985 assert!(result.is_ok());
3986 let output = result.unwrap();
3987 assert_eq!(output.wavetrend.len(), close.len());
3988 }
3989
3990 fn check_mod_god_mode_modes(kernel: Kernel) {
3991 let candles = generate_test_candles(50);
3992
3993 let modes = vec![
3994 ModGodModeMode::Godmode,
3995 ModGodModeMode::Tradition,
3996 ModGodModeMode::GodmodeMg,
3997 ModGodModeMode::TraditionMg,
3998 ];
3999
4000 for mode in modes {
4001 let params = ModGodModeParams {
4002 n1: Some(5),
4003 n2: Some(3),
4004 n3: Some(2),
4005 mode: Some(mode),
4006 use_volume: Some(false),
4007 };
4008
4009 let input = ModGodModeInput::from_candles(&candles, params);
4010 let result = mod_god_mode_with_kernel(&input, kernel);
4011
4012 assert!(result.is_ok(), "Mode {:?} should succeed", mode);
4013 }
4014 }
4015
4016 fn check_mod_god_mode_builder(kernel: Kernel) {
4017 let candles = generate_test_candles(20);
4018
4019 let result = ModGodModeBuilder::new()
4020 .n1(5)
4021 .n2(3)
4022 .n3(2)
4023 .mode(ModGodModeMode::Godmode)
4024 .use_volume(false)
4025 .calculate_with_kernel(ModGodModeData::Candles { candles: &candles }, kernel);
4026
4027 assert!(result.is_ok());
4028 let output = result.unwrap();
4029 assert_eq!(output.wavetrend.len(), candles.close.len());
4030 }
4031
4032 fn check_mod_god_mode_stream(_kernel: Kernel) {
4033 let mut stream = ModGodModeStream::new(3, 2, 2, ModGodModeMode::TraditionMg, false);
4034
4035 let test_data = vec![
4036 (10.5, 9.5, 10.0),
4037 (11.5, 10.5, 11.0),
4038 (12.5, 11.5, 12.0),
4039 (12.0, 11.0, 11.5),
4040 (11.0, 10.0, 10.5),
4041 (11.5, 10.5, 11.0),
4042 (12.0, 11.0, 11.5),
4043 (12.5, 11.5, 12.0),
4044 ];
4045
4046 let mut got_result = false;
4047 for (high, low, close) in test_data {
4048 let result = stream.update(high, low, close, None);
4049 if result.is_some() {
4050 got_result = true;
4051 }
4052 }
4053
4054 assert!(
4055 got_result,
4056 "Stream should produce results after sufficient data"
4057 );
4058 }
4059
4060 fn check_mod_god_mode_batch(kernel: Kernel) {
4061 let datasets: Vec<Candles> = (0..3).map(|_| generate_test_candles(20)).collect();
4062
4063 let batch_builder = ModGodModeBatchBuilder::new()
4064 .n1(5)
4065 .n2(3)
4066 .n3(2)
4067 .mode(ModGodModeMode::TraditionMg)
4068 .parallel(false);
4069
4070 let results = batch_builder.calculate_batch(&datasets);
4071
4072 assert_eq!(results.len(), datasets.len());
4073 for result in results {
4074 assert!(result.is_ok());
4075 }
4076 }
4077
4078 fn check_mod_god_mode_consistency(kernel: Kernel) {
4079 let candles = generate_test_candles(50);
4080 let params = ModGodModeParams {
4081 n1: Some(7),
4082 n2: Some(4),
4083 n3: Some(3),
4084 mode: Some(ModGodModeMode::TraditionMg),
4085 use_volume: Some(false),
4086 };
4087
4088 let input = ModGodModeInput::from_candles(&candles, params.clone());
4089 let result1 = mod_god_mode_with_kernel(&input, kernel).unwrap();
4090 let result2 = mod_god_mode_with_kernel(&input, kernel).unwrap();
4091
4092 for i in 0..result1.wavetrend.len() {
4093 if !result1.wavetrend[i].is_nan() && !result2.wavetrend[i].is_nan() {
4094 assert_eq!(result1.wavetrend[i], result2.wavetrend[i]);
4095 } else if result1.wavetrend[i].is_nan() != result2.wavetrend[i].is_nan() {
4096 panic!("Wavetrend NaN mismatch at index {}", i);
4097 }
4098
4099 if !result1.signal[i].is_nan() && !result2.signal[i].is_nan() {
4100 assert_eq!(result1.signal[i], result2.signal[i]);
4101 } else if result1.signal[i].is_nan() != result2.signal[i].is_nan() {
4102 panic!("Signal NaN mismatch at index {}", i);
4103 }
4104
4105 if !result1.histogram[i].is_nan() && !result2.histogram[i].is_nan() {
4106 assert_eq!(result1.histogram[i], result2.histogram[i]);
4107 } else if result1.histogram[i].is_nan() != result2.histogram[i].is_nan() {
4108 panic!("Histogram NaN mismatch at index {}", i);
4109 }
4110 }
4111 }
4112
4113 fn check_mod_god_mode_accuracy(kernel: Kernel) {
4114 let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
4115 let candles = match read_candles_from_csv(file_path) {
4116 Ok(c) => c,
4117 Err(e) => {
4118 panic!("Failed to read CSV file: {}", e);
4119 }
4120 };
4121
4122 let params = ModGodModeParams {
4123 n1: Some(17),
4124 n2: Some(6),
4125 n3: Some(4),
4126 mode: Some(ModGodModeMode::TraditionMg),
4127 use_volume: Some(true),
4128 };
4129
4130 let input = ModGodModeInput::from_candles(&candles, params);
4131 let result = mod_god_mode_with_kernel(&input, kernel).unwrap();
4132
4133 let expected_last_five = [
4134 61.66219598,
4135 55.92955776,
4136 34.70836488,
4137 39.48824969,
4138 15.74958884,
4139 ];
4140
4141 let non_nan_values: Vec<f64> = result
4142 .wavetrend
4143 .iter()
4144 .filter(|v| !v.is_nan())
4145 .cloned()
4146 .collect();
4147
4148 assert!(
4149 non_nan_values.len() >= 5,
4150 "Not enough non-NaN values: got {}, need at least 5",
4151 non_nan_values.len()
4152 );
4153
4154 let start = non_nan_values.len().saturating_sub(5);
4155 for (i, &val) in non_nan_values[start..].iter().enumerate() {
4156 let diff = (val - expected_last_five[i]).abs();
4157
4158 assert!(
4159 diff < 4.0,
4160 "MOD_GOD_MODE wavetrend mismatch at index {}: got {:.8}, expected {:.8}, diff {:.8}",
4161 i, val, expected_last_five[i], diff
4162 );
4163 }
4164 }
4165
4166 fn check_mod_god_mode_nan_handling(kernel: Kernel) {
4167 let candles = generate_test_candles(50);
4168 let params = ModGodModeParams {
4169 n1: Some(7),
4170 n2: Some(4),
4171 n3: Some(3),
4172 mode: Some(ModGodModeMode::TraditionMg),
4173 use_volume: Some(false),
4174 };
4175
4176 let input = ModGodModeInput::from_candles(&candles, params);
4177 let result = mod_god_mode_with_kernel(&input, kernel);
4178 assert!(result.is_ok());
4179 let output = result.unwrap();
4180
4181 let warmup = 7 + 4 + 3;
4182 for i in warmup..output.wavetrend.len() {
4183 if output.wavetrend[i].is_nan() {
4184 panic!("Found NaN at index {} after warmup period {}", i, warmup);
4185 }
4186 }
4187 }
4188
4189 fn check_mod_god_mode_reinput(kernel: Kernel) {
4190 let candles = generate_test_candles(50);
4191 let params = ModGodModeParams {
4192 n1: Some(5),
4193 n2: Some(3),
4194 n3: Some(2),
4195 mode: Some(ModGodModeMode::TraditionMg),
4196 use_volume: Some(false),
4197 };
4198
4199 let input1 = ModGodModeInput::from_candles(&candles, params.clone());
4200 let result1 = mod_god_mode_with_kernel(&input1, kernel).unwrap();
4201
4202 let input2 = ModGodModeInput::from_slices(
4203 &candles.high,
4204 &candles.low,
4205 &result1.wavetrend,
4206 None,
4207 params,
4208 );
4209 let result2 = mod_god_mode_with_kernel(&input2, kernel);
4210
4211 assert!(result2.is_ok());
4212 }
4213
4214 fn check_mod_god_mode_streaming_parity(_kernel: Kernel) {
4215 let candles = generate_test_candles(50);
4216 let params = ModGodModeParams {
4217 n1: Some(5),
4218 n2: Some(3),
4219 n3: Some(2),
4220 mode: Some(ModGodModeMode::TraditionMg),
4221 use_volume: Some(false),
4222 };
4223
4224 let input = ModGodModeInput::from_candles(&candles, params.clone());
4225 let batch_result = mod_god_mode(&input).unwrap();
4226
4227 let mut stream = ModGodModeStream::try_new(params).unwrap();
4228 let mut stream_results = Vec::new();
4229
4230 for i in 0..candles.close.len() {
4231 let result = stream.update(candles.high[i], candles.low[i], candles.close[i], None);
4232 stream_results.push(result);
4233 }
4234
4235 if let Some(Some((wt, sig, hist))) = stream_results.last() {
4236 let last_idx = batch_result.wavetrend.len() - 1;
4237 if !batch_result.wavetrend[last_idx].is_nan() {
4238 assert!(
4239 (wt - batch_result.wavetrend[last_idx]).abs() < 1e-10,
4240 "Streaming wavetrend mismatch"
4241 );
4242 assert!(
4243 (sig - batch_result.signal[last_idx]).abs() < 1e-10,
4244 "Streaming signal mismatch"
4245 );
4246 assert!(
4247 (hist - batch_result.histogram[last_idx]).abs() < 1e-10,
4248 "Streaming histogram mismatch"
4249 );
4250 }
4251 }
4252 }
4253
4254 fn check_batch_default_row(kernel: Kernel) {
4255 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
4256 let candles = match read_candles_from_csv(file) {
4257 Ok(c) => c,
4258 Err(e) => {
4259 println!("WARNING: Could not read CSV file: {}", e);
4260 println!("Using generated test data instead");
4261 generate_test_candles(20)
4262 }
4263 };
4264 let range = ModGodModeBatchRange {
4265 n1: (5, 5, 0),
4266 n2: (3, 3, 0),
4267 n3: (2, 2, 0),
4268 mode: ModGodModeMode::TraditionMg,
4269 };
4270
4271 let batch_kernel = match kernel {
4272 Kernel::Scalar => Kernel::ScalarBatch,
4273 Kernel::Avx2 => Kernel::Avx2Batch,
4274 Kernel::Avx512 => Kernel::Avx512Batch,
4275 Kernel::Auto => Kernel::Auto,
4276 k if k.is_batch() => k,
4277 _ => Kernel::ScalarBatch,
4278 };
4279
4280 let batch_result = mod_god_mode_batch_with_kernel(
4281 &candles.high,
4282 &candles.low,
4283 &candles.close,
4284 None,
4285 &range,
4286 batch_kernel,
4287 )
4288 .unwrap();
4289
4290 assert_eq!(batch_result.rows, 1);
4291 assert_eq!(batch_result.cols, candles.close.len());
4292 assert_eq!(batch_result.combos.len(), 1);
4293 }
4294
4295 fn check_batch_sweep(kernel: Kernel) {
4296 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
4297 let candles = match read_candles_from_csv(file) {
4298 Ok(c) => c,
4299 Err(e) => {
4300 println!("WARNING: Could not read CSV file: {}", e);
4301 println!("Using generated test data instead");
4302 generate_test_candles(20)
4303 }
4304 };
4305 let range = ModGodModeBatchRange {
4306 n1: (3, 5, 1),
4307 n2: (2, 3, 1),
4308 n3: (2, 2, 0),
4309 mode: ModGodModeMode::TraditionMg,
4310 };
4311
4312 let batch_kernel = match kernel {
4313 Kernel::Scalar => Kernel::ScalarBatch,
4314 Kernel::Avx2 => Kernel::Avx2Batch,
4315 Kernel::Avx512 => Kernel::Avx512Batch,
4316 Kernel::Auto => Kernel::Auto,
4317 k if k.is_batch() => k,
4318 _ => Kernel::ScalarBatch,
4319 };
4320
4321 let batch_result = mod_god_mode_batch_with_kernel(
4322 &candles.high,
4323 &candles.low,
4324 &candles.close,
4325 None,
4326 &range,
4327 batch_kernel,
4328 )
4329 .unwrap();
4330
4331 assert_eq!(batch_result.rows, 6);
4332 assert_eq!(batch_result.combos.len(), 6);
4333
4334 let test_params = ModGodModeParams {
4335 n1: Some(4),
4336 n2: Some(2),
4337 n3: Some(2),
4338 mode: Some(ModGodModeMode::TraditionMg),
4339 use_volume: Some(false),
4340 };
4341 assert!(batch_result.row_for_params(&test_params).is_some());
4342 }
4343
4344 #[cfg(debug_assertions)]
4345 #[test]
4346 fn check_mod_god_mode_no_poison() {
4347 let c = generate_test_candles(64);
4348 let params = ModGodModeParams::default();
4349 let out = mod_god_mode(&ModGodModeInput::from_candles(&c, params)).unwrap();
4350 for v in out
4351 .wavetrend
4352 .iter()
4353 .chain(out.signal.iter())
4354 .chain(out.histogram.iter())
4355 {
4356 if v.is_nan() {
4357 continue;
4358 }
4359 let b = v.to_bits();
4360 assert_ne!(
4361 b, 0x11111111_11111111,
4362 "alloc_with_nan_prefix poison leaked"
4363 );
4364 assert_ne!(b, 0x22222222_22222222, "init_matrix_prefixes poison leaked");
4365 assert_ne!(b, 0x33333333_33333333, "make_uninit_matrix poison leaked");
4366 }
4367 }
4368
4369 #[test]
4370 fn check_mod_god_mode_basic_auto_detect() {
4371 check_mod_god_mode_basic(Kernel::Auto);
4372 }
4373
4374 #[test]
4375 fn check_batch_default_row_auto_detect() {
4376 check_batch_default_row(Kernel::Auto);
4377 }
4378
4379 generate_all_mod_god_mode_tests!(
4380 check_mod_god_mode_basic,
4381 check_mod_god_mode_accuracy,
4382 check_mod_god_mode_empty_data,
4383 check_mod_god_mode_all_nan,
4384 check_mod_god_mode_insufficient_data,
4385 check_mod_god_mode_with_volume,
4386 check_mod_god_mode_modes,
4387 check_mod_god_mode_builder,
4388 check_mod_god_mode_stream,
4389 check_mod_god_mode_batch,
4390 check_mod_god_mode_consistency,
4391 check_mod_god_mode_nan_handling,
4392 check_mod_god_mode_reinput,
4393 check_mod_god_mode_streaming_parity,
4394 check_batch_default_row,
4395 check_batch_sweep
4396 );
4397
4398 #[cfg(test)]
4399 mod proptest_tests {
4400 use super::*;
4401 use proptest::prelude::*;
4402
4403 proptest! {
4404 #[test]
4405 fn test_mod_god_mode_never_panics(
4406 n1 in 1usize..20,
4407 n2 in 1usize..20,
4408 n3 in 1usize..20,
4409 len in 0usize..100,
4410 ) {
4411 let candles = generate_test_candles(len);
4412
4413 let n3_safe = if len > 0 { n3.min(len.saturating_sub(1).max(1)) } else { n3 };
4414 let params = ModGodModeParams {
4415 n1: Some(n1),
4416 n2: Some(n2),
4417 n3: Some(n3_safe),
4418 mode: Some(ModGodModeMode::TraditionMg),
4419 use_volume: Some(false),
4420 };
4421
4422 let input = ModGodModeInput::from_candles(&candles, params);
4423 let _ = mod_god_mode(&input);
4424 }
4425
4426 #[test]
4427 fn test_mod_god_mode_output_length(
4428 n1 in 1usize..10,
4429 n2 in 1usize..10,
4430 n3 in 1usize..10,
4431 len in 20usize..100,
4432 ) {
4433 let candles = generate_test_candles(len);
4434 let params = ModGodModeParams {
4435 n1: Some(n1),
4436 n2: Some(n2),
4437 n3: Some(n3),
4438 mode: Some(ModGodModeMode::TraditionMg),
4439 use_volume: Some(false),
4440 };
4441
4442 let input = ModGodModeInput::from_candles(&candles, params);
4443 if let Ok(output) = mod_god_mode(&input) {
4444 prop_assert_eq!(output.wavetrend.len(), len);
4445 prop_assert_eq!(output.signal.len(), len);
4446 prop_assert_eq!(output.histogram.len(), len);
4447 }
4448 }
4449 }
4450 }
4451
4452 #[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
4453 #[test]
4454 fn test_mod_god_mode_into_matches_api() {
4455 fn eq_or_both_nan_eps(a: f64, b: f64) -> bool {
4456 (a.is_nan() && b.is_nan()) || (a - b).abs() <= 1e-12
4457 }
4458
4459 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
4460 let candles = read_candles_from_csv(file).expect("failed to read candles CSV");
4461 let params = ModGodModeParams::default();
4462 let input = ModGodModeInput::from_candles(&candles, params);
4463
4464 let base = mod_god_mode(&input).expect("baseline mod_god_mode should succeed");
4465
4466 let len = candles.close.len();
4467 let mut wt = vec![0.0; len];
4468 let mut sig = vec![0.0; len];
4469 let mut hist = vec![0.0; len];
4470
4471 mod_god_mode_into(&input, &mut wt, &mut sig, &mut hist)
4472 .expect("mod_god_mode_into should succeed");
4473
4474 assert_eq!(wt.len(), base.wavetrend.len());
4475 assert_eq!(sig.len(), base.signal.len());
4476 assert_eq!(hist.len(), base.histogram.len());
4477
4478 for i in 0..len {
4479 assert!(
4480 eq_or_both_nan_eps(wt[i], base.wavetrend[i]),
4481 "wavetrend mismatch at {}: got {:?} expected {:?}",
4482 i,
4483 wt[i],
4484 base.wavetrend[i]
4485 );
4486 assert!(
4487 eq_or_both_nan_eps(sig[i], base.signal[i]),
4488 "signal mismatch at {}: got {:?} expected {:?}",
4489 i,
4490 sig[i],
4491 base.signal[i]
4492 );
4493 assert!(
4494 eq_or_both_nan_eps(hist[i], base.histogram[i]),
4495 "histogram mismatch at {}: got {:?} expected {:?}",
4496 i,
4497 hist[i],
4498 base.histogram[i]
4499 );
4500 }
4501 }
4502}