use crate::indicators::traits::Indicator;
use crate::indicators::utils::{standard_deviation, validate_data_length, validate_period};
use crate::indicators::{Candle, IndicatorError};
use std::collections::VecDeque;
#[derive(Debug)]
pub struct Std {
period: usize,
values: VecDeque<f64>,
}
impl Std {
pub fn new(period: usize) -> Result<Self, IndicatorError> {
validate_period(period, 1)?;
Ok(Self {
period,
values: VecDeque::with_capacity(period),
})
}
pub fn reset_state(&mut self) {
self.values.clear();
}
}
impl Indicator<f64, f64> for Std {
fn calculate(&mut self, data: &[f64]) -> Result<Vec<f64>, IndicatorError> {
validate_data_length(data, self.period)?;
let n = data.len();
let mut result = Vec::with_capacity(n - self.period + 1);
self.reset_state();
for i in 0..=(n - self.period) {
let period_data = &data[i..(i + self.period)];
let std_dev = standard_deviation(period_data, None)?;
result.push(std_dev);
}
for &value in data.iter().take(n).skip(n - self.period) {
self.values.push_back(value);
}
Ok(result)
}
fn next(&mut self, value: f64) -> Result<Option<f64>, IndicatorError> {
self.values.push_back(value);
if self.values.len() > self.period {
self.values.pop_front();
}
if self.values.len() == self.period {
standard_deviation(self.values.make_contiguous(), None).map(Some)
} else {
Ok(None)
}
}
fn reset(&mut self) {
self.reset_state();
}
}
impl Indicator<Candle, f64> for Std {
fn calculate(&mut self, data: &[Candle]) -> Result<Vec<f64>, IndicatorError> {
validate_data_length(data, self.period)?;
let close_prices: Vec<f64> = data.iter().map(|candle| candle.close).collect();
self.calculate(&close_prices)
}
fn next(&mut self, candle: Candle) -> Result<Option<f64>, IndicatorError> {
let close_price = candle.close;
self.next(close_price)
}
fn reset(&mut self) {
self.reset_state();
}
}
#[cfg(test)]
mod tests {
use super::*;
const FLOAT_EPSILON: f64 = 1e-10;
fn assert_float_eq(a: f64, b: f64) {
assert!((a - b).abs() < FLOAT_EPSILON, "{} != {}", a, b);
}
#[test]
fn test_std_calculation_basic() {
let mut std = Std::new(3).unwrap();
let data = vec![2.0, 4.0, 6.0];
let result = std.calculate(&data).unwrap();
assert_eq!(result.len(), 1);
assert_float_eq(result[0], 1.632993161855452);
}
#[test]
fn test_std_calculation_multiple_periods() {
let mut std = Std::new(2).unwrap();
let data = vec![1.0, 2.0, 3.0];
let result = std.calculate(&data).unwrap();
assert_eq!(result.len(), 2);
assert_float_eq(result[0], 0.5);
assert_float_eq(result[1], 0.5);
}
#[test]
fn test_std_with_decimal_values() {
let mut std = Std::new(4).unwrap();
let data = vec![1.5, 2.5, 3.5, 4.5];
let result = std.calculate(&data).unwrap();
assert_eq!(result.len(), 1);
assert_float_eq(result[0], 1.118033988749895);
}
#[test]
fn test_std_edge_cases() {
let mut std = Std::new(1).unwrap();
let data = vec![2.0, 4.0, 6.0, 8.0, 10.0];
let result = std.calculate(&data).unwrap();
assert_eq!(result.len(), 5);
for value in result {
assert_float_eq(value, 0.0);
}
let mut std = Std::new(3).unwrap();
let data = vec![5.0, 5.0, 5.0, 5.0, 5.0];
let result = std.calculate(&data).unwrap();
assert_eq!(result.len(), 3);
for value in result {
assert_float_eq(value, 0.0);
}
}
#[test]
fn test_std_next_value() {
let mut std = Std::new(3).unwrap();
assert_eq!(std.next(2.0).unwrap(), None);
assert_eq!(std.next(4.0).unwrap(), None);
let result = std.next(6.0).unwrap().unwrap();
assert_float_eq(result, 1.632993161855452);
let result = std.next(8.0).unwrap().unwrap();
assert_float_eq(result, 1.632993161855452);
}
#[test]
fn test_std_with_market_pattern() {
let mut std = Std::new(5).unwrap();
let data = vec![
100.0, 101.0, 101.5, 102.0, 103.0, 105.0, 104.0, 106.0, 103.0, 107.0, ];
let result = std.calculate(&data).unwrap();
assert_eq!(result.len(), 6);
assert!(result[0] < result[result.len() - 1]);
}
#[test]
fn test_std_error_handling() {
let mut std = Std::new(5).unwrap();
let data = vec![1.0, 2.0, 3.0];
assert!(matches!(
std.calculate(&data),
Err(IndicatorError::InsufficientData(_))
));
let data: Vec<f64> = vec![];
assert!(matches!(
std.calculate(&data),
Err(IndicatorError::InsufficientData(_))
));
assert!(Std::new(100).is_ok());
}
#[test]
fn test_std_reset() {
let mut std = Std::new(3).unwrap();
std.next(1.0).unwrap();
std.next(2.0).unwrap();
std.next(3.0).unwrap();
std.reset_state();
assert_eq!(std.next(4.0).unwrap(), None);
}
#[test]
fn test_std_calculation_with_candles() {
let mut std = Std::new(3).unwrap();
let candles = vec![
Candle {
timestamp: 1,
open: 1.5,
high: 2.5,
low: 1.5,
close: 2.0,
volume: 1000.0,
},
Candle {
timestamp: 2,
open: 3.5,
high: 4.5,
low: 3.5,
close: 4.0,
volume: 1000.0,
},
Candle {
timestamp: 3,
open: 5.5,
high: 6.5,
low: 5.5,
close: 6.0,
volume: 1000.0,
},
];
let result = std.calculate(&candles).unwrap();
assert_eq!(result.len(), 1);
assert_float_eq(result[0], 1.632993161855452);
let prices = vec![2.0, 4.0, 6.0];
let mut std_prices = Std::new(3).unwrap();
let price_result = std_prices.calculate(&prices).unwrap();
assert_eq!(result.len(), price_result.len());
for (res_candle, res_price) in result.iter().zip(price_result.iter()) {
assert_float_eq(*res_candle, *res_price);
}
}
#[test]
fn test_std_next_with_candles() {
let mut std = Std::new(3).unwrap();
let candle1 = Candle {
timestamp: 1,
open: 1.5,
high: 2.5,
low: 1.5,
close: 2.0,
volume: 1000.0,
};
let candle2 = Candle {
timestamp: 2,
open: 3.5,
high: 4.5,
low: 3.5,
close: 4.0,
volume: 1000.0,
};
assert_eq!(std.next(candle1).unwrap(), None);
assert_eq!(std.next(candle2).unwrap(), None);
let candle3 = Candle {
timestamp: 3,
open: 5.5,
high: 6.5,
low: 5.5,
close: 6.0,
volume: 1000.0,
};
let result = std.next(candle3).unwrap().unwrap();
assert_float_eq(result, 1.632993161855452);
let candle4 = Candle {
timestamp: 4,
open: 7.5,
high: 8.5,
low: 7.5,
close: 8.0,
volume: 1000.0,
};
let result = std.next(candle4).unwrap().unwrap();
assert_float_eq(result, 1.632993161855452);
let mut std_prices = Std::new(3).unwrap();
std_prices.next(2.0).unwrap();
std_prices.next(4.0).unwrap();
std_prices.next(6.0).unwrap();
let price_result = std_prices.next(8.0).unwrap().unwrap();
assert_float_eq(result, price_result);
}
#[test]
fn test_std_reset_with_candles() {
let mut std = Std::new(3).unwrap();
let candle1 = Candle {
timestamp: 1,
open: 0.5,
high: 1.5,
low: 0.5,
close: 1.0,
volume: 1000.0,
};
let candle2 = Candle {
timestamp: 2,
open: 1.5,
high: 2.5,
low: 1.5,
close: 2.0,
volume: 1000.0,
};
let candle3 = Candle {
timestamp: 3,
open: 2.5,
high: 3.5,
low: 2.5,
close: 3.0,
volume: 1000.0,
};
std.next(candle1).unwrap();
std.next(candle2).unwrap();
std.next(candle3).unwrap();
std.reset_state();
let candle4 = Candle {
timestamp: 4,
open: 3.5,
high: 4.5,
low: 3.5,
close: 4.0,
volume: 1000.0,
};
assert_eq!(std.next(candle4).unwrap(), None);
}
#[test]
fn test_std_with_market_pattern_candles() {
let mut std = Std::new(5).unwrap();
let candles = vec![
Candle {
timestamp: 1,
open: 99.0,
high: 101.0,
low: 99.0,
close: 100.0,
volume: 1000.0,
},
Candle {
timestamp: 2,
open: 100.0,
high: 102.0,
low: 100.0,
close: 101.0,
volume: 1000.0,
},
Candle {
timestamp: 3,
open: 100.5,
high: 102.5,
low: 100.5,
close: 101.5,
volume: 1000.0,
},
Candle {
timestamp: 4,
open: 101.0,
high: 103.0,
low: 101.0,
close: 102.0,
volume: 1000.0,
},
Candle {
timestamp: 5,
open: 102.0,
high: 104.0,
low: 102.0,
close: 103.0,
volume: 1000.0,
},
Candle {
timestamp: 6,
open: 104.0,
high: 106.0,
low: 104.0,
close: 105.0,
volume: 1000.0,
},
Candle {
timestamp: 7,
open: 103.0,
high: 105.0,
low: 103.0,
close: 104.0,
volume: 1000.0,
},
Candle {
timestamp: 8,
open: 105.0,
high: 107.0,
low: 105.0,
close: 106.0,
volume: 1000.0,
},
Candle {
timestamp: 9,
open: 102.0,
high: 104.0,
low: 102.0,
close: 103.0,
volume: 1000.0,
},
Candle {
timestamp: 10,
open: 106.0,
high: 108.0,
low: 106.0,
close: 107.0,
volume: 1000.0,
},
];
let result = std.calculate(&candles).unwrap();
assert_eq!(result.len(), 6);
assert!(result[0] < result[result.len() - 1]);
let prices = vec![
100.0, 101.0, 101.5, 102.0, 103.0, 105.0, 104.0, 106.0, 103.0, 107.0,
];
let mut std_prices = Std::new(5).unwrap();
let price_result = std_prices.calculate(&prices).unwrap();
assert_eq!(result.len(), price_result.len());
for (res_candle, res_price) in result.iter().zip(price_result.iter()) {
assert_float_eq(*res_candle, *res_price);
}
}
}