finlib_ta/indicators/
chandelier_exit.rs

1use core::fmt;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use crate::errors::Result;
7use crate::indicators::{AverageTrueRange, Maximum, Minimum};
8use crate::{Close, High, Low, Next, Period, Reset};
9
10/// Chandelier Exit (CE).
11///
12/// Developed by Charles Le Beau and featured in Alexander Elder's books, the Chandelier Exit sets
13/// a trailing stop-loss based on the Average True Range (ATR). The indicator is designed to keep
14/// traders in a trend and prevent an early exit as long as the trend extends. Typically, the
15/// Chandelier Exit will be above prices during a downtrend and below prices during an uptrend.
16///
17/// # Formula
18///
19/// Chandelier Exit (long) = Max(_period_) - ATR(_period_) * _multipler_
20/// Chandelier Exit (short) = Min(_period_) + ATR(_period_) * _multipler_
21///
22/// # Parameters
23///
24/// * _period_ - number of periods (integer greater than 0). Default is 22.
25/// * _multipler_ - ATR factor. Default is 3.
26///
27/// # Example
28///
29/// ```
30/// use finlib_ta::indicators::ChandelierExit;
31/// use finlib_ta::{Next, DataItem};
32///
33/// let value1 = DataItem::builder()
34/// .open(21.0).high(22.0).low(20.0).close(21.0).volume(1.0).build().unwrap();
35/// let value2 = DataItem::builder()
36/// .open(23.0).high(24.0).low(22.0).close(23.0).volume(1.0).build().unwrap();
37///
38/// let mut ce = ChandelierExit::default();
39///
40/// let first = ce.next(&value1);
41/// assert_eq!(first.long, 16.0);
42/// assert_eq!(first.short, 26.0);
43///
44/// let second = ce.next(&value2);
45/// assert_eq!((second.long * 100.0).round() / 100.0, 17.74);
46/// assert_eq!((second.short * 100.0).round() / 100.0, 26.26);
47/// ```
48///
49/// # Links
50///
51/// * [Chandelier Exit, StockCharts](https://school.stockcharts.com/doku.php?id=technical_indicators:chandelier_exit)
52///
53#[doc(alias = "CE")]
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55#[derive(Debug, Clone)]
56pub struct ChandelierExit {
57    atr: AverageTrueRange,
58    min: Minimum,
59    max: Maximum,
60    multiplier: f64,
61}
62
63impl ChandelierExit {
64    pub fn new(period: usize, multiplier: f64) -> Result<Self> {
65        Ok(Self {
66            atr: AverageTrueRange::new(period)?,
67            min: Minimum::new(period)?,
68            max: Maximum::new(period)?,
69            multiplier,
70        })
71    }
72
73    pub fn multiplier(&self) -> f64 {
74        self.multiplier
75    }
76}
77
78#[derive(Debug, Clone, PartialEq)]
79pub struct ChandelierExitOutput {
80    pub long: f64,
81    pub short: f64,
82}
83
84impl From<ChandelierExitOutput> for (f64, f64) {
85    fn from(ce: ChandelierExitOutput) -> Self {
86        (ce.long, ce.short)
87    }
88}
89
90impl Period for ChandelierExit {
91    fn period(&self) -> usize {
92        self.atr.period()
93    }
94}
95
96impl<T: Low + High + Close> Next<&T> for ChandelierExit {
97    type Output = ChandelierExitOutput;
98
99    fn next(&mut self, input: &T) -> Self::Output {
100        let atr = self.atr.next(input) * self.multiplier;
101        let min = self.min.next(input);
102        let max = self.max.next(input);
103
104        ChandelierExitOutput {
105            long: max - atr,
106            short: min + atr,
107        }
108    }
109}
110
111impl Reset for ChandelierExit {
112    fn reset(&mut self) {
113        self.atr.reset();
114        self.min.reset();
115        self.max.reset();
116    }
117}
118
119impl Default for ChandelierExit {
120    fn default() -> Self {
121        Self::new(22, 3.0).unwrap()
122    }
123}
124
125impl fmt::Display for ChandelierExit {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        write!(f, "CE({}, {})", self.atr.period(), self.multiplier)
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use crate::test_helper::*;
134
135    use super::*;
136    use alloc::format;
137
138    type Ce = ChandelierExit;
139
140    fn round(nums: (f64, f64)) -> (f64, f64) {
141        let n0 = (nums.0 * 100.0).round() / 100.0;
142        let n1 = (nums.1 * 100.0).round() / 100.0;
143        (n0, n1)
144    }
145
146    #[test]
147    fn test_new() {
148        assert!(Ce::new(0, 0.0).is_err());
149        assert!(Ce::new(1, 1.0).is_ok());
150        assert!(Ce::new(22, 3.0).is_ok());
151    }
152
153    #[test]
154    fn test_next_bar() {
155        let mut ce = Ce::new(5, 2.0).unwrap();
156
157        let bar1 = Bar::new().high(2).low(1).close(1.5);
158        assert_eq!(round(ce.next(&bar1).into()), (0.0, 3.0));
159
160        let bar2 = Bar::new().high(5).low(3).close(4);
161        assert_eq!(round(ce.next(&bar2).into()), (1.33, 4.67));
162
163        let bar3 = Bar::new().high(9).low(7).close(8);
164        assert_eq!(round(ce.next(&bar3).into()), (3.22, 6.78));
165
166        let bar4 = Bar::new().high(5).low(3).close(4);
167        assert_eq!(round(ce.next(&bar4).into()), (1.81, 8.19));
168
169        let bar5 = Bar::new().high(5).low(3).close(4);
170        assert_eq!(round(ce.next(&bar5).into()), (2.88, 7.12));
171
172        let bar6 = Bar::new().high(2).low(1).close(1.5);
173        assert_eq!(round(ce.next(&bar6).into()), (2.92, 7.08));
174    }
175
176    #[test]
177    fn test_reset() {
178        let mut ce = Ce::new(5, 2.0).unwrap();
179
180        let bar1 = Bar::new().high(2).low(1).close(1.5);
181        let bar2 = Bar::new().high(5).low(3).close(4);
182
183        assert_eq!(round(ce.next(&bar1).into()), (0.0, 3.0));
184        assert_eq!(round(ce.next(&bar2).into()), (1.33, 4.67));
185
186        ce.reset();
187
188        assert_eq!(round(ce.next(&bar1).into()), (0.0, 3.0));
189        assert_eq!(round(ce.next(&bar2).into()), (1.33, 4.67));
190    }
191
192    #[test]
193    fn test_default() {
194        Ce::default();
195    }
196
197    #[test]
198    fn test_display() {
199        let indicator = Ce::new(10, 5.0).unwrap();
200        assert_eq!(format!("{}", indicator), "CE(10, 5)");
201    }
202}