use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::ohlcv_field::OhlcvField;
#[derive(Debug, Clone)]
pub struct Rma {
period: usize,
value: f64,
count: usize,
source: OhlcvField,
}
impl Rma {
pub fn period(&self) -> usize {
self.period
}
pub fn new(period: usize) -> Self {
Self::with_source(period, OhlcvField::Close)
}
pub fn with_source(period: usize, source: OhlcvField) -> Self {
Self {
period,
value: 0.0,
count: 0,
source,
}
}
pub fn update_bar(&mut self, open: f64, high: f64, low: f64, close: f64, volume: f64) -> f64 {
let value = self.source.extract(open, high, low, close, volume);
if self.count == 0 {
self.value = value;
} else {
self.value = (self.value * (self.period as f64 - 1.0) + value) / self.period as f64;
}
self.count += 1;
self.value
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.value)
}
pub fn is_ready(&self) -> bool {
self.count >= self.period
}
pub fn reset(&mut self) {
self.value = 0.0;
self.count = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rma_basic_calculation() {
let mut rma = Rma::new(3);
let v1 = rma.update_bar(0.0, 0.0, 0.0, 10.0, 0.0);
assert!((v1 - 10.0).abs() < 1e-10);
let v2 = rma.update_bar(0.0, 0.0, 0.0, 20.0, 0.0);
assert!((v2 - 13.333333).abs() < 0.001);
let v3 = rma.update_bar(0.0, 0.0, 0.0, 30.0, 0.0);
assert!(rma.is_ready());
assert!((v3 - 18.888888).abs() < 0.001);
}
#[test]
fn test_rma_vs_ema_smoothing() {
let _rma = Rma::new(14);
let alpha = 1.0 / 14.0;
assert!((alpha - 0.0714_f64).abs() < 0.001);
}
#[test]
fn test_rma_reset() {
let mut rma = Rma::new(3);
rma.update_bar(0.0, 0.0, 0.0, 10.0, 0.0);
rma.update_bar(0.0, 0.0, 0.0, 20.0, 0.0);
rma.update_bar(0.0, 0.0, 0.0, 30.0, 0.0);
assert!(rma.is_ready());
rma.reset();
assert!(!rma.is_ready());
}
#[test]
fn test_rma_with_source_hl2() {
let mut rma = Rma::with_source(3, OhlcvField::HL2);
let v1 = rma.update_bar(0.0, 110.0, 90.0, 105.0, 0.0);
assert!((v1 - 100.0).abs() < 1e-10);
let v2 = rma.update_bar(0.0, 120.0, 80.0, 110.0, 0.0);
assert!((v2 - 100.0).abs() < 1e-10);
let v3 = rma.update_bar(0.0, 130.0, 70.0, 115.0, 0.0);
assert!(rma.is_ready());
assert!((v3 - 100.0).abs() < 1e-10);
}
#[test]
fn test_rma_with_source_high() {
let mut rma = Rma::with_source(2, OhlcvField::High);
let v1 = rma.update_bar(100.0, 110.0, 90.0, 105.0, 0.0);
assert!((v1 - 110.0).abs() < 1e-10);
let v2 = rma.update_bar(105.0, 120.0, 95.0, 110.0, 0.0);
assert!((v2 - 115.0).abs() < 1e-10);
}
}