Skip to content

Commit deac215

Browse files
committed
add: indicator parabolic stop and reverse
1 parent ecd65d8 commit deac215

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed

src/indicators/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,6 @@ pub use self::money_flow_index::MoneyFlowIndex;
6767

6868
mod on_balance_volume;
6969
pub use self::on_balance_volume::OnBalanceVolume;
70+
71+
mod parabolic_stop_and_reverse;
72+
pub use self::parabolic_stop_and_reverse::ParabolicStopAndReverse;
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use std::fmt;
2+
3+
use crate::errors::Result;
4+
use crate::{High, Low, Next, Reset};
5+
#[cfg(feature = "serde")]
6+
use serde::{Deserialize, Serialize};
7+
8+
/// Parabolic Stop and Reverse (Parabolic SAR) indicator.
9+
///
10+
/// The Parabolic SAR is a trend-following indicator used to determine the direction of an asset's momentum and potential reversal points.
11+
/// It is displayed as a series of dots placed either above or below the price bars, indicating potential stop and reverse levels.
12+
///
13+
/// # Formula
14+
/// The Parabolic SAR is calculated:
15+
/// - If the trend is **up**, SAR is placed below the price and rises over time.
16+
/// - If the trend is **down**, SAR is placed above the price and falls over time.
17+
/// - The indicator starts with an initial acceleration factor (`step_af`) and increases it up to a maximum (`maximum_af`) as the trend extends.
18+
///
19+
/// The formula for the next SAR value is:
20+
/// `SAR = previous_SAR + acceleration_factor * (extreme_point - previous_SAR)`
21+
/// where `extreme_point` is the highest high (for uptrend) or lowest low (for downtrend) during the trend.
22+
///
23+
/// # Example
24+
/// ```
25+
/// use ta::{DataItem, Next};
26+
/// use ta::indicators::ParabolicStopAndReverse;
27+
///
28+
/// let mut sar = ParabolicStopAndReverse::new(0.2, 0.02).unwrap();
29+
/// let bar = DataItem::builder().open(105.0).high(105.0).low(95.0).close(95.0).volume(0.0).build().unwrap();
30+
/// let sar_value = sar.next(&bar);
31+
/// println!("Parabolic SAR: {}", sar_value);
32+
/// ```
33+
#[doc(alias = "ParabolicSAR")]
34+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35+
#[derive(Debug, Clone)]
36+
37+
pub struct ParabolicStopAndReverse {
38+
accelerator_factor: f64,
39+
maximum_af: f64,
40+
step_af: f64,
41+
extreme_point: f64,
42+
stop_and_reverse: f64,
43+
is_uptrend: bool,
44+
initialized: bool,
45+
}
46+
47+
impl ParabolicStopAndReverse {
48+
pub fn new(max_af: f64, step_af: f64) -> Result<Self> {
49+
if max_af <= 0. || step_af <= 0. || max_af <= step_af {
50+
return Err(crate::errors::TaError::InvalidParameter);
51+
}
52+
53+
Ok(Self {
54+
accelerator_factor: 0.0,
55+
maximum_af: max_af,
56+
step_af,
57+
extreme_point: 0.0,
58+
stop_and_reverse: 0.0,
59+
is_uptrend: false,
60+
initialized: false,
61+
})
62+
}
63+
64+
pub fn is_uptrend(&self) -> bool {
65+
self.is_uptrend
66+
}
67+
}
68+
69+
impl<T: High + Low> Next<&T> for ParabolicStopAndReverse {
70+
type Output = f64;
71+
72+
fn next(&mut self, input: &T) -> Self::Output {
73+
let (high, low) = (input.high(), input.low());
74+
75+
if !self.initialized {
76+
self.is_uptrend = true; // uptrend by default
77+
self.extreme_point = high;
78+
self.stop_and_reverse = low;
79+
self.initialized = true;
80+
return self.stop_and_reverse;
81+
}
82+
83+
let new_sar = self.stop_and_reverse
84+
+ self.accelerator_factor * (self.extreme_point - self.stop_and_reverse);
85+
86+
if self.is_uptrend && low < new_sar {
87+
self.is_uptrend = false;
88+
self.stop_and_reverse = self.extreme_point;
89+
self.extreme_point = low;
90+
self.accelerator_factor = 0.02;
91+
return self.stop_and_reverse;
92+
}
93+
94+
if !self.is_uptrend && high > new_sar {
95+
self.is_uptrend = true;
96+
self.stop_and_reverse = self.extreme_point;
97+
self.extreme_point = high;
98+
self.accelerator_factor = 0.02;
99+
return self.stop_and_reverse;
100+
}
101+
102+
self.stop_and_reverse = new_sar;
103+
let old_ep = self.extreme_point;
104+
105+
self.extreme_point = if self.is_uptrend {
106+
self.extreme_point.max(high)
107+
} else {
108+
self.extreme_point.min(low)
109+
};
110+
111+
if old_ep != self.extreme_point {
112+
self.accelerator_factor = (self.accelerator_factor + self.step_af).min(self.maximum_af);
113+
}
114+
115+
self.stop_and_reverse
116+
}
117+
}
118+
119+
impl Reset for ParabolicStopAndReverse {
120+
fn reset(&mut self) {
121+
self.accelerator_factor = 0.0;
122+
self.initialized = false;
123+
}
124+
}
125+
126+
impl Default for ParabolicStopAndReverse {
127+
fn default() -> Self {
128+
Self {
129+
accelerator_factor: 0.02,
130+
maximum_af: 0.2,
131+
step_af: 0.02,
132+
extreme_point: 0.,
133+
stop_and_reverse: 0.,
134+
is_uptrend: false,
135+
initialized: false,
136+
}
137+
}
138+
}
139+
140+
impl fmt::Display for ParabolicStopAndReverse {
141+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142+
write!(f, "SAR({})", self.stop_and_reverse)
143+
}
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use super::*;
149+
use crate::test_helper::*;
150+
151+
#[test]
152+
fn test_default() {
153+
ParabolicStopAndReverse::default();
154+
}
155+
156+
#[test]
157+
fn test_initialization() {
158+
let mut sar = ParabolicStopAndReverse::default();
159+
let bar = Bar::new().high(15.0).low(5.0);
160+
assert_eq!(sar.next(&bar), 5.0);
161+
assert_eq!(sar.extreme_point, 15.0);
162+
assert!(sar.is_uptrend);
163+
}
164+
165+
#[test]
166+
fn test_trend_up_to_down() {
167+
let mut sar = ParabolicStopAndReverse::new(0.2, 0.02).unwrap();
168+
let bar1 = Bar::new().high(15.0).low(5.0);
169+
sar.next(&bar1);
170+
assert!(sar.is_uptrend);
171+
let bar2 = Bar::new().high(16.0).low(4.0);
172+
let sar_value = sar.next(&bar2);
173+
assert!(!sar.is_uptrend);
174+
assert_eq!(sar_value, 15.0);
175+
}
176+
177+
#[test]
178+
fn test_trend_down_to_up() {
179+
let mut sar = ParabolicStopAndReverse::new(0.2, 0.02).unwrap();
180+
let bar1 = Bar::new().high(15.0).low(5.0); // up by default
181+
let bar2 = Bar::new().high(16.0).low(4.0);
182+
sar.next(&bar1); // up
183+
sar.next(&bar2); // down
184+
assert!(!sar.is_uptrend);
185+
let bar3 = Bar::new().high(15.0).low(5.0);
186+
sar.next(&bar3); // re.up
187+
assert!(sar.is_uptrend);
188+
}
189+
190+
#[test]
191+
fn test_acceleration_update() {
192+
let mut sar = ParabolicStopAndReverse::new(0.2, 0.02).unwrap();
193+
let bar1 = Bar::new().high(15.0).low(5.0);
194+
sar.next(&bar1);
195+
let initial_af = sar.accelerator_factor;
196+
197+
let bar2 = Bar::new().high(18.0).low(12.0);
198+
sar.next(&bar2);
199+
assert!(sar.accelerator_factor > initial_af);
200+
assert!(sar.accelerator_factor <= 0.2);
201+
}
202+
203+
#[test]
204+
fn test_display() {
205+
let sar = ParabolicStopAndReverse::new(0.2, 0.02).unwrap();
206+
assert_eq!(format!("{}", sar), "SAR(0)");
207+
}
208+
209+
#[test]
210+
fn test_invalid_parameters() {
211+
let result = ParabolicStopAndReverse::new(0.0, 0.02);
212+
assert!(result.is_err());
213+
let result = ParabolicStopAndReverse::new(0.1, 0.2);
214+
assert!(result.is_err());
215+
}
216+
}

0 commit comments

Comments
 (0)