1use quant_primitives::Candle;
4use rust_decimal::Decimal;
5
6use crate::error::IndicatorError;
7use crate::indicator::Indicator;
8use crate::series::Series;
9
10#[derive(Debug, Clone)]
37pub struct Wma {
38 period: usize,
39 weight_sum: Decimal,
40 name: String,
41}
42
43impl Wma {
44 pub fn new(period: usize) -> Result<Self, IndicatorError> {
50 if period == 0 {
51 return Err(IndicatorError::InvalidParameter {
52 message: "WMA period must be > 0".to_string(),
53 });
54 }
55 let weight_sum =
57 Decimal::from(period as u64) * Decimal::from(period as u64 + 1) / Decimal::TWO;
58 Ok(Self {
59 period,
60 weight_sum,
61 name: format!("WMA({})", period),
62 })
63 }
64}
65
66impl Indicator for Wma {
67 fn name(&self) -> &str {
68 &self.name
69 }
70
71 fn warmup_period(&self) -> usize {
72 self.period
73 }
74
75 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
76 if candles.len() < self.period {
77 return Err(IndicatorError::InsufficientData {
78 required: self.period,
79 actual: candles.len(),
80 });
81 }
82
83 let mut values = Vec::with_capacity(candles.len() - self.period + 1);
84
85 for window in candles.windows(self.period) {
86 let weighted_sum: Decimal = window
87 .iter()
88 .enumerate()
89 .map(|(i, c)| c.close() * Decimal::from((i + 1) as u64))
90 .sum();
91
92 let wma = weighted_sum / self.weight_sum;
93 let ts = window[self.period - 1].timestamp();
94 values.push((ts, wma));
95 }
96
97 Ok(Series::new(values))
98 }
99}
100
101#[cfg(test)]
102#[path = "wma_tests.rs"]
103mod tests;