use crate::indicators::utils::{validate_data_length, validate_period};
use crate::indicators::{Candle, Indicator, IndicatorError};
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DonchianResult {
pub upper: f64,
pub middle: f64,
pub lower: f64,
}
#[derive(Debug)]
pub struct Donchian {
period: usize,
buffer: VecDeque<(f64, f64)>,
}
impl Donchian {
pub fn new(period: usize) -> Result<Self, IndicatorError> {
validate_period(period, 1)?;
Ok(Self {
period,
buffer: VecDeque::with_capacity(period),
})
}
pub fn reset_state(&mut self) {
self.buffer.clear();
}
fn step(&mut self, value: Candle) -> Option<DonchianResult> {
self.buffer.push_back((value.high, value.low));
if self.buffer.len() > self.period {
self.buffer.pop_front();
}
if self.buffer.len() < self.period {
return None;
}
let upper = self
.buffer
.iter()
.map(|&(h, _)| h)
.fold(f64::NEG_INFINITY, f64::max);
let lower = self
.buffer
.iter()
.map(|&(_, l)| l)
.fold(f64::INFINITY, f64::min);
Some(DonchianResult {
upper,
middle: (upper + lower) / 2.0,
lower,
})
}
}
impl Indicator<Candle, DonchianResult> for Donchian {
fn calculate(&mut self, data: &[Candle]) -> Result<Vec<DonchianResult>, IndicatorError> {
validate_data_length(data, self.period)?;
self.reset_state();
let mut out = Vec::with_capacity(data.len() - self.period + 1);
for c in data {
if let Some(v) = self.step(*c) {
out.push(v);
}
}
Ok(out)
}
fn next(&mut self, value: Candle) -> Result<Option<DonchianResult>, IndicatorError> {
Ok(self.step(value))
}
fn reset(&mut self) {
self.reset_state();
}
fn name(&self) -> &'static str {
"Donchian"
}
fn period(&self) -> Option<usize> {
Some(self.period)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn donchian_candles() -> Vec<Candle> {
vec![
Candle {
timestamp: 0,
open: 10.0,
high: 12.0,
low: 8.0,
close: 11.0,
volume: 1.0,
},
Candle {
timestamp: 1,
open: 11.0,
high: 13.0,
low: 9.0,
close: 12.0,
volume: 1.0,
},
Candle {
timestamp: 2,
open: 12.0,
high: 15.0,
low: 10.0,
close: 13.0,
volume: 1.0,
},
Candle {
timestamp: 3,
open: 13.0,
high: 14.0,
low: 11.0,
close: 12.0,
volume: 1.0,
},
Candle {
timestamp: 4,
open: 12.0,
high: 16.0,
low: 11.0,
close: 15.0,
volume: 1.0,
},
]
}
#[test]
fn validates_period() {
assert!(Donchian::new(0).is_err());
assert!(Donchian::new(20).is_ok());
}
#[test]
fn returns_max_high_min_low() {
let mut dc = Donchian::new(3).unwrap();
let out = dc.calculate(&donchian_candles()).unwrap();
assert_eq!(out.len(), 3);
assert_eq!(out[0].upper, 15.0);
assert_eq!(out[0].lower, 8.0);
assert_eq!(out[0].middle, 11.5);
assert_eq!(out[2].upper, 16.0);
assert_eq!(out[2].lower, 10.0);
assert_eq!(out[2].middle, 13.0);
}
}