use std::collections::VecDeque;
use crate::bar_indicators::indicator_value::IndicatorValue;
use super::super::ohlcv_field::OhlcvField;
#[derive(Debug, Clone)]
pub struct Alma {
period: usize,
source: OhlcvField,
#[allow(dead_code)]
offset: f64,
#[allow(dead_code)]
sigma: f64,
buffer: VecDeque<f64>,
weights: Vec<f64>,
weight_sum: f64,
value: f64,
}
impl Alma {
pub fn new(period: usize) -> Self {
Self::with_params(period, 0.85, 6.0)
}
pub fn with_params(period: usize, offset: f64, sigma: f64) -> Self {
Self::with_source(period, OhlcvField::Close, offset, sigma)
}
pub fn with_source(period: usize, source: OhlcvField, offset: f64, sigma: f64) -> Self {
let period = period.max(1);
let (weights, weight_sum) = Self::build_weights(period, offset, sigma);
Self {
period,
source,
offset,
sigma,
buffer: VecDeque::with_capacity(period),
weights,
weight_sum,
value: 0.0,
}
}
#[inline]
fn build_weights(period: usize, offset: f64, sigma: f64) -> (Vec<f64>, f64) {
let m = offset * (period as f64 - 1.0);
let s = (period as f64 / sigma).max(1e-9);
let mut w = Vec::with_capacity(period);
let mut sum = 0.0;
for i in 0..period {
let x = (i as f64 - m) / s;
let wi = (-0.5 * x * x).exp();
w.push(wi);
sum += wi;
}
(w, sum)
}
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);
self.buffer.push_back(value);
if self.buffer.len() > self.period {
self.buffer.pop_front();
}
if self.buffer.len() == self.period {
let mut acc = 0.0;
for (i, &p) in self.buffer.iter().enumerate() {
acc += p * self.weights[i];
}
self.value = acc / self.weight_sum;
}
self.value
}
#[inline]
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.value)
}
#[inline]
pub fn is_ready(&self) -> bool {
self.buffer.len() == self.period
}
#[inline]
pub fn reset(&mut self) {
self.buffer.clear();
self.value = 0.0;
}
#[inline]
pub fn period(&self) -> usize {
self.period
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alma_basic_calculation() {
let mut alma = Alma::new(5);
for i in 1..=5 {
alma.update_bar(0.0, 0.0, 0.0, i as f64 * 10.0, 0.0);
}
assert!(alma.is_ready());
assert!(alma.value().main() > 0.0);
}
#[test]
fn test_alma_custom_params() {
let mut alma = Alma::with_params(5, 0.5, 3.0);
for i in 1..=5 {
alma.update_bar(0.0, 0.0, 0.0, i as f64 * 10.0, 0.0);
}
assert!(alma.is_ready());
}
#[test]
fn test_alma_reset() {
let mut alma = Alma::new(3);
alma.update_bar(0.0, 0.0, 0.0, 10.0, 0.0);
alma.update_bar(0.0, 0.0, 0.0, 20.0, 0.0);
alma.update_bar(0.0, 0.0, 0.0, 30.0, 0.0);
assert!(alma.is_ready());
alma.reset();
assert!(!alma.is_ready());
}
}