use crate::bar_indicators::volatility::atr::Atr;
use crate::bar_indicators::average::MovingAverageType;
use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Debug, Clone)]
pub struct ChandelierStop {
period: usize,
multiplier: f64,
atr: Atr,
highs: Vec<f64>,
lows: Vec<f64>,
long_stop: f64, short_stop: f64,
bars_count: usize,
is_ready: bool,
}
impl ChandelierStop {
pub fn new() -> Self {
Self::with_params(22, 3.0)
}
pub fn with_params(period: usize, multiplier: f64) -> Self {
assert!(period > 0, "Period must be greater than 0");
assert!(multiplier > 0.0, "Multiplier must be greater than 0");
Self {
period,
multiplier,
atr: Atr::new(period, MovingAverageType::RMA),
highs: Vec::with_capacity(512),
lows: Vec::with_capacity(512),
long_stop: 0.0,
short_stop: 0.0,
bars_count: 0,
is_ready: false,
}
}
pub fn with_atr_type(period: usize, multiplier: f64, atr_type: MovingAverageType) -> Self {
assert!(period > 0, "Period must be greater than 0");
assert!(multiplier > 0.0, "Multiplier must be greater than 0");
Self {
period,
multiplier,
atr: Atr::new(period, atr_type),
highs: Vec::with_capacity(512),
lows: Vec::with_capacity(512),
long_stop: 0.0,
short_stop: 0.0,
bars_count: 0,
is_ready: false,
}
}
pub fn for_optimization(period: usize, multiplier: f64, atr_type: MovingAverageType) -> Self {
Self::with_atr_type(period, multiplier, atr_type)
}
pub fn update_bar(&mut self, open: f64, high: f64, low: f64, close: f64, volume: f64) -> (f64, f64) {
self.bars_count += 1;
let atr_value = self.atr.update_bar(open, high, low, close, volume);
if self.highs.len() >= self.period {
self.highs.remove(0);
self.lows.remove(0);
}
self.highs.push(high);
self.lows.push(low);
let highest_high = self.highs.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let lowest_low = self.lows.iter().cloned().fold(f64::INFINITY, f64::min);
self.long_stop = highest_high - (atr_value * self.multiplier);
self.short_stop = lowest_low + (atr_value * self.multiplier);
self.is_ready = self.bars_count >= self.period && self.atr.is_ready();
(self.long_stop, self.short_stop)
}
pub fn long_stop(&self) -> f64 {
self.long_stop
}
pub fn short_stop(&self) -> f64 {
self.short_stop
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.long_stop)
}
pub fn levels(&self) -> (f64, f64) {
(self.long_stop, self.short_stop)
}
pub fn is_ready(&self) -> bool {
self.is_ready
}
pub fn atr_value(&self) -> f64 {
self.atr.value().main()
}
pub fn highest_high(&self) -> f64 {
if self.highs.is_empty() {
0.0
} else {
self.highs.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
}
}
pub fn lowest_low(&self) -> f64 {
if self.lows.is_empty() {
0.0
} else {
self.lows.iter().cloned().fold(f64::INFINITY, f64::min)
}
}
pub fn reset(&mut self) {
self.atr.reset();
self.highs.clear();
self.lows.clear();
self.long_stop = 0.0;
self.short_stop = 0.0;
self.bars_count = 0;
self.is_ready = false;
}
pub fn params(&self) -> (usize, f64) {
(self.period, self.multiplier)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chandelier_stop_creation() {
let ind = ChandelierStop::new();
assert!(!ind.is_ready());
assert_eq!(ind.long_stop(), 0.0);
}
#[test]
fn test_chandelier_stop_with_params() {
let ind = ChandelierStop::with_params(20, 2.5);
assert!(!ind.is_ready());
assert_eq!(ind.params(), (20, 2.5));
}
#[test]
fn test_chandelier_stop_warmup() {
let mut ind = ChandelierStop::with_params(22, 3.0);
for i in 0..30 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
ind.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
assert!(ind.is_ready());
}
#[test]
fn test_chandelier_stop_values_finite() {
let mut ind = ChandelierStop::with_params(22, 3.0);
for i in 0..40 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
let (long, short) = ind.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
assert!(long.is_finite());
assert!(short.is_finite());
}
}
#[test]
fn test_chandelier_stop_reset() {
let mut ind = ChandelierStop::with_params(22, 3.0);
for i in 0..30 {
ind.update_bar(100.0 + i as f64, 105.0, 95.0, 101.0, 1000.0);
}
ind.reset();
assert!(!ind.is_ready());
assert_eq!(ind.long_stop(), 0.0);
}
}