Skip to main content

quant_indicators/
momentum.rs

1//! Momentum indicator.
2
3use quant_primitives::Candle;
4
5use crate::error::IndicatorError;
6use crate::indicator::Indicator;
7use crate::series::Series;
8
9/// Momentum indicator.
10///
11/// Measures the rate of change in price over a specified period.
12/// Simple but effective for identifying trend strength.
13///
14/// # Formula
15///
16/// Momentum = Close - Close[n periods ago]
17///
18/// # Example
19///
20/// ```
21/// use quant_indicators::{Indicator, Momentum};
22/// use quant_primitives::Candle;
23/// use chrono::Utc;
24/// use rust_decimal_macros::dec;
25///
26/// let ts = Utc::now();
27/// let candles: Vec<Candle> = (0..15).map(|i| {
28///     Candle::new(dec!(100), dec!(110), dec!(90), dec!(100) + rust_decimal::Decimal::from(i), dec!(1000), ts).unwrap()
29/// }).collect();
30/// let mom = Momentum::new(10).unwrap();
31/// let series = mom.compute(&candles).unwrap();
32/// ```
33#[derive(Debug, Clone)]
34pub struct Momentum {
35    period: usize,
36    name: String,
37}
38
39impl Momentum {
40    /// Create a new Momentum indicator with the specified period.
41    ///
42    /// # Errors
43    ///
44    /// Returns `InvalidParameter` if period is 0.
45    pub fn new(period: usize) -> Result<Self, IndicatorError> {
46        if period == 0 {
47            return Err(IndicatorError::InvalidParameter {
48                message: "Momentum period must be > 0".to_string(),
49            });
50        }
51        Ok(Self {
52            period,
53            name: format!("Momentum({})", period),
54        })
55    }
56}
57
58impl Indicator for Momentum {
59    fn name(&self) -> &str {
60        &self.name
61    }
62
63    fn warmup_period(&self) -> usize {
64        self.period + 1
65    }
66
67    fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
68        let required = self.period + 1;
69        if candles.len() < required {
70            return Err(IndicatorError::InsufficientData {
71                required,
72                actual: candles.len(),
73            });
74        }
75
76        let mut values = Vec::with_capacity(candles.len() - self.period);
77
78        for i in self.period..candles.len() {
79            let current = candles[i].close();
80            let past = candles[i - self.period].close();
81            let momentum = current - past;
82            values.push((candles[i].timestamp(), momentum));
83        }
84
85        Ok(Series::new(values))
86    }
87}
88
89#[cfg(test)]
90#[path = "momentum_tests.rs"]
91mod tests;