1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2pub use crate::indicators::incremental::bbands::BBANDS;
3pub use crate::indicators::incremental::dema::DEMA;
4impl From<usize> for DEMA {
5 fn from(p: usize) -> Self {
6 Self::new(p)
7 }
8}
9pub use crate::indicators::incremental::overlap_ta::{KAMA, MIDPOINT, MIDPRICE, T3, TRIMA};
10impl From<usize> for TRIMA {
11 fn from(p: usize) -> Self {
12 Self::new(p)
13 }
14}
15impl From<usize> for KAMA {
16 fn from(p: usize) -> Self {
17 Self::new(p)
18 }
19}
20impl From<usize> for MIDPOINT {
21 fn from(p: usize) -> Self {
22 Self::new(p)
23 }
24}
25impl From<usize> for MIDPRICE {
26 fn from(p: usize) -> Self {
27 Self::new(p)
28 }
29}
30pub use crate::indicators::mama::MAMA;
31pub use crate::indicators::incremental::sar::{SAR, SAREXT};
32pub use crate::indicators::incremental::mavp::MAVP;
33pub use crate::indicators::incremental::hilbert_ta::HT_TRENDLINE;
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use crate::traits::Next;
39 use proptest::prelude::*;
40
41 proptest! {
42 #[test]
43 fn test_dema_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
44 let period = 10;
45 let mut dema = DEMA::new(period);
46 let streaming_results: Vec<f64> = input.iter().map(|&x| dema.next(x)).collect();
47 let batch_results = talib_rs::overlap::dema(&input, period).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
48
49 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
50 if s.is_nan() {
51 assert!(b.is_nan());
52 } else {
53 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
54 }
55 }
56 }
57
58 #[test]
59 fn test_trima_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
60 let period = 10;
61 let mut trima = TRIMA::new(period);
62 let streaming_results: Vec<f64> = input.iter().map(|&x| trima.next(x)).collect();
63 let batch_results = talib_rs::overlap::trima(&input, period).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
64
65 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
66 if s.is_nan() {
67 assert!(b.is_nan());
68 } else {
69 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
70 }
71 }
72 }
73
74 #[test]
75 fn test_kama_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
76 let period = 10;
77 let mut kama = KAMA::new(period);
78 let streaming_results: Vec<f64> = input.iter().map(|&x| kama.next(x)).collect();
79 let batch_results = talib_rs::overlap::kama(&input, period).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
80
81 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
82 if s.is_nan() {
83 assert!(b.is_nan());
84 } else {
85 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
86 }
87 }
88 }
89
90 #[test]
91 fn test_t3_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
92 let period = 10;
93 let v_factor = 0.7;
94 let mut t3 = T3::new(period, v_factor);
95 let streaming_results: Vec<f64> = input.iter().map(|&x| t3.next(x)).collect();
96 let batch_results = talib_rs::overlap::t3(&input, period, v_factor).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
97
98 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
99 if s.is_nan() {
100 assert!(b.is_nan());
101 } else {
102 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
103 }
104 }
105 }
106
107 #[test]
108 fn test_bbands_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
109 let period = 10;
110 let nbdevup = 2.0;
111 let nbdevdn = 2.0;
112 let matype = talib_rs::MaType::Sma;
113 let mut bbands = BBANDS::new(period, nbdevup, nbdevdn, matype);
114 let streaming_results: Vec<(f64, f64, f64)> = input.iter().map(|&x| bbands.next(x)).collect();
115 let (b_upper, b_middle, b_lower) = talib_rs::overlap::bbands(&input, period, nbdevup, nbdevdn, matype).unwrap_or_else(|_| {
116 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
117 });
118
119 for (i, (s_upper, s_middle, s_lower)) in streaming_results.into_iter().enumerate() {
120 if s_upper.is_nan() {
121 assert!(b_upper[i].is_nan());
122 } else {
123 approx::assert_relative_eq!(s_upper, b_upper[i], epsilon = 1e-6);
124 }
125 if s_middle.is_nan() {
126 assert!(b_middle[i].is_nan());
127 } else {
128 approx::assert_relative_eq!(s_middle, b_middle[i], epsilon = 1e-6);
129 }
130 if s_lower.is_nan() {
131 assert!(b_lower[i].is_nan());
132 } else {
133 approx::assert_relative_eq!(s_lower, b_lower[i], epsilon = 1e-6);
134 }
135 }
136 }
137
138 #[test]
139 fn test_sar_parity(
140 h in prop::collection::vec(10.0..100.0, 1..100),
141 l in prop::collection::vec(10.0..100.0, 1..100)
142 ) {
143 let len = h.len().min(l.len());
144 if len == 0 { return Ok(()); }
145 let mut high = Vec::with_capacity(len);
146 let mut low = Vec::with_capacity(len);
147 for i in 0..len {
148 let v_h: f64 = h[i];
149 let v_l: f64 = l[i];
150 high.push(v_h.max(v_l));
151 low.push(v_h.min(v_l));
152 }
153
154 let accel = 0.02;
155 let max = 0.2;
156 let mut sar = SAR::new(accel, max);
157 let streaming_results: Vec<f64> = (0..len).map(|i| sar.next((high[i], low[i]))).collect();
158 let batch_results = talib_rs::overlap::sar(&high, &low, accel, max).unwrap_or_else(|_| vec![f64::NAN; len]);
159
160 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
161 if s.is_nan() {
162 assert!(b.is_nan());
163 } else {
164 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
165 }
166 }
167 }
168 }
169}
170
171pub const DEMA_METADATA: IndicatorMetadata = IndicatorMetadata {
172 name: "Double Exponential Moving Average (DEMA)",
173 description: "A fast-acting moving average that reduces lag by using two exponential moving averages.",
174 usage: "Use as a replacement for EMA when faster signal generation is required without excessive noise. DEMA reacts more quickly to price changes than a standard EMA.",
175 keywords: &["moving-average", "smoothing", "lag-reduction", "classic"],
176 ehlers_summary: "Developed by Patrick Mulloy in 1994, DEMA provides a less-laggy alternative to traditional moving averages. It is calculated by taking a single EMA and then subtracting it from a double EMA of the same period. This effectively cancels out some of the lag inherent in the EMA calculation. — StockCharts ChartSchool",
177 params: &[ParamDef {
178 name: "timeperiod",
179 default: "30",
180 description: "Smoothing period",
181 }],
182 formula_source: "https://www.investopedia.com/terms/d/double-exponential-moving-average.asp",
183 formula_latex: r#"
184\[
185DEMA = 2 \times EMA - EMA(EMA)
186\]
187"#,
188 gold_standard_file: "dema.json",
189 category: "Classic",
190};
191
192pub const TRIMA_METADATA: IndicatorMetadata = IndicatorMetadata {
193 name: "Triangular Moving Average (TRIMA)",
194 description: "A double-smoothed simple moving average that gives more weight to the middle of the lookback period.",
195 usage: "Use for extremely smooth trend identification. TRIMA is significantly smoother than a standard SMA but introduces more lag; it is ideal for identifying long-term cycles.",
196 keywords: &["moving-average", "smoothing", "classic"],
197 ehlers_summary: "The Triangular Moving Average is an SMA of an SMA. For a period N, it averages the values over N/2 periods twice. This results in a weight distribution that is triangular, peaking at the center of the window, making it very effective at filtering out high-frequency noise. — StockCharts ChartSchool",
198 params: &[ParamDef {
199 name: "timeperiod",
200 default: "30",
201 description: "Smoothing period",
202 }],
203 formula_source: "https://www.tradingview.com/support/solutions/43000591273-triangular-moving-average-tma/",
204 formula_latex: r#"
205\[
206TRIMA = SMA(SMA(Price, n/2), n/2)
207\]
208"#,
209 gold_standard_file: "trima.json",
210 category: "Classic",
211};
212
213pub const T3_METADATA: IndicatorMetadata = IndicatorMetadata {
214 name: "Tilson T3 Moving Average",
215 description: "A smooth, low-lag moving average that uses multiple exponential smoothing.",
216 usage: "Use for trend following in noisy markets. T3 offers a superior balance between lag reduction and smoothness compared to DEMA or TEMA.",
217 keywords: &["moving-average", "smoothing", "lag-reduction", "classic"],
218 ehlers_summary: "Developed by Tim Tilson in 1998, the T3 moving average uses a 'v-factor' (volume factor) to control how much the indicator reacts to price changes. It is essentially a sextuple EMA smoothing process that provides a very smooth curve with remarkably little lag. — Technical Analysis of Stocks & Commodities",
219 params: &[
220 ParamDef {
221 name: "timeperiod",
222 default: "5",
223 description: "Smoothing period",
224 },
225 ParamDef {
226 name: "v_factor",
227 default: "0.7",
228 description: "Volume factor (0.0 to 1.0)",
229 },
230 ],
231 formula_source: "https://www.tradingview.com/script/667W2a8n-T3-Moving-Average/",
232 formula_latex: r#"
233\[
234e1 = EMA(Price, n) \\ e2 = EMA(e1, n) \\ \dots \\ e6 = EMA(e5, n) \\ T3 = c1 \times e6 + c2 \times e5 + c3 \times e4 + c4 \times e3
235\]
236"#,
237 gold_standard_file: "t3.json",
238 category: "Classic",
239};
240
241pub const BBANDS_METADATA: IndicatorMetadata = IndicatorMetadata {
242 name: "Bollinger Bands",
243 description: "A volatility indicator consisting of a middle SMA and two outer bands based on standard deviation.",
244 usage: "Use to identify overbought/oversold levels and volatility breakouts. Prices near the upper band suggest overbought conditions, while prices near the lower band suggest oversold conditions. Narrowing bands (The Squeeze) often precede large price moves.",
245 keywords: &["volatility", "trend", "classic", "bands"],
246 ehlers_summary: "Developed by John Bollinger in the 1980s, Bollinger Bands adapt to volatility by using standard deviation. The middle band is typically a 20-period SMA, and the outer bands are set 2 standard deviations away. This ensures that 95% of price action typically stays within the bands, making escapes highly significant. — BollingerOnBollingerBands.com",
247 params: &[
248 ParamDef {
249 name: "timeperiod",
250 default: "20",
251 description: "SMA period",
252 },
253 ParamDef {
254 name: "nbdevup",
255 default: "2.0",
256 description: "Upper deviation multiplier",
257 },
258 ParamDef {
259 name: "nbdevdn",
260 default: "2.0",
261 description: "Lower deviation multiplier",
262 },
263 ],
264 formula_source: "https://www.investopedia.com/terms/b/bollingerbands.asp",
265 formula_latex: r#"
266\[
267Middle = SMA(n) \\ Upper = Middle + (k \times \sigma) \\ Lower = Middle - (k \times \sigma)
268\]
269"#,
270 gold_standard_file: "bbands.json",
271 category: "Classic",
272};
273
274pub const SAR_METADATA: IndicatorMetadata = IndicatorMetadata {
275 name: "Parabolic SAR",
276 description: "A trend-following indicator used to determine price direction and potential reversals.",
277 usage: "Use for setting trailing stop losses and identifying trend reversals. Dots below price indicate an uptrend, while dots above price indicate a downtrend.",
278 keywords: &["trend", "classic", "stop-loss", "wilder"],
279 ehlers_summary: "Developed by J. Welles Wilder, the Parabolic Stop and Reverse (SAR) uses an acceleration factor that increases as the trend persists. This 'parabolic' nature allows the indicator to stay close to price action and provide timely exit signals when a trend exhausts. — StockCharts ChartSchool",
280 params: &[
281 ParamDef {
282 name: "acceleration",
283 default: "0.02",
284 description: "Acceleration factor",
285 },
286 ParamDef {
287 name: "maximum",
288 default: "0.2",
289 description: "Maximum acceleration",
290 },
291 ],
292 formula_source: "https://www.investopedia.com/terms/p/parabolicindicator.asp",
293 formula_latex: r#"
294\[
295SAR_{t+1} = SAR_t + AF \times (EP - SAR_t)
296\]
297"#,
298 gold_standard_file: "sar.json",
299 category: "Classic",
300};
301
302pub use crate::indicators::mama::MAMA_METADATA;