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::frama::{frama, FramaInput, FramaParams};
33use crate::indicators::moving_averages::ehma::{ehma, EhmaData, EhmaInput, EhmaParams};
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}