Skip to main content

vector_ta/indicators/moving_averages/
ma.rs

1use crate::indicators::alma::{alma, AlmaData, AlmaInput, AlmaParams};
2use crate::indicators::cora_wave::{cora_wave, CoraWaveData, CoraWaveInput, CoraWaveParams};
3use crate::indicators::cwma::{cwma, CwmaData, CwmaInput, CwmaParams};
4use crate::indicators::dema::{dema, DemaData, DemaInput, DemaParams};
5use crate::indicators::edcf::{edcf, EdcfData, EdcfInput, EdcfParams};
6use crate::indicators::ehlers_itrend::{
7    ehlers_itrend, EhlersITrendData, EhlersITrendInput, EhlersITrendParams,
8};
9use crate::indicators::ema::{ema, EmaData, EmaInput, EmaParams};
10use crate::indicators::epma::{epma, EpmaData, EpmaInput, EpmaParams};
11use crate::indicators::fwma::{fwma, FwmaData, FwmaInput, FwmaParams};
12use crate::indicators::gaussian::{gaussian, GaussianData, GaussianInput, GaussianParams};
13use crate::indicators::highpass::{highpass, HighPassData, HighPassInput, HighPassParams};
14use crate::indicators::highpass_2_pole::{
15    highpass_2_pole, HighPass2Data, HighPass2Input, HighPass2Params,
16};
17use crate::indicators::hma::{hma, HmaData, HmaInput, HmaParams};
18use crate::indicators::hwma::{hwma, HwmaData, HwmaInput, HwmaParams};
19use crate::indicators::jma::{jma, JmaData, JmaInput, JmaParams};
20use crate::indicators::jsa::{jsa, JsaData, JsaInput, JsaParams};
21use crate::indicators::kama::{kama, KamaData, KamaInput, KamaParams};
22use crate::indicators::linreg::{linreg, LinRegData, LinRegInput, LinRegParams};
23use crate::indicators::maaq::{maaq, MaaqData, MaaqInput, MaaqParams};
24use crate::indicators::mama::{mama, MamaData, MamaInput, MamaParams};
25use crate::indicators::moving_averages::dma::{dma, DmaData, DmaInput, DmaParams};
26use crate::indicators::moving_averages::ehlers_ecema::{
27    ehlers_ecema, EhlersEcemaData, EhlersEcemaInput, EhlersEcemaParams,
28};
29use crate::indicators::moving_averages::ehlers_kama::{
30    ehlers_kama, EhlersKamaData, EhlersKamaInput, EhlersKamaParams,
31};
32use crate::indicators::moving_averages::ehma::{ehma, EhmaData, EhmaInput, EhmaParams};
33use crate::indicators::moving_averages::frama::{frama, FramaInput, FramaParams};
34use crate::indicators::moving_averages::nama::{nama, NamaData, NamaInput, NamaParams};
35use crate::indicators::moving_averages::sama::{sama, SamaData, SamaInput, SamaParams};
36use crate::indicators::moving_averages::volatility_adjusted_ma::{
37    vama, VamaData, VamaInput, VamaParams,
38};
39use crate::indicators::mwdx::{mwdx, MwdxData, MwdxInput, MwdxParams};
40use crate::indicators::nma::{nma, NmaData, NmaInput, NmaParams};
41use crate::indicators::pwma::{pwma, PwmaData, PwmaInput, PwmaParams};
42use crate::indicators::reflex::{reflex, ReflexData, ReflexInput, ReflexParams};
43use crate::indicators::sinwma::{sinwma, SinWmaData, SinWmaInput, SinWmaParams};
44use crate::indicators::sma::{sma, SmaData, SmaInput, SmaParams};
45use crate::indicators::smma::{smma, SmmaData, SmmaInput, SmmaParams};
46use crate::indicators::sqwma::{sqwma, SqwmaData, SqwmaInput, SqwmaParams};
47use crate::indicators::srwma::{srwma, SrwmaData, SrwmaInput, SrwmaParams};
48use crate::indicators::supersmoother::{
49    supersmoother, SuperSmootherData, SuperSmootherInput, SuperSmootherParams,
50};
51use crate::indicators::supersmoother_3_pole::{
52    supersmoother_3_pole, SuperSmoother3PoleData, SuperSmoother3PoleInput, SuperSmoother3PoleParams,
53};
54use crate::indicators::swma::{swma, SwmaData, SwmaInput, SwmaParams};
55use crate::indicators::tema::{tema, TemaData, TemaInput, TemaParams};
56use crate::indicators::tilson::{tilson, TilsonData, TilsonInput, TilsonParams};
57use crate::indicators::trendflex::{trendflex, TrendFlexData, TrendFlexInput, TrendFlexParams};
58use crate::indicators::trima::{trima, TrimaData, TrimaInput, TrimaParams};
59use crate::indicators::vpwma::{vpwma, VpwmaData, VpwmaInput, VpwmaParams};
60use crate::indicators::vwap::{vwap, VwapData, VwapInput, VwapParams};
61use crate::indicators::vwma::{vwma, VwmaData, VwmaInput, VwmaParams};
62use crate::indicators::wilders::{wilders, WildersData, WildersInput, WildersParams};
63use crate::indicators::wma::{wma, WmaData, WmaInput, WmaParams};
64use crate::indicators::zlema::{zlema, ZlemaData, ZlemaInput, ZlemaParams};
65use crate::utilities::data_loader::Candles;
66use crate::utilities::enums::Kernel;
67use std::error::Error;
68use thiserror::Error;
69
70use crate::indicators::alma::alma_with_kernel;
71use crate::indicators::cora_wave::cora_wave_with_kernel;
72use crate::indicators::cwma::cwma_with_kernel;
73use crate::indicators::dema::dema_with_kernel;
74use crate::indicators::edcf::edcf_with_kernel;
75use crate::indicators::ehlers_itrend::ehlers_itrend_with_kernel;
76use crate::indicators::ema::ema_with_kernel;
77use crate::indicators::epma::epma_with_kernel;
78use crate::indicators::fwma::fwma_with_kernel;
79use crate::indicators::gaussian::gaussian_with_kernel;
80use crate::indicators::highpass::highpass_with_kernel;
81use crate::indicators::highpass_2_pole::highpass_2_pole_with_kernel;
82use crate::indicators::hma::hma_with_kernel;
83use crate::indicators::hwma::hwma_with_kernel;
84use crate::indicators::jma::jma_with_kernel;
85use crate::indicators::jsa::jsa_with_kernel;
86use crate::indicators::kama::kama_with_kernel;
87use crate::indicators::linreg::linreg_with_kernel;
88use crate::indicators::maaq::maaq_with_kernel;
89use crate::indicators::mama::mama_with_kernel;
90use crate::indicators::moving_averages::dma::dma_with_kernel;
91use crate::indicators::moving_averages::ehlers_ecema::ehlers_ecema_with_kernel;
92use crate::indicators::moving_averages::ehlers_kama::ehlers_kama_with_kernel;
93use crate::indicators::moving_averages::ehma::ehma_with_kernel;
94use crate::indicators::moving_averages::frama::frama_with_kernel;
95use crate::indicators::moving_averages::nama::nama_with_kernel;
96use crate::indicators::moving_averages::sama::sama_with_kernel;
97use crate::indicators::moving_averages::volatility_adjusted_ma::vama_with_kernel;
98use crate::indicators::mwdx::mwdx_with_kernel;
99use crate::indicators::nma::nma_with_kernel;
100use crate::indicators::pwma::pwma_with_kernel;
101use crate::indicators::reflex::reflex_with_kernel;
102use crate::indicators::sinwma::sinwma_with_kernel;
103use crate::indicators::sma::sma_with_kernel;
104use crate::indicators::smma::smma_with_kernel;
105use crate::indicators::sqwma::sqwma_with_kernel;
106use crate::indicators::srwma::srwma_with_kernel;
107use crate::indicators::supersmoother::supersmoother_with_kernel;
108use crate::indicators::supersmoother_3_pole::supersmoother_3_pole_with_kernel;
109use crate::indicators::swma::swma_with_kernel;
110use crate::indicators::tema::tema_with_kernel;
111use crate::indicators::tilson::tilson_with_kernel;
112use crate::indicators::trendflex::trendflex_with_kernel;
113use crate::indicators::trima::trima_with_kernel;
114use crate::indicators::vpwma::vpwma_with_kernel;
115use crate::indicators::vwap::vwap_with_kernel;
116use crate::indicators::vwma::vwma_with_kernel;
117use crate::indicators::wilders::wilders_with_kernel;
118use crate::indicators::wma::wma_with_kernel;
119use crate::indicators::zlema::zlema_with_kernel;
120
121#[cfg(feature = "python")]
122use crate::utilities::kernel_validation::validate_kernel;
123#[cfg(feature = "python")]
124use numpy::{PyArray1, PyReadonlyArray1};
125#[cfg(feature = "python")]
126use pyo3::exceptions::PyValueError;
127#[cfg(feature = "python")]
128use pyo3::prelude::*;
129
130#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
131use serde::{Deserialize, Serialize};
132#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
133use wasm_bindgen::prelude::*;
134
135#[derive(Debug, Clone)]
136pub enum MaData<'a> {
137    Candles {
138        candles: &'a Candles,
139        source: &'a str,
140    },
141    Slice(&'a [f64]),
142}
143
144#[derive(Debug, Error)]
145pub enum MaError {
146    #[error("Unknown moving average type: {ma_type}")]
147    UnknownType { ma_type: String },
148    #[error("{indicator} requires high/low data, use the indicator directly")]
149    RequiresHighLow { indicator: &'static str },
150    #[error("{indicator} requires volume data, use the indicator directly")]
151    RequiresVolume { indicator: &'static str },
152    #[error("{indicator} returns dual outputs, use the indicator directly")]
153    DualOutputNotSupported { indicator: &'static str },
154
155    #[error("input data is empty")]
156    EmptyInputData,
157    #[error("all input values are NaN")]
158    AllValuesNaN,
159    #[error("invalid period {period} for data length {data_len}")]
160    InvalidPeriod { period: usize, data_len: usize },
161    #[error("not enough valid data: needed {needed}, found {valid}")]
162    NotEnoughValidData { needed: usize, valid: usize },
163    #[error("output length mismatch: expected {expected}, got {got}")]
164    OutputLengthMismatch { expected: usize, got: usize },
165    #[error("invalid sigma value: {sigma}")]
166    InvalidSigma { sigma: f64 },
167    #[error("invalid offset value: {offset}")]
168    InvalidOffset { offset: f64 },
169    #[error("invalid range: start={start}, end={end}, step={step}")]
170    InvalidRange { start: f64, end: f64, step: f64 },
171    #[error("invalid kernel for batch path: {0:?}")]
172    InvalidKernelForBatch(Kernel),
173}
174
175#[inline]
176pub fn ma<'a>(ma_type: &str, data: MaData<'a>, period: usize) -> Result<Vec<f64>, Box<dyn Error>> {
177    match ma_type.to_lowercase().as_str() {
178        "sma" => {
179            let input = match data {
180                MaData::Candles { candles, source } => SmaInput {
181                    data: SmaData::Candles { candles, source },
182                    params: SmaParams {
183                        period: Some(period),
184                    },
185                },
186                MaData::Slice(slice) => SmaInput {
187                    data: SmaData::Slice(slice),
188                    params: SmaParams {
189                        period: Some(period),
190                    },
191                },
192            };
193            let output = sma(&input)?;
194            Ok(output.values)
195        }
196
197        "alma" => {
198            let input = match data {
199                MaData::Candles { candles, source } => AlmaInput {
200                    data: AlmaData::Candles { candles, source },
201                    params: AlmaParams {
202                        period: Some(period),
203                        offset: None,
204                        sigma: None,
205                    },
206                },
207                MaData::Slice(slice) => AlmaInput {
208                    data: AlmaData::Slice(slice),
209                    params: AlmaParams {
210                        period: Some(period),
211                        offset: None,
212                        sigma: None,
213                    },
214                },
215            };
216            let output = alma(&input)?;
217            Ok(output.values)
218        }
219
220        "cwma" => {
221            let input = match data {
222                MaData::Candles { candles, source } => CwmaInput {
223                    data: CwmaData::Candles { candles, source },
224                    params: CwmaParams {
225                        period: Some(period),
226                    },
227                },
228                MaData::Slice(slice) => CwmaInput {
229                    data: CwmaData::Slice(slice),
230                    params: CwmaParams {
231                        period: Some(period),
232                    },
233                },
234            };
235            let output = cwma(&input)?;
236            Ok(output.values)
237        }
238
239        "cora_wave" => {
240            let input = match data {
241                MaData::Candles { candles, source } => CoraWaveInput {
242                    data: CoraWaveData::Candles { candles, source },
243                    params: CoraWaveParams {
244                        period: Some(period),
245                        r_multi: None,
246                        smooth: None,
247                    },
248                },
249                MaData::Slice(slice) => CoraWaveInput {
250                    data: CoraWaveData::Slice(slice),
251                    params: CoraWaveParams {
252                        period: Some(period),
253                        r_multi: None,
254                        smooth: None,
255                    },
256                },
257            };
258            let output = cora_wave(&input)?;
259            Ok(output.values)
260        }
261
262        "dema" => {
263            let input = match data {
264                MaData::Candles { candles, source } => DemaInput {
265                    data: DemaData::Candles { candles, source },
266                    params: DemaParams {
267                        period: Some(period),
268                    },
269                },
270                MaData::Slice(slice) => DemaInput {
271                    data: DemaData::Slice(slice),
272                    params: DemaParams {
273                        period: Some(period),
274                    },
275                },
276            };
277            let output = dema(&input)?;
278            Ok(output.values)
279        }
280
281        "edcf" => {
282            let input = match data {
283                MaData::Candles { candles, source } => EdcfInput {
284                    data: EdcfData::Candles { candles, source },
285                    params: EdcfParams {
286                        period: Some(period),
287                    },
288                },
289                MaData::Slice(slice) => EdcfInput {
290                    data: EdcfData::Slice(slice),
291                    params: EdcfParams {
292                        period: Some(period),
293                    },
294                },
295            };
296            let output = edcf(&input)?;
297            Ok(output.values)
298        }
299
300        "ema" => {
301            let input = match data {
302                MaData::Candles { candles, source } => EmaInput {
303                    data: EmaData::Candles { candles, source },
304                    params: EmaParams {
305                        period: Some(period),
306                    },
307                },
308                MaData::Slice(slice) => EmaInput {
309                    data: EmaData::Slice(slice),
310                    params: EmaParams {
311                        period: Some(period),
312                    },
313                },
314            };
315            let output = ema(&input)?;
316            Ok(output.values)
317        }
318
319        "epma" => {
320            let input = match data {
321                MaData::Candles { candles, source } => EpmaInput {
322                    data: EpmaData::Candles { candles, source },
323                    params: EpmaParams {
324                        period: Some(period),
325                        offset: None,
326                    },
327                },
328                MaData::Slice(slice) => EpmaInput {
329                    data: EpmaData::Slice(slice),
330                    params: EpmaParams {
331                        period: Some(period),
332                        offset: None,
333                    },
334                },
335            };
336            let output = epma(&input)?;
337            Ok(output.values)
338        }
339
340        "fwma" => {
341            let input = match data {
342                MaData::Candles { candles, source } => FwmaInput {
343                    data: FwmaData::Candles { candles, source },
344                    params: FwmaParams {
345                        period: Some(period),
346                    },
347                },
348                MaData::Slice(slice) => FwmaInput {
349                    data: FwmaData::Slice(slice),
350                    params: FwmaParams {
351                        period: Some(period),
352                    },
353                },
354            };
355            let output = fwma(&input)?;
356            Ok(output.values)
357        }
358
359        "gaussian" => {
360            let input = match data {
361                MaData::Candles { candles, source } => GaussianInput {
362                    data: GaussianData::Candles { candles, source },
363                    params: GaussianParams {
364                        period: Some(period),
365                        poles: None,
366                    },
367                },
368                MaData::Slice(slice) => GaussianInput {
369                    data: GaussianData::Slice(slice),
370                    params: GaussianParams {
371                        period: Some(period),
372                        poles: None,
373                    },
374                },
375            };
376            let output = gaussian(&input)?;
377            Ok(output.values)
378        }
379
380        "highpass" => {
381            let input = match data {
382                MaData::Candles { candles, source } => HighPassInput {
383                    data: HighPassData::Candles { candles, source },
384                    params: HighPassParams {
385                        period: Some(period),
386                    },
387                },
388                MaData::Slice(slice) => HighPassInput {
389                    data: HighPassData::Slice(slice),
390                    params: HighPassParams {
391                        period: Some(period),
392                    },
393                },
394            };
395            let output = highpass(&input)?;
396            Ok(output.values)
397        }
398
399        "highpass2" | "highpass_2_pole" => {
400            let input = match data {
401                MaData::Candles { candles, source } => HighPass2Input {
402                    data: HighPass2Data::Candles { candles, source },
403                    params: HighPass2Params {
404                        period: Some(period),
405                        k: Some(0.707),
406                    },
407                },
408                MaData::Slice(slice) => HighPass2Input {
409                    data: HighPass2Data::Slice(slice),
410                    params: HighPass2Params {
411                        period: Some(period),
412                        k: Some(0.707),
413                    },
414                },
415            };
416            let output = highpass_2_pole(&input)?;
417            Ok(output.values)
418        }
419
420        "hma" => {
421            let input = match data {
422                MaData::Candles { candles, source } => HmaInput {
423                    data: HmaData::Candles { candles, source },
424                    params: HmaParams {
425                        period: Some(period),
426                    },
427                },
428                MaData::Slice(slice) => HmaInput {
429                    data: HmaData::Slice(slice),
430                    params: HmaParams {
431                        period: Some(period),
432                    },
433                },
434            };
435            let output = hma(&input)?;
436            Ok(output.values)
437        }
438
439        "ehlers_itrend" => {
440            let input = match data {
441                MaData::Candles { candles, source } => EhlersITrendInput {
442                    data: EhlersITrendData::Candles { candles, source },
443                    params: EhlersITrendParams {
444                        warmup_bars: Some(20),
445                        max_dc_period: Some(period),
446                    },
447                },
448                MaData::Slice(slice) => EhlersITrendInput {
449                    data: EhlersITrendData::Slice(slice),
450                    params: EhlersITrendParams {
451                        warmup_bars: Some(20),
452                        max_dc_period: Some(period),
453                    },
454                },
455            };
456            let output = ehlers_itrend(&input)?;
457            Ok(output.values)
458        }
459
460        "hwma" => {
461            let input = match data {
462                MaData::Candles { candles, source } => HwmaInput {
463                    data: HwmaData::Candles { candles, source },
464                    params: HwmaParams {
465                        na: None,
466                        nb: None,
467                        nc: None,
468                    },
469                },
470                MaData::Slice(slice) => HwmaInput {
471                    data: HwmaData::Slice(slice),
472                    params: HwmaParams {
473                        na: None,
474                        nb: None,
475                        nc: None,
476                    },
477                },
478            };
479            let output = hwma(&input)?;
480            Ok(output.values)
481        }
482
483        "jma" => {
484            let input = match data {
485                MaData::Candles { candles, source } => JmaInput {
486                    data: JmaData::Candles { candles, source },
487                    params: JmaParams {
488                        period: Some(period),
489                        phase: None,
490                        power: None,
491                    },
492                },
493                MaData::Slice(slice) => JmaInput {
494                    data: JmaData::Slice(slice),
495                    params: JmaParams {
496                        period: Some(period),
497                        phase: None,
498                        power: None,
499                    },
500                },
501            };
502            let output = jma(&input)?;
503            Ok(output.values)
504        }
505
506        "jsa" => {
507            let input = match data {
508                MaData::Candles { candles, source } => JsaInput {
509                    data: JsaData::Candles { candles, source },
510                    params: JsaParams {
511                        period: Some(period),
512                    },
513                },
514                MaData::Slice(slice) => JsaInput {
515                    data: JsaData::Slice(slice),
516                    params: JsaParams {
517                        period: Some(period),
518                    },
519                },
520            };
521            let output = jsa(&input)?;
522            Ok(output.values)
523        }
524
525        "kama" => {
526            let input = match data {
527                MaData::Candles { candles, source } => KamaInput {
528                    data: KamaData::Candles { candles, source },
529                    params: KamaParams {
530                        period: Some(period),
531                    },
532                },
533                MaData::Slice(slice) => KamaInput {
534                    data: KamaData::Slice(slice),
535                    params: KamaParams {
536                        period: Some(period),
537                    },
538                },
539            };
540            let output = kama(&input)?;
541            Ok(output.values)
542        }
543
544        "linreg" => {
545            let input = match data {
546                MaData::Candles { candles, source } => LinRegInput {
547                    data: LinRegData::Candles { candles, source },
548                    params: LinRegParams {
549                        period: Some(period),
550                    },
551                },
552                MaData::Slice(s) => LinRegInput {
553                    data: LinRegData::Slice(s),
554                    params: LinRegParams {
555                        period: Some(period),
556                    },
557                },
558            };
559            let output = linreg(&input)?;
560            Ok(output.values)
561        }
562
563        "maaq" => {
564            let slow = period.checked_mul(2).ok_or(MaError::InvalidPeriod {
565                period,
566                data_len: 0,
567            })?;
568            let input = match data {
569                MaData::Candles { candles, source } => MaaqInput {
570                    data: MaaqData::Candles { candles, source },
571                    params: MaaqParams {
572                        period: Some(period),
573                        fast_period: Some(period / 2),
574                        slow_period: Some(slow),
575                    },
576                },
577                MaData::Slice(s) => MaaqInput {
578                    data: MaaqData::Slice(s),
579                    params: MaaqParams {
580                        period: Some(period),
581                        fast_period: Some(period / 2),
582                        slow_period: Some(slow),
583                    },
584                },
585            };
586            let output = maaq(&input)?;
587            Ok(output.values)
588        }
589
590        "mama" => {
591            let input = match data {
592                MaData::Candles { candles, source } => {
593                    MamaInput::from_candles(candles, source, MamaParams::default())
594                }
595                MaData::Slice(s) => MamaInput::from_slice(s, MamaParams::default()),
596            };
597            let output = mama(&input)?;
598            Ok(output.mama_values)
599        }
600
601        "mwdx" => {
602            let input = match data {
603                MaData::Candles { candles, source } => MwdxInput {
604                    data: MwdxData::Candles { candles, source },
605                    params: MwdxParams { factor: None },
606                },
607                MaData::Slice(s) => MwdxInput {
608                    data: MwdxData::Slice(s),
609                    params: MwdxParams { factor: None },
610                },
611            };
612            let output = mwdx(&input)?;
613            Ok(output.values)
614        }
615
616        "nma" => {
617            let input = match data {
618                MaData::Candles { candles, source } => NmaInput {
619                    data: NmaData::Candles { candles, source },
620                    params: NmaParams {
621                        period: Some(period),
622                    },
623                },
624                MaData::Slice(s) => NmaInput {
625                    data: NmaData::Slice(s),
626                    params: NmaParams {
627                        period: Some(period),
628                    },
629                },
630            };
631            let output = nma(&input)?;
632            Ok(output.values)
633        }
634
635        "pwma" => {
636            let input = match data {
637                MaData::Candles { candles, source } => PwmaInput {
638                    data: PwmaData::Candles { candles, source },
639                    params: PwmaParams {
640                        period: Some(period),
641                    },
642                },
643                MaData::Slice(s) => PwmaInput {
644                    data: PwmaData::Slice(s),
645                    params: PwmaParams {
646                        period: Some(period),
647                    },
648                },
649            };
650            let output = pwma(&input)?;
651            Ok(output.values)
652        }
653
654        "reflex" => {
655            let input = match data {
656                MaData::Candles { candles, source } => ReflexInput {
657                    data: ReflexData::Candles { candles, source },
658                    params: ReflexParams {
659                        period: Some(period),
660                    },
661                },
662                MaData::Slice(s) => ReflexInput {
663                    data: ReflexData::Slice(s),
664                    params: ReflexParams {
665                        period: Some(period),
666                    },
667                },
668            };
669            let output = reflex(&input)?;
670            Ok(output.values)
671        }
672
673        "sinwma" => {
674            let input = match data {
675                MaData::Candles { candles, source } => SinWmaInput {
676                    data: SinWmaData::Candles { candles, source },
677                    params: SinWmaParams {
678                        period: Some(period),
679                    },
680                },
681                MaData::Slice(s) => SinWmaInput {
682                    data: SinWmaData::Slice(s),
683                    params: SinWmaParams {
684                        period: Some(period),
685                    },
686                },
687            };
688            let output = sinwma(&input)?;
689            Ok(output.values)
690        }
691
692        "smma" => {
693            let input = match data {
694                MaData::Candles { candles, source } => SmmaInput {
695                    data: SmmaData::Candles { candles, source },
696                    params: SmmaParams {
697                        period: Some(period),
698                    },
699                },
700                MaData::Slice(s) => SmmaInput {
701                    data: SmmaData::Slice(s),
702                    params: SmmaParams {
703                        period: Some(period),
704                    },
705                },
706            };
707            let output = smma(&input)?;
708            Ok(output.values)
709        }
710
711        "sqwma" => {
712            let input = match data {
713                MaData::Candles { candles, source } => SqwmaInput {
714                    data: SqwmaData::Candles { candles, source },
715                    params: SqwmaParams {
716                        period: Some(period),
717                    },
718                },
719                MaData::Slice(s) => SqwmaInput {
720                    data: SqwmaData::Slice(s),
721                    params: SqwmaParams {
722                        period: Some(period),
723                    },
724                },
725            };
726            let output = sqwma(&input)?;
727            Ok(output.values)
728        }
729
730        "srwma" => {
731            let input = match data {
732                MaData::Candles { candles, source } => SrwmaInput {
733                    data: SrwmaData::Candles { candles, source },
734                    params: SrwmaParams {
735                        period: Some(period),
736                    },
737                },
738                MaData::Slice(s) => SrwmaInput {
739                    data: SrwmaData::Slice(s),
740                    params: SrwmaParams {
741                        period: Some(period),
742                    },
743                },
744            };
745            let output = srwma(&input)?;
746            Ok(output.values)
747        }
748
749        "supersmoother" => {
750            let input = match data {
751                MaData::Candles { candles, source } => SuperSmootherInput {
752                    data: SuperSmootherData::Candles { candles, source },
753                    params: SuperSmootherParams {
754                        period: Some(period),
755                    },
756                },
757                MaData::Slice(s) => SuperSmootherInput {
758                    data: SuperSmootherData::Slice(s),
759                    params: SuperSmootherParams {
760                        period: Some(period),
761                    },
762                },
763            };
764            let output = supersmoother(&input)?;
765            Ok(output.values)
766        }
767
768        "supersmoother_3_pole" => {
769            let input = match data {
770                MaData::Candles { candles, source } => SuperSmoother3PoleInput {
771                    data: SuperSmoother3PoleData::Candles { candles, source },
772                    params: SuperSmoother3PoleParams {
773                        period: Some(period),
774                    },
775                },
776                MaData::Slice(s) => SuperSmoother3PoleInput {
777                    data: SuperSmoother3PoleData::Slice(s),
778                    params: SuperSmoother3PoleParams {
779                        period: Some(period),
780                    },
781                },
782            };
783            let output = supersmoother_3_pole(&input)?;
784            Ok(output.values)
785        }
786
787        "swma" => {
788            let input = match data {
789                MaData::Candles { candles, source } => SwmaInput {
790                    data: SwmaData::Candles { candles, source },
791                    params: SwmaParams {
792                        period: Some(period),
793                    },
794                },
795                MaData::Slice(s) => SwmaInput {
796                    data: SwmaData::Slice(s),
797                    params: SwmaParams {
798                        period: Some(period),
799                    },
800                },
801            };
802            let output = swma(&input)?;
803            Ok(output.values)
804        }
805
806        "tema" => {
807            let input = match data {
808                MaData::Candles { candles, source } => TemaInput {
809                    data: TemaData::Candles { candles, source },
810                    params: TemaParams {
811                        period: Some(period),
812                    },
813                },
814                MaData::Slice(s) => TemaInput {
815                    data: TemaData::Slice(s),
816                    params: TemaParams {
817                        period: Some(period),
818                    },
819                },
820            };
821            let output = tema(&input)?;
822            Ok(output.values)
823        }
824
825        "tilson" => {
826            let input = match data {
827                MaData::Candles { candles, source } => TilsonInput {
828                    data: TilsonData::Candles { candles, source },
829                    params: TilsonParams {
830                        period: Some(period),
831                        volume_factor: None,
832                    },
833                },
834                MaData::Slice(s) => TilsonInput {
835                    data: TilsonData::Slice(s),
836                    params: TilsonParams {
837                        period: Some(period),
838                        volume_factor: None,
839                    },
840                },
841            };
842            let output = tilson(&input)?;
843            Ok(output.values)
844        }
845
846        "trendflex" => {
847            let input = match data {
848                MaData::Candles { candles, source } => TrendFlexInput {
849                    data: TrendFlexData::Candles { candles, source },
850                    params: TrendFlexParams {
851                        period: Some(period),
852                    },
853                },
854                MaData::Slice(s) => TrendFlexInput {
855                    data: TrendFlexData::Slice(s),
856                    params: TrendFlexParams {
857                        period: Some(period),
858                    },
859                },
860            };
861            let output = trendflex(&input)?;
862            Ok(output.values)
863        }
864
865        "trima" => {
866            let input = match data {
867                MaData::Candles { candles, source } => TrimaInput {
868                    data: TrimaData::Candles { candles, source },
869                    params: TrimaParams {
870                        period: Some(period),
871                    },
872                },
873                MaData::Slice(s) => TrimaInput {
874                    data: TrimaData::Slice(s),
875                    params: TrimaParams {
876                        period: Some(period),
877                    },
878                },
879            };
880            let output = trima(&input)?;
881            Ok(output.values)
882        }
883
884        "vpwma" => {
885            if let MaData::Candles { candles, source } = data {
886                let input = VpwmaInput {
887                    data: VpwmaData::Candles { candles, source },
888                    params: VpwmaParams {
889                        period: Some(period),
890                        power: None,
891                    },
892                };
893                let output = vpwma(&input)?;
894                Ok(output.values)
895            } else {
896                eprintln!("Unknown data type for 'vpwma'. Defaulting to 'sma'.");
897
898                let input = match data {
899                    MaData::Candles { candles, source } => SmaInput::from_candles(
900                        candles,
901                        source,
902                        SmaParams {
903                            period: Some(period),
904                        },
905                    ),
906                    MaData::Slice(slice) => SmaInput::from_slice(
907                        slice,
908                        SmaParams {
909                            period: Some(period),
910                        },
911                    ),
912                };
913                let output = sma(&input)?;
914                Ok(output.values)
915            }
916        }
917
918        "vwap" => {
919            if let MaData::Candles { candles, source } = data {
920                let input = VwapInput {
921                    data: VwapData::Candles { candles, source },
922                    params: VwapParams { anchor: None },
923                };
924                let output = vwap(&input)?;
925                Ok(output.values)
926            } else {
927                eprintln!("Unknown data type for 'vwap'. Defaulting to 'sma'.");
928
929                let input = match data {
930                    MaData::Candles { candles, source } => SmaInput::from_candles(
931                        candles,
932                        source,
933                        SmaParams {
934                            period: Some(period),
935                        },
936                    ),
937                    MaData::Slice(slice) => SmaInput::from_slice(
938                        slice,
939                        SmaParams {
940                            period: Some(period),
941                        },
942                    ),
943                };
944                let output = sma(&input)?;
945                Ok(output.values)
946            }
947        }
948        "vwma" => {
949            if let MaData::Candles { candles, source } = data {
950                let input = VwmaInput {
951                    data: VwmaData::Candles { candles, source },
952                    params: VwmaParams {
953                        period: Some(period),
954                    },
955                };
956                let output = vwma(&input)?;
957                Ok(output.values)
958            } else {
959                eprintln!("Unknown data type for 'vwma'. Defaulting to 'sma'.");
960
961                let input = match data {
962                    MaData::Candles { candles, source } => SmaInput::from_candles(
963                        candles,
964                        source,
965                        SmaParams {
966                            period: Some(period),
967                        },
968                    ),
969                    MaData::Slice(slice) => SmaInput::from_slice(
970                        slice,
971                        SmaParams {
972                            period: Some(period),
973                        },
974                    ),
975                };
976                let output = sma(&input)?;
977                Ok(output.values)
978            }
979        }
980
981        "wilders" => {
982            let input = match data {
983                MaData::Candles { candles, source } => WildersInput {
984                    data: WildersData::Candles { candles, source },
985                    params: WildersParams {
986                        period: Some(period),
987                    },
988                },
989                MaData::Slice(s) => WildersInput {
990                    data: WildersData::Slice(s),
991                    params: WildersParams {
992                        period: Some(period),
993                    },
994                },
995            };
996            let output = wilders(&input)?;
997            Ok(output.values)
998        }
999
1000        "wma" => {
1001            let input = match data {
1002                MaData::Candles { candles, source } => WmaInput {
1003                    data: WmaData::Candles { candles, source },
1004                    params: WmaParams {
1005                        period: Some(period),
1006                    },
1007                },
1008                MaData::Slice(s) => WmaInput {
1009                    data: WmaData::Slice(s),
1010                    params: WmaParams {
1011                        period: Some(period),
1012                    },
1013                },
1014            };
1015            let output = wma(&input)?;
1016            Ok(output.values)
1017        }
1018
1019        "zlema" => {
1020            let input = match data {
1021                MaData::Candles { candles, source } => ZlemaInput {
1022                    data: ZlemaData::Candles { candles, source },
1023                    params: ZlemaParams {
1024                        period: Some(period),
1025                    },
1026                },
1027                MaData::Slice(s) => ZlemaInput {
1028                    data: ZlemaData::Slice(s),
1029                    params: ZlemaParams {
1030                        period: Some(period),
1031                    },
1032                },
1033            };
1034            let output = zlema(&input)?;
1035            Ok(output.values)
1036        }
1037
1038        "buff_averages" => {
1039            return Err(MaError::RequiresVolume {
1040                indicator: "buff_averages",
1041            }
1042            .into());
1043        }
1044
1045        "dma" => {
1046            let input = match data {
1047                MaData::Candles { candles, source } => DmaInput {
1048                    data: DmaData::Candles { candles, source },
1049                    params: DmaParams {
1050                        ema_length: Some(period),
1051                        ..Default::default()
1052                    },
1053                },
1054                MaData::Slice(s) => DmaInput {
1055                    data: DmaData::Slice(s),
1056                    params: DmaParams {
1057                        ema_length: Some(period),
1058                        ..Default::default()
1059                    },
1060                },
1061            };
1062            let output = dma(&input)?;
1063            Ok(output.values)
1064        }
1065
1066        "ehlers_ecema" => {
1067            let input = match data {
1068                MaData::Candles { candles, source } => EhlersEcemaInput {
1069                    data: EhlersEcemaData::Candles { candles, source },
1070                    params: EhlersEcemaParams {
1071                        length: Some(period),
1072                        ..Default::default()
1073                    },
1074                },
1075                MaData::Slice(s) => EhlersEcemaInput {
1076                    data: EhlersEcemaData::Slice(s),
1077                    params: EhlersEcemaParams {
1078                        length: Some(period),
1079                        ..Default::default()
1080                    },
1081                },
1082            };
1083            let output = ehlers_ecema(&input)?;
1084            Ok(output.values)
1085        }
1086
1087        "ehlers_kama" => {
1088            let input = match data {
1089                MaData::Candles { candles, source } => EhlersKamaInput {
1090                    data: EhlersKamaData::Candles { candles, source },
1091                    params: EhlersKamaParams {
1092                        period: Some(period),
1093                        ..Default::default()
1094                    },
1095                },
1096                MaData::Slice(s) => EhlersKamaInput {
1097                    data: EhlersKamaData::Slice(s),
1098                    params: EhlersKamaParams {
1099                        period: Some(period),
1100                        ..Default::default()
1101                    },
1102                },
1103            };
1104            let output = ehlers_kama(&input)?;
1105            Ok(output.values)
1106        }
1107
1108        "ehlers_pma" => {
1109            return Err(MaError::DualOutputNotSupported {
1110                indicator: "ehlers_pma",
1111            }
1112            .into());
1113        }
1114
1115        "ehma" => {
1116            let input = match data {
1117                MaData::Candles { candles, source } => EhmaInput {
1118                    data: EhmaData::Candles { candles, source },
1119                    params: EhmaParams {
1120                        period: Some(period),
1121                        ..Default::default()
1122                    },
1123                },
1124                MaData::Slice(s) => EhmaInput {
1125                    data: EhmaData::Slice(s),
1126                    params: EhmaParams {
1127                        period: Some(period),
1128                        ..Default::default()
1129                    },
1130                },
1131            };
1132            let output = ehma(&input)?;
1133            Ok(output.values)
1134        }
1135
1136        "frama" => {
1137            let input = match data {
1138                MaData::Candles { candles, .. } => FramaInput::from_candles(
1139                    candles,
1140                    FramaParams {
1141                        window: Some(period),
1142                        ..Default::default()
1143                    },
1144                ),
1145                MaData::Slice(slice) => FramaInput::from_slices(
1146                    slice,
1147                    slice,
1148                    slice,
1149                    FramaParams {
1150                        window: Some(period),
1151                        ..Default::default()
1152                    },
1153                ),
1154            };
1155            let output = frama(&input)?;
1156            Ok(output.values)
1157        }
1158
1159        "nama" => {
1160            let input = match data {
1161                MaData::Candles { candles, source } => NamaInput {
1162                    data: NamaData::Candles { candles, source },
1163                    params: NamaParams {
1164                        period: Some(period),
1165                        ..Default::default()
1166                    },
1167                },
1168                MaData::Slice(s) => NamaInput {
1169                    data: NamaData::Slice(s),
1170                    params: NamaParams {
1171                        period: Some(period),
1172                        ..Default::default()
1173                    },
1174                },
1175            };
1176            let output = nama(&input)?;
1177            Ok(output.values)
1178        }
1179
1180        "sama" => {
1181            let input = match data {
1182                MaData::Candles { candles, source } => SamaInput {
1183                    data: SamaData::Candles { candles, source },
1184                    params: SamaParams {
1185                        length: Some(period),
1186                        ..Default::default()
1187                    },
1188                },
1189                MaData::Slice(s) => SamaInput {
1190                    data: SamaData::Slice(s),
1191                    params: SamaParams {
1192                        length: Some(period),
1193                        ..Default::default()
1194                    },
1195                },
1196            };
1197            let output = sama(&input)?;
1198            Ok(output.values)
1199        }
1200
1201        "tradjema" => {
1202            return Err(MaError::RequiresHighLow {
1203                indicator: "tradjema",
1204            }
1205            .into());
1206        }
1207
1208        "uma" => {
1209            return Err(MaError::RequiresVolume { indicator: "uma" }.into());
1210        }
1211
1212        "volatility_adjusted_ma" | "vama" => {
1213            let input = match data {
1214                MaData::Candles { candles, source } => VamaInput {
1215                    data: VamaData::Candles { candles, source },
1216                    params: VamaParams {
1217                        base_period: Some(period),
1218                        ..Default::default()
1219                    },
1220                },
1221                MaData::Slice(s) => VamaInput {
1222                    data: VamaData::Slice(s),
1223                    params: VamaParams {
1224                        base_period: Some(period),
1225                        ..Default::default()
1226                    },
1227                },
1228            };
1229            let output = vama(&input)?;
1230            Ok(output.values)
1231        }
1232
1233        "volume_adjusted_ma" => {
1234            return Err(MaError::RequiresVolume {
1235                indicator: "volume_adjusted_ma",
1236            }
1237            .into());
1238        }
1239
1240        _ => {
1241            return Err(MaError::UnknownType {
1242                ma_type: ma_type.to_string(),
1243            }
1244            .into());
1245        }
1246    }
1247}
1248
1249#[inline]
1250pub fn ma_with_kernel<'a>(
1251    ma_type: &str,
1252    data: MaData<'a>,
1253    period: usize,
1254    kernel: Kernel,
1255) -> Result<Vec<f64>, Box<dyn Error>> {
1256    match ma_type.to_lowercase().as_str() {
1257        "sma" => {
1258            let input = match data {
1259                MaData::Candles { candles, source } => SmaInput {
1260                    data: SmaData::Candles { candles, source },
1261                    params: SmaParams {
1262                        period: Some(period),
1263                    },
1264                },
1265                MaData::Slice(slice) => SmaInput {
1266                    data: SmaData::Slice(slice),
1267                    params: SmaParams {
1268                        period: Some(period),
1269                    },
1270                },
1271            };
1272            let output = sma_with_kernel(&input, kernel)?;
1273            Ok(output.values)
1274        }
1275
1276        "alma" => {
1277            let input = match data {
1278                MaData::Candles { candles, source } => AlmaInput {
1279                    data: AlmaData::Candles { candles, source },
1280                    params: AlmaParams {
1281                        period: Some(period),
1282                        offset: None,
1283                        sigma: None,
1284                    },
1285                },
1286                MaData::Slice(slice) => AlmaInput {
1287                    data: AlmaData::Slice(slice),
1288                    params: AlmaParams {
1289                        period: Some(period),
1290                        offset: None,
1291                        sigma: None,
1292                    },
1293                },
1294            };
1295            let output = alma_with_kernel(&input, kernel)?;
1296            Ok(output.values)
1297        }
1298
1299        "cwma" => {
1300            let input = match data {
1301                MaData::Candles { candles, source } => CwmaInput {
1302                    data: CwmaData::Candles { candles, source },
1303                    params: CwmaParams {
1304                        period: Some(period),
1305                    },
1306                },
1307                MaData::Slice(slice) => CwmaInput {
1308                    data: CwmaData::Slice(slice),
1309                    params: CwmaParams {
1310                        period: Some(period),
1311                    },
1312                },
1313            };
1314            let output = cwma_with_kernel(&input, kernel)?;
1315            Ok(output.values)
1316        }
1317
1318        "cora_wave" => {
1319            let input = match data {
1320                MaData::Candles { candles, source } => CoraWaveInput {
1321                    data: CoraWaveData::Candles { candles, source },
1322                    params: CoraWaveParams {
1323                        period: Some(period),
1324                        r_multi: None,
1325                        smooth: None,
1326                    },
1327                },
1328                MaData::Slice(slice) => CoraWaveInput {
1329                    data: CoraWaveData::Slice(slice),
1330                    params: CoraWaveParams {
1331                        period: Some(period),
1332                        r_multi: None,
1333                        smooth: None,
1334                    },
1335                },
1336            };
1337            let output = cora_wave_with_kernel(&input, kernel)?;
1338            Ok(output.values)
1339        }
1340
1341        "dema" => {
1342            let input = match data {
1343                MaData::Candles { candles, source } => DemaInput {
1344                    data: DemaData::Candles { candles, source },
1345                    params: DemaParams {
1346                        period: Some(period),
1347                    },
1348                },
1349                MaData::Slice(slice) => DemaInput {
1350                    data: DemaData::Slice(slice),
1351                    params: DemaParams {
1352                        period: Some(period),
1353                    },
1354                },
1355            };
1356            let output = dema_with_kernel(&input, kernel)?;
1357            Ok(output.values)
1358        }
1359
1360        "edcf" => {
1361            let input = match data {
1362                MaData::Candles { candles, source } => EdcfInput {
1363                    data: EdcfData::Candles { candles, source },
1364                    params: EdcfParams {
1365                        period: Some(period),
1366                    },
1367                },
1368                MaData::Slice(slice) => EdcfInput {
1369                    data: EdcfData::Slice(slice),
1370                    params: EdcfParams {
1371                        period: Some(period),
1372                    },
1373                },
1374            };
1375            let output = edcf_with_kernel(&input, kernel)?;
1376            Ok(output.values)
1377        }
1378
1379        "ema" => {
1380            let input = match data {
1381                MaData::Candles { candles, source } => EmaInput {
1382                    data: EmaData::Candles { candles, source },
1383                    params: EmaParams {
1384                        period: Some(period),
1385                    },
1386                },
1387                MaData::Slice(slice) => EmaInput {
1388                    data: EmaData::Slice(slice),
1389                    params: EmaParams {
1390                        period: Some(period),
1391                    },
1392                },
1393            };
1394            let output = ema_with_kernel(&input, kernel)?;
1395            Ok(output.values)
1396        }
1397
1398        "epma" => {
1399            let input = match data {
1400                MaData::Candles { candles, source } => EpmaInput {
1401                    data: EpmaData::Candles { candles, source },
1402                    params: EpmaParams {
1403                        period: Some(period),
1404                        offset: None,
1405                    },
1406                },
1407                MaData::Slice(slice) => EpmaInput {
1408                    data: EpmaData::Slice(slice),
1409                    params: EpmaParams {
1410                        period: Some(period),
1411                        offset: None,
1412                    },
1413                },
1414            };
1415            let output = epma_with_kernel(&input, kernel)?;
1416            Ok(output.values)
1417        }
1418
1419        "fwma" => {
1420            let input = match data {
1421                MaData::Candles { candles, source } => FwmaInput {
1422                    data: FwmaData::Candles { candles, source },
1423                    params: FwmaParams {
1424                        period: Some(period),
1425                    },
1426                },
1427                MaData::Slice(slice) => FwmaInput {
1428                    data: FwmaData::Slice(slice),
1429                    params: FwmaParams {
1430                        period: Some(period),
1431                    },
1432                },
1433            };
1434            let output = fwma_with_kernel(&input, kernel)?;
1435            Ok(output.values)
1436        }
1437
1438        "gaussian" => {
1439            let input = match data {
1440                MaData::Candles { candles, source } => GaussianInput {
1441                    data: GaussianData::Candles { candles, source },
1442                    params: GaussianParams {
1443                        period: Some(period),
1444                        poles: None,
1445                    },
1446                },
1447                MaData::Slice(slice) => GaussianInput {
1448                    data: GaussianData::Slice(slice),
1449                    params: GaussianParams {
1450                        period: Some(period),
1451                        poles: None,
1452                    },
1453                },
1454            };
1455            let output = gaussian_with_kernel(&input, kernel)?;
1456            Ok(output.values)
1457        }
1458
1459        "highpass" => {
1460            let input = match data {
1461                MaData::Candles { candles, source } => HighPassInput {
1462                    data: HighPassData::Candles { candles, source },
1463                    params: HighPassParams {
1464                        period: Some(period),
1465                    },
1466                },
1467                MaData::Slice(slice) => HighPassInput {
1468                    data: HighPassData::Slice(slice),
1469                    params: HighPassParams {
1470                        period: Some(period),
1471                    },
1472                },
1473            };
1474            let output = highpass_with_kernel(&input, kernel)?;
1475            Ok(output.values)
1476        }
1477
1478        "highpass2" | "highpass_2_pole" => {
1479            let input = match data {
1480                MaData::Candles { candles, source } => HighPass2Input {
1481                    data: HighPass2Data::Candles { candles, source },
1482                    params: HighPass2Params {
1483                        period: Some(period),
1484                        k: Some(0.707),
1485                    },
1486                },
1487                MaData::Slice(slice) => HighPass2Input {
1488                    data: HighPass2Data::Slice(slice),
1489                    params: HighPass2Params {
1490                        period: Some(period),
1491                        k: Some(0.707),
1492                    },
1493                },
1494            };
1495            let output = highpass_2_pole_with_kernel(&input, kernel)?;
1496            Ok(output.values)
1497        }
1498
1499        "hma" => {
1500            let input = match data {
1501                MaData::Candles { candles, source } => HmaInput {
1502                    data: HmaData::Candles { candles, source },
1503                    params: HmaParams {
1504                        period: Some(period),
1505                    },
1506                },
1507                MaData::Slice(slice) => HmaInput {
1508                    data: HmaData::Slice(slice),
1509                    params: HmaParams {
1510                        period: Some(period),
1511                    },
1512                },
1513            };
1514            let output = hma_with_kernel(&input, kernel)?;
1515            Ok(output.values)
1516        }
1517
1518        "ehlers_itrend" => {
1519            let input = match data {
1520                MaData::Candles { candles, source } => EhlersITrendInput {
1521                    data: EhlersITrendData::Candles { candles, source },
1522                    params: EhlersITrendParams {
1523                        warmup_bars: Some(12),
1524                        max_dc_period: Some(50),
1525                    },
1526                },
1527                MaData::Slice(slice) => EhlersITrendInput {
1528                    data: EhlersITrendData::Slice(slice),
1529                    params: EhlersITrendParams {
1530                        warmup_bars: Some(12),
1531                        max_dc_period: Some(50),
1532                    },
1533                },
1534            };
1535            let output = ehlers_itrend_with_kernel(&input, kernel)?;
1536            Ok(output.values)
1537        }
1538
1539        "hwma" => {
1540            let input = match data {
1541                MaData::Candles { candles, source } => HwmaInput {
1542                    data: HwmaData::Candles { candles, source },
1543                    params: HwmaParams {
1544                        na: None,
1545                        nb: None,
1546                        nc: None,
1547                    },
1548                },
1549                MaData::Slice(slice) => HwmaInput {
1550                    data: HwmaData::Slice(slice),
1551                    params: HwmaParams {
1552                        na: None,
1553                        nb: None,
1554                        nc: None,
1555                    },
1556                },
1557            };
1558            let output = hwma_with_kernel(&input, kernel)?;
1559            Ok(output.values)
1560        }
1561
1562        "jma" => {
1563            let input = match data {
1564                MaData::Candles { candles, source } => JmaInput {
1565                    data: JmaData::Candles { candles, source },
1566                    params: JmaParams {
1567                        period: Some(period),
1568                        phase: None,
1569                        power: None,
1570                    },
1571                },
1572                MaData::Slice(slice) => JmaInput {
1573                    data: JmaData::Slice(slice),
1574                    params: JmaParams {
1575                        period: Some(period),
1576                        phase: None,
1577                        power: None,
1578                    },
1579                },
1580            };
1581            let output = jma_with_kernel(&input, kernel)?;
1582            Ok(output.values)
1583        }
1584
1585        "jsa" => {
1586            let input = match data {
1587                MaData::Candles { candles, source } => JsaInput {
1588                    data: JsaData::Candles { candles, source },
1589                    params: JsaParams {
1590                        period: Some(period),
1591                    },
1592                },
1593                MaData::Slice(slice) => JsaInput {
1594                    data: JsaData::Slice(slice),
1595                    params: JsaParams {
1596                        period: Some(period),
1597                    },
1598                },
1599            };
1600            let output = jsa_with_kernel(&input, kernel)?;
1601            Ok(output.values)
1602        }
1603
1604        "kama" => {
1605            let input = match data {
1606                MaData::Candles { candles, source } => KamaInput {
1607                    data: KamaData::Candles { candles, source },
1608                    params: KamaParams {
1609                        period: Some(period),
1610                    },
1611                },
1612                MaData::Slice(slice) => KamaInput {
1613                    data: KamaData::Slice(slice),
1614                    params: KamaParams {
1615                        period: Some(period),
1616                    },
1617                },
1618            };
1619            let output = kama_with_kernel(&input, kernel)?;
1620            Ok(output.values)
1621        }
1622
1623        "linreg" => {
1624            let input = match data {
1625                MaData::Candles { candles, source } => LinRegInput {
1626                    data: LinRegData::Candles { candles, source },
1627                    params: LinRegParams {
1628                        period: Some(period),
1629                    },
1630                },
1631                MaData::Slice(slice) => LinRegInput {
1632                    data: LinRegData::Slice(slice),
1633                    params: LinRegParams {
1634                        period: Some(period),
1635                    },
1636                },
1637            };
1638            let output = linreg_with_kernel(&input, kernel)?;
1639            Ok(output.values)
1640        }
1641
1642        "maaq" => {
1643            let input = match data {
1644                MaData::Candles { candles, source } => MaaqInput {
1645                    data: MaaqData::Candles { candles, source },
1646                    params: MaaqParams {
1647                        period: Some(period),
1648                        fast_period: None,
1649                        slow_period: None,
1650                    },
1651                },
1652                MaData::Slice(slice) => MaaqInput {
1653                    data: MaaqData::Slice(slice),
1654                    params: MaaqParams {
1655                        period: Some(period),
1656                        fast_period: None,
1657                        slow_period: None,
1658                    },
1659                },
1660            };
1661            let output = maaq_with_kernel(&input, kernel)?;
1662            Ok(output.values)
1663        }
1664
1665        "mama" => {
1666            let input = match data {
1667                MaData::Candles { candles, source } => {
1668                    MamaInput::from_candles(candles, source, MamaParams::default())
1669                }
1670                MaData::Slice(slice) => MamaInput::from_slice(slice, MamaParams::default()),
1671            };
1672            let output = mama_with_kernel(&input, kernel)?;
1673            Ok(output.mama_values)
1674        }
1675
1676        "mwdx" => {
1677            let input = match data {
1678                MaData::Candles { candles, source } => {
1679                    MwdxInput::from_candles(candles, source, MwdxParams { factor: Some(0.2) })
1680                }
1681                MaData::Slice(slice) => {
1682                    MwdxInput::from_slice(slice, MwdxParams { factor: Some(0.2) })
1683                }
1684            };
1685            let output = mwdx_with_kernel(&input, kernel)?;
1686            Ok(output.values)
1687        }
1688
1689        "nma" => {
1690            let input = match data {
1691                MaData::Candles { candles, source } => NmaInput {
1692                    data: NmaData::Candles { candles, source },
1693                    params: NmaParams {
1694                        period: Some(period),
1695                    },
1696                },
1697                MaData::Slice(slice) => NmaInput {
1698                    data: NmaData::Slice(slice),
1699                    params: NmaParams {
1700                        period: Some(period),
1701                    },
1702                },
1703            };
1704            let output = nma_with_kernel(&input, kernel)?;
1705            Ok(output.values)
1706        }
1707
1708        "pwma" => {
1709            let input = match data {
1710                MaData::Candles { candles, source } => PwmaInput {
1711                    data: PwmaData::Candles { candles, source },
1712                    params: PwmaParams {
1713                        period: Some(period),
1714                    },
1715                },
1716                MaData::Slice(slice) => PwmaInput {
1717                    data: PwmaData::Slice(slice),
1718                    params: PwmaParams {
1719                        period: Some(period),
1720                    },
1721                },
1722            };
1723            let output = pwma_with_kernel(&input, kernel)?;
1724            Ok(output.values)
1725        }
1726
1727        "reflex" => {
1728            let input = match data {
1729                MaData::Candles { candles, source } => ReflexInput {
1730                    data: ReflexData::Candles { candles, source },
1731                    params: ReflexParams {
1732                        period: Some(period),
1733                    },
1734                },
1735                MaData::Slice(slice) => ReflexInput {
1736                    data: ReflexData::Slice(slice),
1737                    params: ReflexParams {
1738                        period: Some(period),
1739                    },
1740                },
1741            };
1742            let output = reflex_with_kernel(&input, kernel)?;
1743            Ok(output.values)
1744        }
1745
1746        "sinwma" => {
1747            let input = match data {
1748                MaData::Candles { candles, source } => SinWmaInput {
1749                    data: SinWmaData::Candles { candles, source },
1750                    params: SinWmaParams {
1751                        period: Some(period),
1752                    },
1753                },
1754                MaData::Slice(slice) => SinWmaInput {
1755                    data: SinWmaData::Slice(slice),
1756                    params: SinWmaParams {
1757                        period: Some(period),
1758                    },
1759                },
1760            };
1761            let output = sinwma_with_kernel(&input, kernel)?;
1762            Ok(output.values)
1763        }
1764
1765        "sqwma" => {
1766            let input = match data {
1767                MaData::Candles { candles, source } => SqwmaInput {
1768                    data: SqwmaData::Candles { candles, source },
1769                    params: SqwmaParams {
1770                        period: Some(period),
1771                    },
1772                },
1773                MaData::Slice(slice) => SqwmaInput {
1774                    data: SqwmaData::Slice(slice),
1775                    params: SqwmaParams {
1776                        period: Some(period),
1777                    },
1778                },
1779            };
1780            let output = sqwma_with_kernel(&input, kernel)?;
1781            Ok(output.values)
1782        }
1783
1784        "srwma" => {
1785            let input = match data {
1786                MaData::Candles { candles, source } => SrwmaInput {
1787                    data: SrwmaData::Candles { candles, source },
1788                    params: SrwmaParams {
1789                        period: Some(period),
1790                    },
1791                },
1792                MaData::Slice(slice) => SrwmaInput {
1793                    data: SrwmaData::Slice(slice),
1794                    params: SrwmaParams {
1795                        period: Some(period),
1796                    },
1797                },
1798            };
1799            let output = srwma_with_kernel(&input, kernel)?;
1800            Ok(output.values)
1801        }
1802
1803        "smma" => {
1804            let input = match data {
1805                MaData::Candles { candles, source } => SmmaInput {
1806                    data: SmmaData::Candles { candles, source },
1807                    params: SmmaParams {
1808                        period: Some(period),
1809                    },
1810                },
1811                MaData::Slice(slice) => SmmaInput {
1812                    data: SmmaData::Slice(slice),
1813                    params: SmmaParams {
1814                        period: Some(period),
1815                    },
1816                },
1817            };
1818            let output = smma_with_kernel(&input, kernel)?;
1819            Ok(output.values)
1820        }
1821
1822        "supersmoother" => {
1823            let input = match data {
1824                MaData::Candles { candles, source } => SuperSmootherInput {
1825                    data: SuperSmootherData::Candles { candles, source },
1826                    params: SuperSmootherParams {
1827                        period: Some(period),
1828                    },
1829                },
1830                MaData::Slice(slice) => SuperSmootherInput {
1831                    data: SuperSmootherData::Slice(slice),
1832                    params: SuperSmootherParams {
1833                        period: Some(period),
1834                    },
1835                },
1836            };
1837            let output = supersmoother_with_kernel(&input, kernel)?;
1838            Ok(output.values)
1839        }
1840
1841        "supersmoother_3_pole" => {
1842            let input = match data {
1843                MaData::Candles { candles, source } => SuperSmoother3PoleInput {
1844                    data: SuperSmoother3PoleData::Candles { candles, source },
1845                    params: SuperSmoother3PoleParams {
1846                        period: Some(period),
1847                    },
1848                },
1849                MaData::Slice(slice) => SuperSmoother3PoleInput {
1850                    data: SuperSmoother3PoleData::Slice(slice),
1851                    params: SuperSmoother3PoleParams {
1852                        period: Some(period),
1853                    },
1854                },
1855            };
1856            let output = supersmoother_3_pole_with_kernel(&input, kernel)?;
1857            Ok(output.values)
1858        }
1859
1860        "swma" => {
1861            let input = match data {
1862                MaData::Candles { candles, source } => SwmaInput {
1863                    data: SwmaData::Candles { candles, source },
1864                    params: SwmaParams {
1865                        period: Some(period),
1866                    },
1867                },
1868                MaData::Slice(slice) => SwmaInput {
1869                    data: SwmaData::Slice(slice),
1870                    params: SwmaParams {
1871                        period: Some(period),
1872                    },
1873                },
1874            };
1875            let output = swma_with_kernel(&input, kernel)?;
1876            Ok(output.values)
1877        }
1878
1879        "tema" => {
1880            let input = match data {
1881                MaData::Candles { candles, source } => TemaInput {
1882                    data: TemaData::Candles { candles, source },
1883                    params: TemaParams {
1884                        period: Some(period),
1885                    },
1886                },
1887                MaData::Slice(slice) => TemaInput {
1888                    data: TemaData::Slice(slice),
1889                    params: TemaParams {
1890                        period: Some(period),
1891                    },
1892                },
1893            };
1894            let output = tema_with_kernel(&input, kernel)?;
1895            Ok(output.values)
1896        }
1897
1898        "tilson" => {
1899            let input = match data {
1900                MaData::Candles { candles, source } => TilsonInput {
1901                    data: TilsonData::Candles { candles, source },
1902                    params: TilsonParams {
1903                        period: Some(period),
1904                        volume_factor: None,
1905                    },
1906                },
1907                MaData::Slice(slice) => TilsonInput {
1908                    data: TilsonData::Slice(slice),
1909                    params: TilsonParams {
1910                        period: Some(period),
1911                        volume_factor: None,
1912                    },
1913                },
1914            };
1915            let output = tilson_with_kernel(&input, kernel)?;
1916            Ok(output.values)
1917        }
1918
1919        "trendflex" => {
1920            let input = match data {
1921                MaData::Candles { candles, source } => TrendFlexInput {
1922                    data: TrendFlexData::Candles { candles, source },
1923                    params: TrendFlexParams {
1924                        period: Some(period),
1925                    },
1926                },
1927                MaData::Slice(slice) => TrendFlexInput {
1928                    data: TrendFlexData::Slice(slice),
1929                    params: TrendFlexParams {
1930                        period: Some(period),
1931                    },
1932                },
1933            };
1934            let output = trendflex_with_kernel(&input, kernel)?;
1935            Ok(output.values)
1936        }
1937
1938        "trima" => {
1939            let input = match data {
1940                MaData::Candles { candles, source } => TrimaInput {
1941                    data: TrimaData::Candles { candles, source },
1942                    params: TrimaParams {
1943                        period: Some(period),
1944                    },
1945                },
1946                MaData::Slice(slice) => TrimaInput {
1947                    data: TrimaData::Slice(slice),
1948                    params: TrimaParams {
1949                        period: Some(period),
1950                    },
1951                },
1952            };
1953            let output = trima_with_kernel(&input, kernel)?;
1954            Ok(output.values)
1955        }
1956
1957        "wilders" => {
1958            let input = match data {
1959                MaData::Candles { candles, source } => WildersInput {
1960                    data: WildersData::Candles { candles, source },
1961                    params: WildersParams {
1962                        period: Some(period),
1963                    },
1964                },
1965                MaData::Slice(slice) => WildersInput {
1966                    data: WildersData::Slice(slice),
1967                    params: WildersParams {
1968                        period: Some(period),
1969                    },
1970                },
1971            };
1972            let output = wilders_with_kernel(&input, kernel)?;
1973            Ok(output.values)
1974        }
1975
1976        "wma" => {
1977            let input = match data {
1978                MaData::Candles { candles, source } => WmaInput {
1979                    data: WmaData::Candles { candles, source },
1980                    params: WmaParams {
1981                        period: Some(period),
1982                    },
1983                },
1984                MaData::Slice(slice) => WmaInput {
1985                    data: WmaData::Slice(slice),
1986                    params: WmaParams {
1987                        period: Some(period),
1988                    },
1989                },
1990            };
1991            let output = wma_with_kernel(&input, kernel)?;
1992            Ok(output.values)
1993        }
1994
1995        "vpwma" => {
1996            let input = match data {
1997                MaData::Candles { candles, source } => VpwmaInput {
1998                    data: VpwmaData::Candles { candles, source },
1999                    params: VpwmaParams {
2000                        period: Some(period),
2001                        power: Some(0.382),
2002                    },
2003                },
2004                MaData::Slice(_) => {
2005                    return Err(MaError::RequiresVolume { indicator: "vpwma" }.into());
2006                }
2007            };
2008            let output = vpwma_with_kernel(&input, kernel)?;
2009            Ok(output.values)
2010        }
2011
2012        "vwap" => {
2013            let input = match data {
2014                MaData::Candles { candles, .. } => VwapInput {
2015                    data: VwapData::Candles {
2016                        candles,
2017                        source: "hlc3",
2018                    },
2019                    params: VwapParams::default(),
2020                },
2021                MaData::Slice(_) => {
2022                    return Err(MaError::RequiresVolume { indicator: "vwap" }.into());
2023                }
2024            };
2025            let output = vwap_with_kernel(&input, kernel)?;
2026            Ok(output.values)
2027        }
2028
2029        "vwma" => {
2030            let input = match data {
2031                MaData::Candles { candles, source } => VwmaInput {
2032                    data: VwmaData::Candles { candles, source },
2033                    params: VwmaParams {
2034                        period: Some(period),
2035                    },
2036                },
2037                MaData::Slice(_) => {
2038                    return Err(MaError::RequiresVolume { indicator: "vwma" }.into());
2039                }
2040            };
2041            let output = vwma_with_kernel(&input, kernel)?;
2042            Ok(output.values)
2043        }
2044
2045        "zlema" => {
2046            let input = match data {
2047                MaData::Candles { candles, source } => ZlemaInput {
2048                    data: ZlemaData::Candles { candles, source },
2049                    params: ZlemaParams {
2050                        period: Some(period),
2051                    },
2052                },
2053                MaData::Slice(slice) => ZlemaInput {
2054                    data: ZlemaData::Slice(slice),
2055                    params: ZlemaParams {
2056                        period: Some(period),
2057                    },
2058                },
2059            };
2060            let output = zlema_with_kernel(&input, kernel)?;
2061            Ok(output.values)
2062        }
2063
2064        "buff_averages" => {
2065            return Err(MaError::RequiresVolume {
2066                indicator: "buff_averages",
2067            }
2068            .into());
2069        }
2070
2071        "dma" => {
2072            let input = match data {
2073                MaData::Candles { candles, source } => DmaInput {
2074                    data: DmaData::Candles { candles, source },
2075                    params: DmaParams {
2076                        ema_length: Some(period),
2077                        ..Default::default()
2078                    },
2079                },
2080                MaData::Slice(slice) => DmaInput {
2081                    data: DmaData::Slice(slice),
2082                    params: DmaParams {
2083                        ema_length: Some(period),
2084                        ..Default::default()
2085                    },
2086                },
2087            };
2088            let output = dma_with_kernel(&input, kernel)?;
2089            Ok(output.values)
2090        }
2091
2092        "ehlers_ecema" => {
2093            let input = match data {
2094                MaData::Candles { candles, source } => EhlersEcemaInput {
2095                    data: EhlersEcemaData::Candles { candles, source },
2096                    params: EhlersEcemaParams {
2097                        length: Some(period),
2098                        ..Default::default()
2099                    },
2100                },
2101                MaData::Slice(slice) => EhlersEcemaInput {
2102                    data: EhlersEcemaData::Slice(slice),
2103                    params: EhlersEcemaParams {
2104                        length: Some(period),
2105                        ..Default::default()
2106                    },
2107                },
2108            };
2109            let output = ehlers_ecema_with_kernel(&input, kernel)?;
2110            Ok(output.values)
2111        }
2112
2113        "ehlers_kama" => {
2114            let input = match data {
2115                MaData::Candles { candles, source } => EhlersKamaInput {
2116                    data: EhlersKamaData::Candles { candles, source },
2117                    params: EhlersKamaParams {
2118                        period: Some(period),
2119                        ..Default::default()
2120                    },
2121                },
2122                MaData::Slice(slice) => EhlersKamaInput {
2123                    data: EhlersKamaData::Slice(slice),
2124                    params: EhlersKamaParams {
2125                        period: Some(period),
2126                        ..Default::default()
2127                    },
2128                },
2129            };
2130            let output = ehlers_kama_with_kernel(&input, kernel)?;
2131            Ok(output.values)
2132        }
2133
2134        "ehlers_pma" => {
2135            return Err(MaError::DualOutputNotSupported {
2136                indicator: "ehlers_pma",
2137            }
2138            .into());
2139        }
2140
2141        "ehma" => {
2142            let input = match data {
2143                MaData::Candles { candles, source } => EhmaInput {
2144                    data: EhmaData::Candles { candles, source },
2145                    params: EhmaParams {
2146                        period: Some(period),
2147                        ..Default::default()
2148                    },
2149                },
2150                MaData::Slice(slice) => EhmaInput {
2151                    data: EhmaData::Slice(slice),
2152                    params: EhmaParams {
2153                        period: Some(period),
2154                        ..Default::default()
2155                    },
2156                },
2157            };
2158            let output = ehma_with_kernel(&input, kernel)?;
2159            Ok(output.values)
2160        }
2161
2162        "frama" => {
2163            let input = match data {
2164                MaData::Candles { candles, .. } => FramaInput::from_candles(
2165                    candles,
2166                    FramaParams {
2167                        window: Some(period),
2168                        ..Default::default()
2169                    },
2170                ),
2171                MaData::Slice(slice) => FramaInput::from_slices(
2172                    slice,
2173                    slice,
2174                    slice,
2175                    FramaParams {
2176                        window: Some(period),
2177                        ..Default::default()
2178                    },
2179                ),
2180            };
2181            let output = frama_with_kernel(&input, kernel)?;
2182            Ok(output.values)
2183        }
2184
2185        "nama" => {
2186            let input = match data {
2187                MaData::Candles { candles, source } => NamaInput {
2188                    data: NamaData::Candles { candles, source },
2189                    params: NamaParams {
2190                        period: Some(period),
2191                        ..Default::default()
2192                    },
2193                },
2194                MaData::Slice(slice) => NamaInput {
2195                    data: NamaData::Slice(slice),
2196                    params: NamaParams {
2197                        period: Some(period),
2198                        ..Default::default()
2199                    },
2200                },
2201            };
2202            let output = nama_with_kernel(&input, kernel)?;
2203            Ok(output.values)
2204        }
2205
2206        "sama" => {
2207            let input = match data {
2208                MaData::Candles { candles, source } => SamaInput {
2209                    data: SamaData::Candles { candles, source },
2210                    params: SamaParams {
2211                        length: Some(period),
2212                        ..Default::default()
2213                    },
2214                },
2215                MaData::Slice(slice) => SamaInput {
2216                    data: SamaData::Slice(slice),
2217                    params: SamaParams {
2218                        length: Some(period),
2219                        ..Default::default()
2220                    },
2221                },
2222            };
2223            let output = sama_with_kernel(&input, kernel)?;
2224            Ok(output.values)
2225        }
2226
2227        "tradjema" => {
2228            return Err(MaError::RequiresHighLow {
2229                indicator: "tradjema",
2230            }
2231            .into());
2232        }
2233
2234        "uma" => {
2235            return Err(MaError::RequiresVolume { indicator: "uma" }.into());
2236        }
2237
2238        "volatility_adjusted_ma" | "vama" => {
2239            let input = match data {
2240                MaData::Candles { candles, source } => VamaInput {
2241                    data: VamaData::Candles { candles, source },
2242                    params: VamaParams {
2243                        base_period: Some(period),
2244                        ..Default::default()
2245                    },
2246                },
2247                MaData::Slice(slice) => VamaInput {
2248                    data: VamaData::Slice(slice),
2249                    params: VamaParams {
2250                        base_period: Some(period),
2251                        ..Default::default()
2252                    },
2253                },
2254            };
2255            let output = vama_with_kernel(&input, kernel)?;
2256            Ok(output.values)
2257        }
2258
2259        "volume_adjusted_ma" => {
2260            return Err(MaError::RequiresVolume {
2261                indicator: "volume_adjusted_ma",
2262            }
2263            .into());
2264        }
2265
2266        _ => {
2267            return Err(MaError::UnknownType {
2268                ma_type: ma_type.to_string(),
2269            }
2270            .into());
2271        }
2272    }
2273}
2274
2275#[cfg(feature = "python")]
2276#[pyfunction(name = "ma")]
2277#[pyo3(signature = (data, ma_type, period, kernel=None))]
2278pub fn ma_py<'py>(
2279    py: Python<'py>,
2280    data: PyReadonlyArray1<'py, f64>,
2281    ma_type: &str,
2282    period: usize,
2283    kernel: Option<&str>,
2284) -> PyResult<Bound<'py, PyArray1<f64>>> {
2285    use numpy::{IntoPyArray, PyArrayMethods};
2286
2287    let slice_in = data.as_slice()?;
2288    let kern = validate_kernel(kernel, false)?;
2289
2290    let result_vec: Vec<f64> = py
2291        .allow_threads(|| -> Result<Vec<f64>, Box<dyn Error + Send + Sync>> {
2292            match ma_with_kernel(ma_type, MaData::Slice(slice_in), period, kern) {
2293                Ok(result) => Ok(result),
2294                Err(e) => {
2295                    if e.to_string().contains("Unknown moving average type") {
2296                        ma_with_kernel("sma", MaData::Slice(slice_in), period, kern).map_err(
2297                            |e| -> Box<dyn Error + Send + Sync> {
2298                                Box::new(std::io::Error::new(
2299                                    std::io::ErrorKind::Other,
2300                                    e.to_string(),
2301                                ))
2302                            },
2303                        )
2304                    } else {
2305                        Err(Box::new(std::io::Error::new(
2306                            std::io::ErrorKind::Other,
2307                            e.to_string(),
2308                        )) as Box<dyn Error + Send + Sync>)
2309                    }
2310                }
2311            }
2312        })
2313        .map_err(|e| PyValueError::new_err(e.to_string()))?;
2314
2315    Ok(result_vec.into_pyarray(py))
2316}
2317
2318#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2319#[wasm_bindgen(js_name = "ma")]
2320pub fn ma_js(data: &[f64], ma_type: &str, period: usize) -> Result<Vec<f64>, JsValue> {
2321    match ma(ma_type, MaData::Slice(data), period) {
2322        Ok(result) => Ok(result),
2323        Err(e) => {
2324            if e.to_string().contains("Unknown moving average type") {
2325                ma("sma", MaData::Slice(data), period)
2326                    .map_err(|e| JsValue::from_str(&e.to_string()))
2327            } else {
2328                Err(JsValue::from_str(&e.to_string()))
2329            }
2330        }
2331    }
2332}
2333
2334#[cfg(test)]
2335mod tests {
2336    use super::*;
2337    use crate::utilities::data_loader::read_candles_from_csv;
2338
2339    #[test]
2340    fn test_all_ma_variants() {
2341        let ma_types = vec![
2342            "sma",
2343            "ema",
2344            "dema",
2345            "tema",
2346            "smma",
2347            "zlema",
2348            "alma",
2349            "cwma",
2350            "edcf",
2351            "fwma",
2352            "gaussian",
2353            "highpass",
2354            "highpass2",
2355            "hma",
2356            "hwma",
2357            "jma",
2358            "jsa",
2359            "frama",
2360            "linreg",
2361            "maaq",
2362            "mwdx",
2363            "nma",
2364            "pwma",
2365            "reflex",
2366            "sinwma",
2367            "sqwma",
2368            "srwma",
2369            "supersmoother",
2370            "supersmoother_3_pole",
2371            "swma",
2372            "tilson",
2373            "trendflex",
2374            "trima",
2375            "wilders",
2376            "wma",
2377            "vpwma",
2378            "vwap",
2379            "vwma",
2380            "mama",
2381        ];
2382
2383        let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2384        let candles = read_candles_from_csv(file_path).expect("Failed to load test candles");
2385
2386        for &ma_type in &ma_types {
2387            let period = 80;
2388            let candles_result = ma(
2389                ma_type,
2390                MaData::Candles {
2391                    candles: &candles,
2392                    source: "close",
2393                },
2394                period,
2395            )
2396            .unwrap_or_else(|err| panic!("`ma({})` failed with error: {}", ma_type, err));
2397
2398            assert_eq!(
2399                candles_result.len(),
2400                candles.close.len(),
2401                "MA output length for '{}' mismatch",
2402                ma_type
2403            );
2404
2405            let skip_amount = if ma_type == "mama" { 10 } else { 960 };
2406            for (i, &value) in candles_result.iter().enumerate().skip(skip_amount) {
2407                assert!(
2408                    !value.is_nan(),
2409                    "MA result for '{}' at index {} is NaN",
2410                    ma_type,
2411                    i
2412                );
2413            }
2414
2415            if ma_type != "mama" {
2416                let slice_result = ma(ma_type, MaData::Slice(&candles_result), 60)
2417                    .unwrap_or_else(|err| panic!("`ma({})` failed with error: {}", ma_type, err));
2418
2419                assert_eq!(
2420                    slice_result.len(),
2421                    candles.close.len(),
2422                    "MA output length for '{}' mismatch",
2423                    ma_type
2424                );
2425
2426                for (i, &value) in slice_result.iter().enumerate().skip(960) {
2427                    assert!(
2428                        !value.is_nan(),
2429                        "MA result for '{}' at index {} is NaN",
2430                        ma_type,
2431                        i
2432                    );
2433                }
2434            }
2435        }
2436    }
2437}