#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::HLC;
use crate::core::{Error, Method, MovingAverageConstructor, PeriodType, ValueType, Window, OHLCV};
use crate::core::{IndicatorConfig, IndicatorInstance, IndicatorResult};
use crate::helpers::MA;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AverageDirectionalIndex<M: MovingAverageConstructor = MA> {
pub method1: M,
pub method2: M,
pub period1: PeriodType,
pub zone: ValueType,
}
impl<M: MovingAverageConstructor> IndicatorConfig for AverageDirectionalIndex<M> {
type Instance = AverageDirectionalIndexInstance<M>;
const NAME: &'static str = "AverageDirectionalIndex";
fn init<T: OHLCV>(self, candle: &T) -> Result<Self::Instance, Error> {
if !self.validate() {
return Err(Error::WrongConfig);
}
let cfg = self;
let tr = candle.tr(candle);
Ok(Self::Instance {
window: Window::new(cfg.period1, HLC::from(candle)),
prev_close: candle.close(),
tr_ma: cfg.method1.init(tr)?,
plus_di: cfg.method1.init(0.0)?,
minus_di: cfg.method1.init(0.0)?,
ma2: cfg.method2.init(0.0)?,
cfg,
})
}
fn validate(&self) -> bool {
self.method1.ma_period() >= 1
&& self.method1.ma_period() < PeriodType::MAX
&& self.method2.ma_period() >= 1
&& self.method2.ma_period() < PeriodType::MAX
&& self.zone >= 0.
&& self.zone <= 1.
&& self.period1 >= 1
&& self.period1 < self.method1.ma_period()
&& self.period1 < self.method2.ma_period()
}
fn set(&mut self, name: &str, value: String) -> Result<(), Error> {
match name {
"method1" => match value.parse() {
Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
Ok(value) => self.method1 = value,
},
"method2" => match value.parse() {
Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
Ok(value) => self.method2 = value,
},
"period1" => match value.parse() {
Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
Ok(value) => self.period1 = value,
},
"zone" => match value.parse() {
Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
Ok(value) => self.zone = value,
},
_ => {
return Err(Error::ParameterParse(name.to_string(), value));
}
};
Ok(())
}
fn size(&self) -> (u8, u8) {
(3, 2)
}
}
impl Default for AverageDirectionalIndex {
fn default() -> Self {
Self {
method1: MA::RMA(14),
method2: MA::RMA(14),
period1: 1,
zone: 0.2,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AverageDirectionalIndexInstance<M: MovingAverageConstructor = MA> {
cfg: AverageDirectionalIndex<M>,
window: Window<HLC>,
prev_close: ValueType,
tr_ma: M::Instance,
plus_di: M::Instance,
minus_di: M::Instance,
ma2: M::Instance,
}
impl<M: MovingAverageConstructor> AverageDirectionalIndexInstance<M> {
fn dir_mov(&mut self, candle: HLC) -> (ValueType, ValueType) {
let prev_candle = self.window.push(candle);
let true_range = self.tr_ma.next(&candle.tr_close(self.prev_close));
if true_range == 0.0 {
return (0.0, 0.0);
}
self.prev_close = candle.close();
let (du, dd) = (
candle.high() - prev_candle.high(),
prev_candle.low() - candle.low(),
);
let plus_dm = du * (du > dd && du > 0.) as u8 as ValueType; let minus_dm = dd * (dd > du && dd > 0.) as u8 as ValueType;
let plus_di_value = self.plus_di.next(&plus_dm); let minus_di_value = self.minus_di.next(&minus_dm);
(plus_di_value / true_range, minus_di_value / true_range)
}
fn adx(&mut self, plus: ValueType, minus: ValueType) -> ValueType {
let s = plus + minus;
if s == 0. {
return self.ma2.next(&0.);
}
let t = (plus - minus).abs() / s;
self.ma2.next(&t)
}
}
impl<M: MovingAverageConstructor> IndicatorInstance for AverageDirectionalIndexInstance<M> {
type Config = AverageDirectionalIndex<M>;
fn config(&self) -> &Self::Config {
&self.cfg
}
fn next<T: OHLCV>(&mut self, candle: &T) -> IndicatorResult {
let (plus, minus) = self.dir_mov(HLC::from(candle));
let adx = self.adx(plus, minus);
let signal1 = (adx > self.cfg.zone) as i8 * ((plus > minus) as i8 - (plus < minus) as i8);
let signal2 = plus - minus;
let values = [adx, plus, minus];
IndicatorResult::new(&values, &[signal1.into(), signal2.into()])
}
}