use indicato_rs_proc::{Apply, Evaluate};
use crate::{
error::{FinError, FinErrorType},
traits::{Apply, Current, Evaluate, Executable, ExecutionContext, IoState},
};
fn up_down(input: f64, previous: f64) -> (f64, f64) {
match input > previous {
true => (input - previous, 0.0),
false => (0.0, previous - input),
}
}
#[derive(Apply, Evaluate)]
pub struct RelativeStrengthIndex {
seed_period: usize,
up_ws: super::WildersSmoothing,
down_ws: super::WildersSmoothing,
is_seeded: bool,
seed_values: usize,
previous: Option<f64>,
}
impl IoState for RelativeStrengthIndex {
type Input = f64;
type Output = Option<f64>;
}
impl RelativeStrengthIndex {
pub fn new(period: usize, seed_period: usize) -> Result<Self, FinError> {
match period {
0 => Err(FinError::new(
FinErrorType::InvalidInput,
"Period must be greater than 0",
)),
_ => Ok(Self {
seed_period: period + seed_period,
up_ws: super::WildersSmoothing::new(period)?,
down_ws: super::WildersSmoothing::new(period)?,
is_seeded: false,
seed_values: 0,
previous: None,
}),
}
}
}
impl Executable for RelativeStrengthIndex {
fn execute(
&mut self,
input: Self::Input,
execution_context: &ExecutionContext,
) -> Self::Output {
let previous = match self.previous {
None => {
self.previous = Some(input);
self.seed_values += 1;
return None;
}
Some(previous) => previous,
};
let (up, down) = up_down(input, previous);
let up_ws = self.up_ws.execute(up, execution_context);
let down_ws = self.down_ws.execute(down, execution_context);
if !self.is_seeded {
match execution_context {
ExecutionContext::Apply => {
self.seed_values += 1;
if self.seed_values == self.seed_period {
self.is_seeded = true;
}
self.previous = Some(input);
}
ExecutionContext::Evaluate => {}
}
return None;
}
if down_ws == Some(0.0) {
return Some(100.0);
}
match (up_ws, down_ws) {
(Some(up_ws), Some(down_ws)) => {
let rs = up_ws / down_ws;
let rsi = 100.0 - (100.0 / (1.0 + rs));
match execution_context {
ExecutionContext::Apply => {
self.previous = Some(input);
}
ExecutionContext::Evaluate => {}
}
Some(rsi)
}
_ => None,
}
}
}
impl Current for RelativeStrengthIndex {
fn current(&self) -> Self::Output {
if self.is_seeded {
match (self.up_ws.current(), self.down_ws.current()) {
(Some(up_ws), Some(down_ws)) => {
let rs = up_ws / down_ws;
let rsi = 100.0 - (100.0 / (1.0 + rs));
Some(rsi)
}
_ => None,
}
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply() {
let mut rsi = RelativeStrengthIndex::new(3, 0).unwrap();
assert_eq!(rsi.apply(0.0), None);
assert_eq!(rsi.apply(1.0), None);
assert_eq!(rsi.apply(2.0), None);
assert_eq!(rsi.apply(3.0), Some(100.0));
assert_eq!(rsi.apply(4.0), Some(100.0));
assert_eq!(rsi.apply(5.0), Some(100.0));
}
#[test]
fn test_evaluate() {
let mut rsi = RelativeStrengthIndex::new(3, 0).unwrap();
assert_eq!(rsi.apply(0.0), None);
assert_eq!(rsi.apply(1.0), None);
assert_eq!(rsi.apply(2.0), None);
assert_eq!(rsi.apply(3.0), Some(100.0));
assert_eq!(rsi.apply(4.0), Some(100.0));
assert_eq!(rsi.evaluate(5.0), Some(100.0));
assert_eq!(rsi.apply(5.0), Some(100.0));
}
#[test]
fn test_invalid_period() {
let rsi = RelativeStrengthIndex::new(0, 3);
assert!(rsi.is_err());
}
#[test]
fn test_rsi_data() {
let mut rsi = RelativeStrengthIndex::new(14, 0).unwrap();
rsi.apply(10.92521440760443900);
rsi.apply(10.19859579534958400);
rsi.apply(10.67283651362573500);
rsi.apply(10.59985028709600800);
rsi.apply(10.92213316907769000);
rsi.apply(10.21930613382197500);
rsi.apply(10.97345881837492400);
rsi.apply(10.52359275836161700);
rsi.apply(10.84870000849940300);
rsi.apply(10.47114753347496000);
rsi.apply(10.50194664759466100);
rsi.apply(10.56933881368713200);
rsi.apply(10.30682386665992900);
rsi.apply(10.93831484940749100);
assert_eq!(rsi.apply(10.11768126451183100), Some(43.29120317120137));
assert_eq!(rsi.apply(10.11768126451183100), Some(43.29120317120137));
assert_eq!(rsi.apply(10.11768126451183100), Some(43.291203171201374));
assert_eq!(rsi.apply(10.11768126451183100), Some(43.291203171201374));
assert_eq!(rsi.apply(10.11768126451183100), Some(43.291203171201374));
assert_eq!(rsi.apply(10.93831484940749100), Some(52.644368580828655));
}
}