use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::momentum::rsi::Rsi;
#[derive(Clone)]
pub struct ThresholdGate {
upper: f64,
lower: f64,
rsi: Rsi,
signal: i8,
}
impl ThresholdGate {
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 {
upper: upper.clamp(50.0, 100.0),
lower: lower.clamp(0.0, 50.0),
rsi: Rsi::new(rsi_period),
signal: 0,
}
}
#[inline]
pub fn reset(&mut self) {
self.rsi.reset();
self.signal = 0;
}
#[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();
self.signal = if rsi_value >= self.upper {
1 } else if rsi_value <= self.lower {
-1 } else {
0 };
}
self.signal
}
#[inline]
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Signal(self.signal)
}
pub fn thresholds(&self) -> (f64, f64) {
(self.lower, self.upper)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_threshold_gate_creation() {
let tg = ThresholdGate::new(30.0, 70.0);
assert!(!tg.is_ready()); assert_eq!(tg.value().as_signal(), Some(0));
assert_eq!(tg.thresholds(), (30.0, 70.0));
}
#[test]
fn test_threshold_gate_with_uptrend() {
let mut tg = ThresholdGate::new(30.0, 70.0);
let mut price = 100.0;
for _ in 0..30 {
price += 2.0; tg.update_bar(price - 1.0, price + 0.5, price - 1.5, price, 1000.0);
}
assert!(tg.is_ready());
let signal = tg.value().as_signal().unwrap();
assert!(signal >= 0, "Strong uptrend should not be oversold");
}
#[test]
fn test_threshold_gate_with_downtrend() {
let mut tg = ThresholdGate::new(30.0, 70.0);
let mut price = 200.0;
for _ in 0..30 {
price -= 2.0; tg.update_bar(price + 1.0, price + 1.5, price - 0.5, price, 1000.0);
}
assert!(tg.is_ready());
let signal = tg.value().as_signal().unwrap();
assert!(signal <= 0, "Strong downtrend should not be overbought");
}
#[test]
fn test_threshold_gate_with_rsi_period() {
let mut tg = ThresholdGate::with_rsi_period(30.0, 70.0, 7);
assert!(!tg.is_ready());
let mut price = 100.0;
for _ in 0..20 {
price += 2.0;
tg.update_bar(price - 1.0, price + 0.5, price - 1.5, price, 1000.0);
}
assert!(tg.is_ready());
let sig = tg.value().as_signal().unwrap();
assert!(sig >= -1 && sig <= 1);
}
#[test]
fn test_threshold_gate_reset() {
let mut tg = ThresholdGate::new(30.0, 70.0);
let mut price = 100.0;
for _ in 0..20 {
price += 1.0;
tg.update_bar(price, price + 0.5, price - 0.5, price, 1000.0);
}
tg.reset();
assert!(!tg.is_ready());
assert_eq!(tg.value().as_signal(), Some(0));
}
}