use crate::core::Method;
use crate::core::{Action, Error, ValueType};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Cross {
up: CrossAbove,
down: CrossUnder,
}
impl Method for Cross {
type Params = ();
type Input = (ValueType, ValueType);
type Output = Action;
fn new(_: Self::Params, value: &Self::Input) -> Result<Self, Error>
where
Self: Sized,
{
Ok(Self {
up: CrossAbove::new((), value).unwrap(),
down: CrossUnder::new((), value).unwrap(),
})
}
#[inline]
fn next(&mut self, value: &Self::Input) -> Self::Output {
let up = self.up.binary(value.0, value.1);
let down = self.down.binary(value.0, value.1);
((up as i8) - (down as i8)).into()
}
}
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CrossAbove {
last_delta: ValueType,
}
impl CrossAbove {
#[inline]
pub fn binary(&mut self, value1: ValueType, value2: ValueType) -> bool {
let last_delta = self.last_delta;
let current_delta = value1 - value2;
self.last_delta = current_delta;
last_delta < 0. && current_delta >= 0.
}
}
impl Method for CrossAbove {
type Params = ();
type Input = (ValueType, ValueType);
type Output = Action;
fn new(_: Self::Params, value: &Self::Input) -> Result<Self, Error>
where
Self: Sized,
{
Ok(Self {
last_delta: value.0 - value.1,
})
}
#[inline]
fn next(&mut self, value: &Self::Input) -> Self::Output {
Action::from(self.binary(value.0, value.1) as i8)
}
}
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CrossUnder {
last_delta: ValueType,
}
impl CrossUnder {
#[inline]
pub fn binary(&mut self, value1: ValueType, value2: ValueType) -> bool {
let last_delta = self.last_delta;
let current_delta = value1 - value2;
self.last_delta = current_delta;
last_delta > 0. && current_delta <= 0.
}
}
impl Method for CrossUnder {
type Params = ();
type Input = (ValueType, ValueType);
type Output = Action;
fn new(_: Self::Params, value: &Self::Input) -> Result<Self, Error>
where
Self: Sized,
{
Ok(Self {
last_delta: value.0 - value.1,
})
}
#[inline]
fn next(&mut self, value: &Self::Input) -> Self::Output {
Action::from(self.binary(value.0, value.1) as i8)
}
}
#[cfg(test)]
mod tests {
use crate::core::{Method, ValueType};
use crate::helpers::RandomCandles;
use crate::methods::tests::test_const;
#[test]
fn test_cross_const() {
use super::Cross as TestingMethod;
let input = &(7.0, 1.0);
let mut cross = TestingMethod::new((), input).unwrap();
let output = cross.next(input);
test_const(&mut cross, input, &output);
}
#[test]
fn test_cross() {
use super::Cross as TestingMethod;
let candles = RandomCandles::default();
let src: Vec<ValueType> = candles.take(100).map(|x| x.close).collect();
let avg = src.iter().sum::<ValueType>() / src.len() as ValueType;
let mut ma = TestingMethod::new((), &(src[0], avg)).unwrap();
src.iter().enumerate().for_each(|(i, &x)| {
let value1 = ma.next(&(x, avg)).analog();
let value2;
if x > avg && src[i.saturating_sub(1)] < avg {
value2 = 1;
} else if x < avg && src[i.saturating_sub(1)] > avg {
value2 = -1;
} else {
value2 = 0;
}
assert_eq!(value1, value2, "{}, {} at index {}", value2, value1, i);
});
}
#[test]
fn test_cross_above_const() {
use super::CrossAbove as TestingMethod;
let input = &(7.0, 1.0);
let mut cross = TestingMethod::new((), input).unwrap();
let output = cross.next(input);
test_const(&mut cross, input, &output);
}
#[test]
fn test_cross_above() {
use super::CrossAbove as TestingMethod;
let candles = RandomCandles::default();
let src: Vec<ValueType> = candles.take(100).map(|x| x.close).collect();
let avg = src.iter().sum::<ValueType>() / src.len() as ValueType;
let mut ma = TestingMethod::new((), &(src[0], avg)).unwrap();
src.iter().enumerate().for_each(|(i, &x)| {
let value1 = ma.next(&(x, avg)).analog();
let value2 = if x > avg && src[i.saturating_sub(1)] < avg {
1
} else {
0
};
assert_eq!(value1, value2, "{}, {} at index {}", value2, value1, i);
});
}
#[test]
fn test_cross_under_const() {
use super::CrossUnder as TestingMethod;
let input = &(7.0, 1.0);
let mut cross = TestingMethod::new((), input).unwrap();
let output = cross.next(input);
test_const(&mut cross, input, &output);
}
#[test]
fn test_cross_under() {
use super::CrossUnder as TestingMethod;
let candles = RandomCandles::default();
let src: Vec<ValueType> = candles.take(100).map(|x| x.close).collect();
let avg = src.iter().sum::<ValueType>() / src.len() as ValueType;
let mut ma = TestingMethod::new((), &(src[0], avg)).unwrap();
src.iter().enumerate().for_each(|(i, &x)| {
let value1 = ma.next(&(x, avg)).analog();
let value2 = if x < avg && src[i.saturating_sub(1)] > avg {
1
} else {
0
};
assert_eq!(value1, value2, "{}, {} at index {}", value2, value1, i);
});
}
}