#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::core::{Error, Method, MovingAverageConstructor, PeriodType, ValueType, OHLCV};
use crate::core::{IndicatorConfig, IndicatorInstance, IndicatorResult};
use crate::helpers::MA;
use crate::methods::{Cross, CrossAbove, CrossUnder, Highest, Lowest};
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StochasticOscillator<M: MovingAverageConstructor = MA> {
pub period: PeriodType,
pub ma: M,
pub signal: M,
pub zone: ValueType,
}
impl<M: MovingAverageConstructor> IndicatorConfig for StochasticOscillator<M> {
type Instance = StochasticOscillatorInstance<M>;
const NAME: &'static str = "StochasticOscillator";
fn init<T: OHLCV>(self, candle: &T) -> Result<Self::Instance, Error> {
if !self.validate() {
return Err(Error::WrongConfig);
}
let cfg = self;
#[allow(clippy::float_cmp)]
let k_rows = if candle.high() == candle.low() {
0.5
} else {
(candle.close() - candle.low()) / (candle.high() - candle.low())
};
Ok(Self::Instance {
upper_zone: 1. - cfg.zone,
highest: Highest::new(cfg.period, &candle.high())?,
lowest: Lowest::new(cfg.period, &candle.low())?,
ma1: cfg.ma.init(k_rows)?, ma2: cfg.signal.init(k_rows)?, cross_over: Cross::default(),
cross_above1: CrossAbove::default(),
cross_under1: CrossUnder::default(),
cross_above2: CrossAbove::default(),
cross_under2: CrossUnder::default(),
cfg,
})
}
fn validate(&self) -> bool {
self.period > 1 && self.zone >= 0.0 && self.zone <= 0.5
}
fn set(&mut self, name: &str, value: String) -> Result<(), Error> {
match name {
"period" => match value.parse() {
Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
Ok(value) => self.period = value,
},
"ma" => match value.parse() {
Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
Ok(value) => self.ma = value,
},
"signal" => match value.parse() {
Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
Ok(value) => self.signal = 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) {
(2, 3)
}
}
impl Default for StochasticOscillator {
fn default() -> Self {
Self {
period: 14,
ma: MA::SMA(14),
signal: MA::SMA(3),
zone: 0.2,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StochasticOscillatorInstance<M: MovingAverageConstructor = MA> {
cfg: StochasticOscillator<M>,
upper_zone: ValueType,
highest: Highest,
lowest: Lowest,
ma1: M::Instance,
ma2: M::Instance,
cross_over: Cross,
cross_above1: CrossAbove,
cross_under1: CrossUnder,
cross_above2: CrossAbove,
cross_under2: CrossUnder,
}
impl<M: MovingAverageConstructor> IndicatorInstance for StochasticOscillatorInstance<M> {
type Config = StochasticOscillator<M>;
fn config(&self) -> &Self::Config {
&self.cfg
}
fn next<T: OHLCV>(&mut self, candle: &T) -> IndicatorResult {
let (close, high, low) = (candle.close(), candle.high(), candle.low());
let highest = self.highest.next(&high);
let lowest = self.lowest.next(&low);
#[allow(clippy::float_cmp)]
let k_rows = if highest == lowest {
0.5
} else {
(close - lowest) / (highest - lowest)
};
let f1 = self.ma1.next(&k_rows);
let f2 = self.ma2.next(&f1);
let s1 = self.cross_above1.next(&(f1, self.cfg.zone))
- self.cross_under1.next(&(f1, self.upper_zone));
let s2 = self.cross_above2.next(&(f2, self.cfg.zone))
- self.cross_under2.next(&(f2, self.upper_zone));
let s3 = self.cross_over.next(&(f1, f2));
IndicatorResult::new(&[f1, f2], &[s1, s2, s3])
}
}