use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::momentum::rsi::Rsi;
#[derive(Clone)]
pub struct HysteresisGate {
lower: f64,
upper: f64,
state: i8,
rsi: Rsi,
}
impl HysteresisGate {
pub fn new(lower: f64, upper: f64) -> Self {
Self::with_rsi_period(lower, upper, 14)
}
pub fn with_rsi_period(lower: f64, upper: f64, rsi_period: usize) -> Self {
Self {
lower: lower.clamp(0.0, 50.0),
upper: upper.clamp(50.0, 100.0),
state: 0,
rsi: Rsi::new(rsi_period),
}
}
#[inline]
pub fn reset(&mut self) {
self.state = 0;
self.rsi.reset();
}
#[inline]
pub fn is_ready(&self) -> bool {
self.rsi.is_ready()
}
#[inline]
pub fn feed(&mut self, _x: f64) {
}
pub fn update_bar(
&mut self,
open: f64,
high: f64,
low: f64,
close: f64,
volume: f64,
) -> i8 {
self.rsi.update_bar(open, high, low, close, volume);
if self.rsi.is_ready() {
let rsi_value = self.rsi.value().main();
if self.state <= 0 && rsi_value >= self.upper {
self.state = 1; } else if self.state >= 0 && rsi_value <= self.lower {
self.state = -1; }
}
self.state
}
#[inline]
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Signal(self.state)
}
pub fn lower(&self) -> f64 {
self.lower
}
pub fn upper(&self) -> f64 {
self.upper
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hysteresis_gate_creation() {
let hg = HysteresisGate::new(30.0, 70.0);
assert!(!hg.is_ready()); assert_eq!(hg.value().as_signal(), Some(0));
assert!((hg.lower() - 30.0).abs() < 1e-9);
assert!((hg.upper() - 70.0).abs() < 1e-9);
}
#[test]
fn test_hysteresis_gate_with_trend() {
let mut hg = HysteresisGate::new(30.0, 70.0);
let mut price = 100.0;
for _ in 0..30 {
price += 2.0;
hg.update_bar(price - 1.0, price + 0.5, price - 1.5, price, 1000.0);
}
assert!(hg.is_ready());
let state = hg.value().as_signal().unwrap();
assert!(state >= 0, "Uptrend should not give oversold signal");
}
#[test]
fn test_hysteresis_gate_with_rsi_period() {
let mut hg = HysteresisGate::with_rsi_period(30.0, 70.0, 7);
assert!(!hg.is_ready());
let mut price = 100.0;
for _ in 0..20 {
price += 2.0;
hg.update_bar(price - 1.0, price + 0.5, price - 1.5, price, 1000.0);
}
assert!(hg.is_ready());
assert!(hg.value().as_signal().unwrap().abs() <= 1);
}
#[test]
fn test_hysteresis_gate_reset() {
let mut hg = HysteresisGate::new(30.0, 70.0);
let mut price = 100.0;
for _ in 0..20 {
price += 1.0;
hg.update_bar(price, price + 0.5, price - 0.5, price, 1000.0);
}
hg.reset();
assert!(!hg.is_ready());
assert_eq!(hg.value().as_signal(), Some(0));
}
}