use super::error::IndicatorError;
pub trait Indicator<T, O> {
fn calculate(&mut self, data: &[T]) -> Result<Vec<O>, IndicatorError>;
fn next(&mut self, value: T) -> Result<Option<O>, IndicatorError>;
fn reset(&mut self);
fn name(&self) -> &'static str {
let full = std::any::type_name::<Self>();
full.rsplit("::").next().unwrap_or(full)
}
fn period(&self) -> Option<usize> {
None
}
}
pub trait PriceDataAccessor<T> {
fn get_close(&self, data: &T) -> f64;
fn get_high(&self, data: &T) -> f64;
fn get_low(&self, data: &T) -> f64;
fn get_open(&self, data: &T) -> f64;
fn get_volume(&self, data: &T) -> f64;
}
impl PriceDataAccessor<f64> for f64 {
fn get_close(&self, data: &f64) -> f64 {
*data
}
fn get_high(&self, _: &f64) -> f64 {
*self
}
fn get_low(&self, _: &f64) -> f64 {
*self
}
fn get_open(&self, _: &f64) -> f64 {
*self
}
fn get_volume(&self, _: &f64) -> f64 {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::IndicatorError;
#[derive(Debug, Clone, Copy)]
struct MockCandle {
open: f64,
high: f64,
low: f64,
close: f64,
volume: f64,
}
impl PriceDataAccessor<MockCandle> for MockCandle {
fn get_open(&self, data: &MockCandle) -> f64 {
data.open
}
fn get_high(&self, data: &MockCandle) -> f64 {
data.high
}
fn get_low(&self, data: &MockCandle) -> f64 {
data.low
}
fn get_close(&self, data: &MockCandle) -> f64 {
data.close
}
fn get_volume(&self, data: &MockCandle) -> f64 {
data.volume
}
}
struct MockAverageIndicator {
values: Vec<f64>,
}
impl MockAverageIndicator {
fn new() -> Self {
Self { values: Vec::new() }
}
}
impl Indicator<f64, f64> for MockAverageIndicator {
fn calculate(&mut self, data: &[f64]) -> Result<Vec<f64>, IndicatorError> {
if data.is_empty() {
return Err(IndicatorError::InsufficientData("Empty data".to_string()));
}
let average = data.iter().sum::<f64>() / data.len() as f64;
Ok(vec![average])
}
fn next(&mut self, value: f64) -> Result<Option<f64>, IndicatorError> {
self.values.push(value);
let avg = self.values.iter().sum::<f64>() / self.values.len() as f64;
Ok(Some(avg))
}
fn reset(&mut self) {
self.values.clear();
}
}
#[test]
fn test_price_data_accessor_f64() {
let price = 42.0;
assert_eq!(price.get_close(&price), 42.0);
assert_eq!(price.get_high(&price), 42.0);
assert_eq!(price.get_low(&price), 42.0);
assert_eq!(price.get_open(&price), 42.0);
assert_eq!(price.get_volume(&price), 0.0); }
#[test]
fn test_price_data_accessor_mock_candle() {
let candle = MockCandle {
open: 10.0,
high: 15.0,
low: 9.0,
close: 14.0,
volume: 1000.0,
};
assert_eq!(candle.get_open(&candle), 10.0);
assert_eq!(candle.get_high(&candle), 15.0);
assert_eq!(candle.get_low(&candle), 9.0);
assert_eq!(candle.get_close(&candle), 14.0);
assert_eq!(candle.get_volume(&candle), 1000.0);
}
#[test]
fn test_indicator_calculate() {
let mut indicator = MockAverageIndicator::new();
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = indicator.calculate(&data).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], 3.0);
let empty: Vec<f64> = Vec::new();
let error_result = indicator.calculate(&empty);
assert!(error_result.is_err());
if let Err(IndicatorError::InsufficientData(msg)) = error_result {
assert!(msg.contains("Empty"));
} else {
panic!("Expected InsufficientData error");
}
}
#[test]
fn test_indicator_next() {
let mut indicator = MockAverageIndicator::new();
assert_eq!(indicator.next(10.0).unwrap(), Some(10.0));
assert_eq!(indicator.next(20.0).unwrap(), Some(15.0));
assert_eq!(indicator.next(30.0).unwrap(), Some(20.0));
indicator.reset();
assert_eq!(indicator.next(100.0).unwrap(), Some(100.0));
}
#[test]
fn test_trait_usage_with_generic_function() {
fn calculate_range<T, P: PriceDataAccessor<T>>(data: &[T], accessor: &P) -> f64 {
if data.is_empty() {
return 0.0;
}
let high = accessor.get_high(&data[0]);
let low = accessor.get_low(&data[0]);
high - low
}
let prices = vec![42.0];
let range = calculate_range(&prices, &prices[0]);
assert_eq!(range, 0.0);
let candles = vec![MockCandle {
open: 10.0,
high: 15.0,
low: 9.0,
close: 14.0,
volume: 1000.0,
}];
let range = calculate_range(&candles, &candles[0]);
assert_eq!(range, 6.0); }
}