work on new engine

This commit is contained in:
Boki 2025-07-04 11:24:27 -04:00
parent 44476da13f
commit a1e5a21847
126 changed files with 3425 additions and 6695 deletions

View file

@ -0,0 +1,213 @@
use super::{Indicator, IncrementalIndicator, IndicatorResult, IndicatorError, PriceData};
/// Exponential Moving Average (EMA) Indicator
///
/// Calculates the exponentially weighted moving average
/// giving more weight to recent prices
pub struct EMA {
period: usize,
alpha: f64,
value: Option<f64>,
initialized: bool,
}
impl EMA {
pub fn new(period: usize) -> Result<Self, IndicatorError> {
if period == 0 {
return Err(IndicatorError::InvalidParameter(
"Period must be greater than 0".to_string()
));
}
// Calculate smoothing factor (alpha)
// Common formula: 2 / (period + 1)
let alpha = 2.0 / (period as f64 + 1.0);
Ok(Self {
period,
alpha,
value: None,
initialized: false,
})
}
/// Create EMA with custom smoothing factor
pub fn with_alpha(alpha: f64) -> Result<Self, IndicatorError> {
if alpha <= 0.0 || alpha > 1.0 {
return Err(IndicatorError::InvalidParameter(
"Alpha must be between 0 and 1".to_string()
));
}
// Calculate equivalent period for reference
let period = ((2.0 / alpha) - 1.0) as usize;
Ok(Self {
period,
alpha,
value: None,
initialized: false,
})
}
/// Calculate EMA for a series of values
pub fn calculate_series(values: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
if period == 0 {
return Err(IndicatorError::InvalidParameter(
"Period must be greater than 0".to_string()
));
}
if values.is_empty() {
return Ok(vec![]);
}
let alpha = 2.0 / (period as f64 + 1.0);
let mut result = Vec::with_capacity(values.len());
// Start with first value as initial EMA
let mut ema = values[0];
result.push(ema);
// Calculate EMA for remaining values
for i in 1..values.len() {
ema = alpha * values[i] + (1.0 - alpha) * ema;
result.push(ema);
}
Ok(result)
}
/// Alternative initialization using SMA of first N values
pub fn calculate_series_sma_init(values: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
if period == 0 {
return Err(IndicatorError::InvalidParameter(
"Period must be greater than 0".to_string()
));
}
if values.len() < period {
return Err(IndicatorError::InsufficientData {
required: period,
actual: values.len(),
});
}
let alpha = 2.0 / (period as f64 + 1.0);
let mut result = Vec::with_capacity(values.len() - period + 1);
// Calculate initial SMA
let initial_sma: f64 = values[0..period].iter().sum::<f64>() / period as f64;
let mut ema = initial_sma;
result.push(ema);
// Calculate EMA for remaining values
for i in period..values.len() {
ema = alpha * values[i] + (1.0 - alpha) * ema;
result.push(ema);
}
Ok(result)
}
}
impl Indicator for EMA {
fn calculate(&mut self, data: &PriceData) -> Result<IndicatorResult, IndicatorError> {
let values = &data.close;
if values.is_empty() {
return Err(IndicatorError::InsufficientData {
required: 1,
actual: 0,
});
}
// Reset and calculate from scratch
self.reset();
let ema_values = Self::calculate_series(values, self.period)?;
// Update internal state with last value
if let Some(&last) = ema_values.last() {
self.value = Some(last);
self.initialized = true;
}
Ok(IndicatorResult::Series(ema_values))
}
fn reset(&mut self) {
self.value = None;
self.initialized = false;
}
fn is_ready(&self) -> bool {
self.initialized && self.value.is_some()
}
}
impl IncrementalIndicator for EMA {
fn update(&mut self, value: f64) -> Result<Option<f64>, IndicatorError> {
match self.value {
Some(prev_ema) => {
// Update EMA: EMA = α × Price + (1 - α) × Previous EMA
let new_ema = self.alpha * value + (1.0 - self.alpha) * prev_ema;
self.value = Some(new_ema);
self.initialized = true;
Ok(Some(new_ema))
}
None => {
// First value becomes the initial EMA
self.value = Some(value);
self.initialized = true;
Ok(Some(value))
}
}
}
fn current(&self) -> Option<f64> {
self.value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ema_calculation() {
let values = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0];
let result = EMA::calculate_series(&values, 3).unwrap();
assert_eq!(result.len(), 6);
assert!((result[0] - 10.0).abs() < 1e-10); // First value
// Verify EMA calculation
let alpha = 2.0 / 4.0; // 0.5
let expected_ema2 = alpha * 11.0 + (1.0 - alpha) * 10.0; // 10.5
assert!((result[1] - expected_ema2).abs() < 1e-10);
}
#[test]
fn test_incremental_ema() {
let mut ema = EMA::new(3).unwrap();
// First value
assert_eq!(ema.update(10.0).unwrap(), Some(10.0));
// Second value: EMA = 0.5 * 12 + 0.5 * 10 = 11
assert_eq!(ema.update(12.0).unwrap(), Some(11.0));
// Third value: EMA = 0.5 * 14 + 0.5 * 11 = 12.5
assert_eq!(ema.update(14.0).unwrap(), Some(12.5));
}
#[test]
fn test_ema_with_sma_init() {
let values = vec![2.0, 4.0, 6.0, 8.0, 10.0, 12.0];
let result = EMA::calculate_series_sma_init(&values, 3).unwrap();
// Initial SMA = (2 + 4 + 6) / 3 = 4
assert_eq!(result.len(), 4);
assert!((result[0] - 4.0).abs() < 1e-10);
}
}