Skip to main content

vector_ta/indicators/
mod_god_mode.rs

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, &params)
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}