1mod accumulation_distribution;
44mod adx;
45mod alma;
46mod aroon;
47mod atr;
48mod awesome_oscillator;
49mod balance_of_power;
50mod bollinger;
51mod bull_bear_power;
52mod cci;
53mod chaikin_oscillator;
54mod choppiness_index;
55mod cmf;
56mod cmo;
57mod coppock_curve;
58mod dema;
59mod donchian_channels;
60mod elder_ray;
61mod ema;
62mod hma;
63mod ichimoku;
64mod keltner_channels;
65mod macd;
66mod mcginley_dynamic;
67mod mfi;
68mod momentum;
69mod obv;
70mod parabolic_sar;
71mod patterns;
72mod roc;
73mod rsi;
74mod sma;
75mod stochastic;
76mod stochastic_rsi;
77mod supertrend;
78mod tema;
79mod true_range;
80mod vwap;
81mod vwma;
82mod williams_r;
83mod wma;
84
85pub mod summary;
87
88pub use accumulation_distribution::accumulation_distribution;
90pub use adx::adx;
91pub use alma::alma;
92pub use aroon::{AroonResult, aroon};
93pub use atr::atr;
94pub use awesome_oscillator::awesome_oscillator;
95pub use balance_of_power::balance_of_power;
96pub use bollinger::{BollingerBands, bollinger_bands};
97pub use bull_bear_power::{BullBearPowerResult, bull_bear_power};
98pub use cci::cci;
99pub use chaikin_oscillator::chaikin_oscillator;
100pub use choppiness_index::choppiness_index;
101pub use cmf::cmf;
102pub use cmo::cmo;
103pub use coppock_curve::coppock_curve;
104pub use dema::dema;
105pub use donchian_channels::{DonchianChannelsResult, donchian_channels};
106pub use elder_ray::{ElderRayResult, elder_ray};
107pub use ema::ema;
108pub use hma::hma;
109pub use ichimoku::{IchimokuResult, ichimoku};
110pub use keltner_channels::{KeltnerChannelsResult, keltner_channels};
111pub use macd::{MacdResult, macd};
112pub use mcginley_dynamic::mcginley_dynamic;
113pub use mfi::mfi;
114pub use momentum::momentum;
115pub use obv::obv;
116pub use parabolic_sar::parabolic_sar;
117pub use patterns::{CandlePattern, PatternSentiment, patterns};
118pub use roc::roc;
119pub use rsi::rsi;
120pub use sma::sma;
121pub use stochastic::{StochasticResult, stochastic};
122pub use stochastic_rsi::stochastic_rsi;
123pub use supertrend::{SuperTrendResult, supertrend};
124pub use tema::tema;
125pub use true_range::true_range;
126pub use vwap::vwap;
127pub use vwma::vwma;
128pub use williams_r::williams_r;
129pub use wma::wma;
130
131pub use summary::{
133 AroonData, BollingerBandsData, BullBearPowerData, DonchianChannelsData, ElderRayData,
134 IchimokuData, IndicatorsSummary, KeltnerChannelsData, MacdData, StochasticData, SuperTrendData,
135};
136
137pub use Indicator as IndicatorType;
139
140#[derive(Debug, thiserror::Error)]
142pub enum IndicatorError {
143 #[error("Insufficient data: need at least {need} data points, got {got}")]
145 InsufficientData {
146 need: usize,
148 got: usize,
150 },
151
152 #[error("Invalid period: {0}")]
154 InvalidPeriod(String),
155}
156
157pub type Result<T> = std::result::Result<T, IndicatorError>;
159
160#[derive(Debug, Clone, PartialEq)]
166#[non_exhaustive]
167pub enum IndicatorResult {
168 Series(Vec<Option<f64>>),
170 Macd(MacdResult),
172 Bollinger(BollingerBands),
174 Stochastic(StochasticResult),
176 Aroon(AroonResult),
178 SuperTrend(SuperTrendResult),
180 Ichimoku(IchimokuResult),
182 BullBearPower(BullBearPowerResult),
184 ElderRay(ElderRayResult),
186 Keltner(KeltnerChannelsResult),
188 Donchian(DonchianChannelsResult),
190}
191
192#[derive(Debug, Clone, Copy, PartialEq)]
197#[non_exhaustive]
198pub enum Indicator {
199 Sma(usize),
201 Ema(usize),
203 Rsi(usize),
205 Macd {
207 fast: usize,
209 slow: usize,
211 signal: usize,
213 },
214 Bollinger {
216 period: usize,
218 std_dev: f64,
220 },
221 Atr(usize),
223 Obv,
225 Vwap,
227 Wma(usize),
229 Dema(usize),
231 Tema(usize),
233 Hma(usize),
235 Vwma(usize),
237 Alma {
239 period: usize,
241 offset: f64,
243 sigma: f64,
245 },
246 McginleyDynamic(usize),
248 Stochastic {
250 k_period: usize,
252 k_slow: usize,
254 d_period: usize,
256 },
257 StochasticRsi {
259 rsi_period: usize,
261 stoch_period: usize,
263 k_period: usize,
265 d_period: usize,
267 },
268 Cci(usize),
270 WilliamsR(usize),
272 Roc(usize),
274 Momentum(usize),
276 Cmo(usize),
278 AwesomeOscillator {
280 fast: usize,
282 slow: usize,
284 },
285 CoppockCurve {
287 wma_period: usize,
289 long_roc: usize,
291 short_roc: usize,
293 },
294 Adx(usize),
296 Aroon(usize),
298 Supertrend {
300 period: usize,
302 multiplier: f64,
304 },
305 Ichimoku {
307 conversion: usize,
309 base: usize,
311 lagging: usize,
313 displacement: usize,
315 },
316 ParabolicSar {
318 step: f64,
320 max: f64,
322 },
323 BullBearPower(usize),
325 ElderRay(usize),
327 KeltnerChannels {
329 period: usize,
331 multiplier: f64,
333 atr_period: usize,
335 },
336 DonchianChannels(usize),
338 TrueRange,
340 ChoppinessIndex(usize),
342 Mfi(usize),
344 Cmf(usize),
346 ChaikinOscillator,
348 AccumulationDistribution,
350 BalanceOfPower(Option<usize>),
352}
353
354impl Indicator {
355 pub fn with_defaults(self) -> Self {
366 match self {
367 Indicator::Sma(_) => Indicator::Sma(20),
368 Indicator::Ema(_) => Indicator::Ema(12),
369 Indicator::Rsi(_) => Indicator::Rsi(14),
370 Indicator::Macd { .. } => Indicator::Macd {
371 fast: 12,
372 slow: 26,
373 signal: 9,
374 },
375 Indicator::Bollinger { .. } => Indicator::Bollinger {
376 period: 20,
377 std_dev: 2.0,
378 },
379 Indicator::Atr(_) => Indicator::Atr(14),
380 ind => ind,
381 }
382 }
383
384 pub fn name(&self) -> &'static str {
386 match self {
387 Indicator::Sma(_) => "Simple Moving Average",
388 Indicator::Ema(_) => "Exponential Moving Average",
389 Indicator::Rsi(_) => "Relative Strength Index",
390 Indicator::Macd { .. } => "MACD",
391 Indicator::Bollinger { .. } => "Bollinger Bands",
392 Indicator::Atr(_) => "Average True Range",
393 Indicator::Obv => "On-Balance Volume",
394 Indicator::Vwap => "VWAP",
395 Indicator::Wma(_) => "Weighted Moving Average",
396 Indicator::Dema(_) => "Double Exponential Moving Average",
397 Indicator::Tema(_) => "Triple Exponential Moving Average",
398 Indicator::Hma(_) => "Hull Moving Average",
399 Indicator::Vwma(_) => "Volume Weighted Moving Average",
400 Indicator::Alma { .. } => "Arnaud Legoux Moving Average",
401 Indicator::McginleyDynamic(_) => "McGinley Dynamic",
402 Indicator::Stochastic { .. } => "Stochastic Oscillator",
403 Indicator::StochasticRsi { .. } => "Stochastic RSI",
404 Indicator::Cci(_) => "Commodity Channel Index",
405 Indicator::WilliamsR(_) => "Williams %R",
406 Indicator::Roc(_) => "Rate of Change",
407 Indicator::Momentum(_) => "Momentum",
408 Indicator::Cmo(_) => "Chande Momentum Oscillator",
409 Indicator::AwesomeOscillator { .. } => "Awesome Oscillator",
410 Indicator::CoppockCurve { .. } => "Coppock Curve",
411 Indicator::Adx(_) => "Average Directional Index",
412 Indicator::Aroon(_) => "Aroon",
413 Indicator::Supertrend { .. } => "SuperTrend",
414 Indicator::Ichimoku { .. } => "Ichimoku Cloud",
415 Indicator::ParabolicSar { .. } => "Parabolic SAR",
416 Indicator::BullBearPower(_) => "Bull/Bear Power",
417 Indicator::ElderRay(_) => "Elder Ray Index",
418 Indicator::KeltnerChannels { .. } => "Keltner Channels",
419 Indicator::DonchianChannels(_) => "Donchian Channels",
420 Indicator::TrueRange => "True Range",
421 Indicator::ChoppinessIndex(_) => "Choppiness Index",
422 Indicator::Mfi(_) => "Money Flow Index",
423 Indicator::Cmf(_) => "Chaikin Money Flow",
424 Indicator::ChaikinOscillator => "Chaikin Oscillator",
425 Indicator::AccumulationDistribution => "Accumulation/Distribution",
426 Indicator::BalanceOfPower(_) => "Balance of Power",
427 }
428 }
429
430 pub fn warmup_bars(&self) -> usize {
446 match self {
447 Self::Sma(p)
448 | Self::Ema(p)
449 | Self::Rsi(p)
450 | Self::Atr(p)
451 | Self::Wma(p)
452 | Self::Dema(p)
453 | Self::Tema(p)
454 | Self::Hma(p)
455 | Self::Vwma(p)
456 | Self::McginleyDynamic(p)
457 | Self::Cci(p)
458 | Self::WilliamsR(p)
459 | Self::Roc(p)
460 | Self::Momentum(p)
461 | Self::Cmo(p)
462 | Self::Adx(p)
463 | Self::Aroon(p)
464 | Self::DonchianChannels(p)
465 | Self::ChoppinessIndex(p)
466 | Self::Mfi(p)
467 | Self::Cmf(p)
468 | Self::BullBearPower(p)
469 | Self::ElderRay(p) => *p,
470 Self::Macd { fast, slow, signal } => *slow.max(fast) + signal,
471 Self::Bollinger { period, .. } => *period,
472 Self::Alma { period, .. } => *period,
473 Self::Stochastic {
474 k_period,
475 k_slow,
476 d_period,
477 } => k_period + k_slow + d_period,
478 Self::StochasticRsi {
479 rsi_period,
480 stoch_period,
481 k_period,
482 d_period,
483 } => rsi_period + stoch_period + k_period.max(d_period),
484 Self::AwesomeOscillator { slow, .. } => *slow,
485 Self::CoppockCurve {
486 long_roc,
487 wma_period,
488 ..
489 } => long_roc + wma_period,
490 Self::Ichimoku {
491 base, displacement, ..
492 } => base + displacement,
493 Self::Supertrend { period, .. } => *period,
494 Self::ParabolicSar { .. } => 2,
495 Self::KeltnerChannels {
496 period, atr_period, ..
497 } => *period.max(atr_period),
498 Self::BalanceOfPower(Some(p)) => *p,
499 Self::Obv
501 | Self::Vwap
502 | Self::TrueRange
503 | Self::ChaikinOscillator
504 | Self::AccumulationDistribution
505 | Self::BalanceOfPower(None) => 1,
506 }
507 }
508}
509
510pub fn last_value(values: &[Option<f64>]) -> Option<f64> {
523 values.iter().rev().find_map(|&v| v)
524}
525
526pub(crate) fn compute_indicator(
532 indicator: Indicator,
533 chart: &crate::models::chart::Chart,
534) -> Result<IndicatorResult> {
535 let o = chart.open_prices();
536 let h = chart.high_prices();
537 let l = chart.low_prices();
538 let c = chart.close_prices();
539 let v = chart.volumes();
540 Ok(match indicator {
541 Indicator::Sma(p) => IndicatorResult::Series(chart.sma(p)),
542 Indicator::Ema(p) => IndicatorResult::Series(chart.ema(p)),
543 Indicator::Rsi(p) => IndicatorResult::Series(chart.rsi(p)?),
544 Indicator::Macd { fast, slow, signal } => {
545 IndicatorResult::Macd(chart.macd(fast, slow, signal)?)
546 }
547 Indicator::Bollinger { period, std_dev } => {
548 IndicatorResult::Bollinger(chart.bollinger_bands(period, std_dev)?)
549 }
550 Indicator::Atr(p) => IndicatorResult::Series(chart.atr(p)?),
551 Indicator::Vwap => IndicatorResult::Series(crate::indicators::vwap(&h, &l, &c, &v)?),
552 Indicator::Wma(p) => IndicatorResult::Series(crate::indicators::wma(&c, p)?),
553 Indicator::Obv => IndicatorResult::Series(crate::indicators::obv(&c, &v)?),
554 Indicator::Dema(p) => IndicatorResult::Series(crate::indicators::dema(&c, p)?),
555 Indicator::Tema(p) => IndicatorResult::Series(crate::indicators::tema(&c, p)?),
556 Indicator::Hma(p) => IndicatorResult::Series(crate::indicators::hma(&c, p)?),
557 Indicator::Vwma(p) => IndicatorResult::Series(crate::indicators::vwma(&c, &v, p)?),
558 Indicator::Alma {
559 period,
560 offset,
561 sigma,
562 } => IndicatorResult::Series(crate::indicators::alma(&c, period, offset, sigma)?),
563 Indicator::McginleyDynamic(p) => {
564 IndicatorResult::Series(crate::indicators::mcginley_dynamic(&c, p)?)
565 }
566 Indicator::Stochastic {
567 k_period,
568 k_slow,
569 d_period,
570 } => IndicatorResult::Stochastic(crate::indicators::stochastic(
571 &h, &l, &c, k_period, k_slow, d_period,
572 )?),
573 Indicator::StochasticRsi {
574 rsi_period,
575 stoch_period,
576 k_period,
577 d_period,
578 } => IndicatorResult::Stochastic(crate::indicators::stochastic_rsi(
579 &c,
580 rsi_period,
581 stoch_period,
582 k_period,
583 d_period,
584 )?),
585 Indicator::Cci(p) => IndicatorResult::Series(crate::indicators::cci(&h, &l, &c, p)?),
586 Indicator::WilliamsR(p) => {
587 IndicatorResult::Series(crate::indicators::williams_r(&h, &l, &c, p)?)
588 }
589 Indicator::Roc(p) => IndicatorResult::Series(crate::indicators::roc(&c, p)?),
590 Indicator::Momentum(p) => IndicatorResult::Series(crate::indicators::momentum(&c, p)?),
591 Indicator::Cmo(p) => IndicatorResult::Series(crate::indicators::cmo(&c, p)?),
592 Indicator::AwesomeOscillator { fast, slow } => {
593 IndicatorResult::Series(crate::indicators::awesome_oscillator(&h, &l, fast, slow)?)
594 }
595 Indicator::CoppockCurve {
596 long_roc,
597 short_roc,
598 wma_period,
599 } => IndicatorResult::Series(crate::indicators::coppock_curve(
600 &c, long_roc, short_roc, wma_period,
601 )?),
602 Indicator::Adx(p) => IndicatorResult::Series(crate::indicators::adx(&h, &l, &c, p)?),
603 Indicator::Aroon(p) => IndicatorResult::Aroon(crate::indicators::aroon(&h, &l, p)?),
604 Indicator::Supertrend { period, multiplier } => IndicatorResult::SuperTrend(
605 crate::indicators::supertrend(&h, &l, &c, period, multiplier)?,
606 ),
607 Indicator::Ichimoku {
608 conversion,
609 base,
610 lagging,
611 displacement,
612 } => IndicatorResult::Ichimoku(crate::indicators::ichimoku(
613 &h,
614 &l,
615 &c,
616 conversion,
617 base,
618 lagging,
619 displacement,
620 )?),
621 Indicator::ParabolicSar { step, max } => {
622 IndicatorResult::Series(crate::indicators::parabolic_sar(&h, &l, &c, step, max)?)
623 }
624 Indicator::BullBearPower(p) => {
625 IndicatorResult::BullBearPower(crate::indicators::bull_bear_power(&h, &l, &c, p)?)
626 }
627 Indicator::ElderRay(p) => {
628 IndicatorResult::ElderRay(crate::indicators::elder_ray(&h, &l, &c, p)?)
629 }
630 Indicator::KeltnerChannels {
631 period,
632 multiplier,
633 atr_period,
634 } => IndicatorResult::Keltner(crate::indicators::keltner_channels(
635 &h, &l, &c, period, atr_period, multiplier,
636 )?),
637 Indicator::DonchianChannels(p) => {
638 IndicatorResult::Donchian(crate::indicators::donchian_channels(&h, &l, p)?)
639 }
640 Indicator::TrueRange => IndicatorResult::Series(crate::indicators::true_range(&h, &l, &c)?),
641 Indicator::ChoppinessIndex(p) => {
642 IndicatorResult::Series(crate::indicators::choppiness_index(&h, &l, &c, p)?)
643 }
644 Indicator::Mfi(p) => IndicatorResult::Series(crate::indicators::mfi(&h, &l, &c, &v, p)?),
645 Indicator::Cmf(p) => IndicatorResult::Series(crate::indicators::cmf(&h, &l, &c, &v, p)?),
646 Indicator::ChaikinOscillator => {
647 IndicatorResult::Series(crate::indicators::chaikin_oscillator(&h, &l, &c, &v)?)
648 }
649 Indicator::AccumulationDistribution => IndicatorResult::Series(
650 crate::indicators::accumulation_distribution(&h, &l, &c, &v)?,
651 ),
652 Indicator::BalanceOfPower(p) => {
653 IndicatorResult::Series(crate::indicators::balance_of_power(&o, &h, &l, &c, p)?)
654 }
655 })
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn test_last_value() {
664 assert_eq!(last_value(&[None, None, Some(1.0), Some(2.0)]), Some(2.0));
665 assert_eq!(last_value(&[None, None, Some(1.0), None]), Some(1.0));
666 assert_eq!(last_value(&[None, None, None]), None);
667 assert_eq!(last_value(&[]), None);
668 }
669}