1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2talib_1_in_1_out!(RSI, talib_rs::momentum::rsi, timeperiod: usize);
3impl From<usize> for RSI {
4 fn from(p: usize) -> Self {
5 Self::new(p)
6 }
7}
8talib_1_in_3_out!(MACD, talib_rs::momentum::macd, fastperiod: usize, slowperiod: usize, signalperiod: usize);
9talib_1_in_3_out!(MACDEXT, talib_rs::momentum::macd_ext, fastperiod: usize, fastmatype: talib_rs::MaType, slowperiod: usize, slowmatype: talib_rs::MaType, signalperiod: usize, signalmatype: talib_rs::MaType);
10talib_1_in_3_out!(MACDFIX, talib_rs::momentum::macd_fix, signalperiod: usize);
11
12talib_3_in_2_out!(STOCH, talib_rs::momentum::stoch, fastk_period: usize, slowk_period: usize, slowk_matype: talib_rs::MaType, slowd_period: usize, slowd_matype: talib_rs::MaType);
13talib_3_in_2_out!(STOCHF, talib_rs::momentum::stochf, fastk_period: usize, fastd_period: usize, fastd_matype: talib_rs::MaType);
14talib_1_in_2_out!(STOCHRSI, talib_rs::momentum::stochrsi, timeperiod: usize, fastk_period: usize, fastd_period: usize, fastd_matype: talib_rs::MaType);
15
16talib_3_in_1_out!(ADX, talib_rs::momentum::adx, timeperiod: usize);
17impl From<usize> for ADX {
18 fn from(p: usize) -> Self {
19 Self::new(p)
20 }
21}
22talib_3_in_1_out!(ADXR, talib_rs::momentum::adxr, timeperiod: usize);
23impl From<usize> for ADXR {
24 fn from(p: usize) -> Self {
25 Self::new(p)
26 }
27}
28talib_3_in_1_out!(CCI, talib_rs::momentum::cci, timeperiod: usize);
29impl From<usize> for CCI {
30 fn from(p: usize) -> Self {
31 Self::new(p)
32 }
33}
34talib_1_in_1_out!(MOM, talib_rs::momentum::mom, timeperiod: usize);
35impl From<usize> for MOM {
36 fn from(p: usize) -> Self {
37 Self::new(p)
38 }
39}
40talib_1_in_1_out!(ROC, talib_rs::momentum::roc, timeperiod: usize);
41impl From<usize> for ROC {
42 fn from(p: usize) -> Self {
43 Self::new(p)
44 }
45}
46talib_1_in_1_out!(ROCP, talib_rs::momentum::rocp, timeperiod: usize);
47impl From<usize> for ROCP {
48 fn from(p: usize) -> Self {
49 Self::new(p)
50 }
51}
52talib_1_in_1_out!(ROCR, talib_rs::momentum::rocr, timeperiod: usize);
53impl From<usize> for ROCR {
54 fn from(p: usize) -> Self {
55 Self::new(p)
56 }
57}
58talib_1_in_1_out!(ROCR100, talib_rs::momentum::rocr100, timeperiod: usize);
59impl From<usize> for ROCR100 {
60 fn from(p: usize) -> Self {
61 Self::new(p)
62 }
63}
64talib_3_in_1_out!(WILLR, talib_rs::momentum::willr, timeperiod: usize);
65impl From<usize> for WILLR {
66 fn from(p: usize) -> Self {
67 Self::new(p)
68 }
69}
70talib_1_in_1_out!(APO, talib_rs::momentum::apo, fastperiod: usize, slowperiod: usize, matype: talib_rs::MaType);
71talib_1_in_1_out!(PPO, talib_rs::momentum::ppo, fastperiod: usize, slowperiod: usize, matype: talib_rs::MaType);
72talib_4_in_1_out!(BOP, talib_rs::momentum::bop);
73impl Default for BOP {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78talib_1_in_1_out!(CMO, talib_rs::momentum::cmo, timeperiod: usize);
79impl From<usize> for CMO {
80 fn from(p: usize) -> Self {
81 Self::new(p)
82 }
83}
84talib_2_in_2_out!(AROON, talib_rs::momentum::aroon, timeperiod: usize);
85talib_2_in_1_out!(AROONOSC, talib_rs::momentum::aroon_osc, timeperiod: usize);
86talib_4_in_1_out!(MFI, talib_rs::momentum::mfi, timeperiod: usize);
87talib_1_in_1_out!(TRIX, talib_rs::momentum::trix, timeperiod: usize);
88impl From<usize> for TRIX {
89 fn from(p: usize) -> Self {
90 Self::new(p)
91 }
92}
93talib_3_in_1_out!(ULTOSC, talib_rs::momentum::ultosc, timeperiod1: usize, timeperiod2: usize, timeperiod3: usize);
94talib_3_in_1_out!(DX, talib_rs::momentum::dx, timeperiod: usize);
95impl From<usize> for DX {
96 fn from(p: usize) -> Self {
97 Self::new(p)
98 }
99}
100talib_3_in_1_out!(PLUS_DI, talib_rs::momentum::plus_di, timeperiod: usize);
101impl From<usize> for PLUS_DI {
102 fn from(p: usize) -> Self {
103 Self::new(p)
104 }
105}
106talib_3_in_1_out!(MINUS_DI, talib_rs::momentum::minus_di, timeperiod: usize);
107impl From<usize> for MINUS_DI {
108 fn from(p: usize) -> Self {
109 Self::new(p)
110 }
111}
112talib_2_in_1_out!(PLUS_DM, talib_rs::momentum::plus_dm, timeperiod: usize);
113talib_2_in_1_out!(MINUS_DM, talib_rs::momentum::minus_dm, timeperiod: usize);
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::traits::Next;
119 use proptest::prelude::*;
120
121 proptest! {
122 #[test]
123 fn test_rsi_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
124 let period = 14;
125 let mut rsi = RSI::new(period);
126 let streaming_results: Vec<f64> = input.iter().map(|&x| rsi.next(x)).collect();
127 let batch_results = talib_rs::momentum::rsi(&input, period).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
128
129 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
130 if s.is_nan() {
131 assert!(b.is_nan());
132 } else {
133 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
134 }
135 }
136 }
137
138 #[test]
139 fn test_macd_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
140 let fast = 12;
141 let slow = 26;
142 let signal = 9;
143 let mut macd = MACD::new(fast, slow, signal);
144 let streaming_results: Vec<(f64, f64, f64)> = input.iter().map(|&x| macd.next(x)).collect();
145 let (b_macd, b_signal, b_hist) = talib_rs::momentum::macd(&input, fast, slow, signal).unwrap_or_else(|_| {
146 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
147 });
148
149 for (i, (s_macd, s_signal, s_hist)) in streaming_results.into_iter().enumerate() {
150 if s_macd.is_nan() {
151 assert!(b_macd[i].is_nan());
152 } else {
153 approx::assert_relative_eq!(s_macd, b_macd[i], epsilon = 1e-6);
154 }
155 if s_signal.is_nan() {
156 assert!(b_signal[i].is_nan());
157 } else {
158 approx::assert_relative_eq!(s_signal, b_signal[i], epsilon = 1e-6);
159 }
160 if s_hist.is_nan() {
161 assert!(b_hist[i].is_nan());
162 } else {
163 approx::assert_relative_eq!(s_hist, b_hist[i], epsilon = 1e-6);
164 }
165 }
166 }
167
168 #[test]
169 fn test_stoch_parity(
170 h in prop::collection::vec(1.0..100.0, 1..100),
171 l in prop::collection::vec(1.0..100.0, 1..100),
172 c in prop::collection::vec(1.0..100.0, 1..100)
173 ) {
174 let len = h.len().min(l.len()).min(c.len());
175 if len == 0 { return Ok(()); }
176 let mut high = Vec::with_capacity(len);
177 let mut low = Vec::with_capacity(len);
178 let mut close = Vec::with_capacity(len);
179 for i in 0..len {
180 let val_h: f64 = h[i];
181 let val_l: f64 = l[i];
182 let val_c: f64 = c[i];
183 let max: f64 = val_h.max(val_l).max(val_c);
184 let min: f64 = val_h.min(val_l).min(val_c);
185 high.push(max);
186 low.push(min);
187 close.push(val_c);
188 }
189
190 let fastk = 5;
191 let slowk = 3;
192 let slowk_ma = talib_rs::MaType::Sma;
193 let slowd = 3;
194 let slowd_ma = talib_rs::MaType::Sma;
195
196 let mut stoch = STOCH::new(fastk, slowk, slowk_ma, slowd, slowd_ma);
197 let streaming_results: Vec<(f64, f64)> = (0..len).map(|i| stoch.next((high[i], low[i], close[i]))).collect();
198 let (b_k, b_d) = talib_rs::momentum::stoch(&high, &low, &close, fastk, slowk, slowk_ma, slowd, slowd_ma).unwrap_or_else(|_| {
199 (vec![f64::NAN; len], vec![f64::NAN; len])
200 });
201
202 for (i, (s_k, s_d)) in streaming_results.into_iter().enumerate() {
203 if s_k.is_nan() {
204 assert!(b_k[i].is_nan());
205 } else {
206 approx::assert_relative_eq!(s_k, b_k[i], epsilon = 1e-6);
207 }
208 if s_d.is_nan() {
209 assert!(b_d[i].is_nan());
210 } else {
211 approx::assert_relative_eq!(s_d, b_d[i], epsilon = 1e-6);
212 }
213 }
214 }
215
216 #[test]
217 fn test_adx_parity(
218 h in prop::collection::vec(1.0..100.0, 1..100),
219 l in prop::collection::vec(1.0..100.0, 1..100),
220 c in prop::collection::vec(1.0..100.0, 1..100)
221 ) {
222 let len = h.len().min(l.len()).min(c.len());
223 if len == 0 { return Ok(()); }
224 let mut high = Vec::with_capacity(len);
225 let mut low = Vec::with_capacity(len);
226 let mut close = Vec::with_capacity(len);
227 for i in 0..len {
228 let val_h: f64 = h[i];
229 let val_l: f64 = l[i];
230 let val_c: f64 = c[i];
231 let max: f64 = val_h.max(val_l).max(val_c);
232 let min: f64 = val_h.min(val_l).min(val_c);
233 high.push(max);
234 low.push(min);
235 close.push(val_c);
236 }
237
238 let period = 14;
239 let mut adx = ADX::new(period);
240 let streaming_results: Vec<f64> = (0..len).map(|i| adx.next((high[i], low[i], close[i]))).collect();
241 let batch_results = talib_rs::momentum::adx(&high, &low, &close, period).unwrap_or_else(|_| vec![f64::NAN; len]);
242
243 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
244 if s.is_nan() {
245 assert!(b.is_nan());
246 } else {
247 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
248 }
249 }
250 }
251 }
252}
253
254pub const RSI_METADATA: IndicatorMetadata = IndicatorMetadata {
255 name: "Relative Strength Index (RSI)",
256 description: "A momentum oscillator that measures the speed and change of price movements.",
257 usage: "Use to identify overbought (>70) and oversold (<30) conditions. RSI divergences against price often signal impending trend reversals.",
258 keywords: &["momentum", "oscillator", "overbought", "oversold", "classic"],
259 ehlers_summary: "Developed by J. Welles Wilder in New Concepts in Technical Trading Systems (1978), the RSI compares the magnitude of recent gains to recent losses to determine overbought and oversold conditions of an asset. It remains the most widely used momentum oscillator in modern technical analysis.",
260 params: &[ParamDef { name: "timeperiod", default: "14", description: "Lookback period" }],
261 formula_source: "https://www.investopedia.com/terms/r/rsi.asp",
262 formula_latex: r#"
263\[
264RS = \frac{Average Gain}{Average Loss} \\ RSI = 100 - \frac{100}{1 + RS}
265\]
266"#,
267 gold_standard_file: "rsi.json",
268 category: "Classic",
269};
270
271pub const MACD_METADATA: IndicatorMetadata = IndicatorMetadata {
272 name: "Moving Average Convergence Divergence (MACD)",
273 description: "A trend-following momentum indicator that shows the relationship between two moving averages.",
274 usage: "Use to identify trend direction and momentum. Crossovers of the MACD line and signal line provide entry and exit signals, while the histogram shows the strength of the trend.",
275 keywords: &["trend", "momentum", "moving-average", "classic"],
276 ehlers_summary: "Gerald Appel developed the MACD in the late 1970s. It is calculated by subtracting the 26-period EMA from the 12-period EMA. A nine-day EMA of the MACD, called the 'signal line,' is then plotted on top of the MACD line, which can function as a trigger for buy and sell signals. — Investopedia",
277 params: &[
278 ParamDef { name: "fastperiod", default: "12", description: "Fast EMA period" },
279 ParamDef { name: "slowperiod", default: "26", description: "Slow EMA period" },
280 ParamDef { name: "signalperiod", default: "9", description: "Signal EMA period" },
281 ],
282 formula_source: "https://www.investopedia.com/terms/m/macd.asp",
283 formula_latex: r#"
284\[
285MACD = EMA(12) - EMA(26) \\ Signal = EMA(MACD, 9)
286\]
287"#,
288 gold_standard_file: "macd.json",
289 category: "Classic",
290};
291
292pub const STOCH_METADATA: IndicatorMetadata = IndicatorMetadata {
293 name: "Stochastic Oscillator",
294 description: "A momentum indicator comparing a particular closing price of a security to a range of its prices over a certain period of time.",
295 usage: "Use to identify trend reversals by looking for crossovers and overbought/oversold levels. The %K and %D lines indicate when the momentum is shifting relative to the recent price range.",
296 keywords: &["momentum", "oscillator", "overbought", "oversold", "classic"],
297 ehlers_summary: "George Lane developed the Stochastic Oscillator in the 1950s. It is based on the observation that in an uptrend, prices tend to close near their high, and in a downtrend, they tend to close near their low. The sensitivity of the oscillator to market movements is reducible by adjusting the time period or by taking a moving average of the result. — StockCharts ChartSchool",
298 params: &[
299 ParamDef { name: "fastk_period", default: "5", description: "Fast %K period" },
300 ParamDef { name: "slowk_period", default: "3", description: "Slow %K period" },
301 ParamDef { name: "slowd_period", default: "3", description: "Slow %D period" },
302 ],
303 formula_source: "https://www.investopedia.com/terms/s/stochasticoscillator.asp",
304 formula_latex: r#"
305\[
306\%K = 100 \times \frac{C - L14}{H14 - L14} \\ \%D = 3\text{-period SMA of } \%K
307\]
308"#,
309 gold_standard_file: "stoch.json",
310 category: "Classic",
311};
312
313pub const ADX_METADATA: IndicatorMetadata = IndicatorMetadata {
314 name: "Average Directional Index (ADX)",
315 description: "An indicator used to quantify trend strength without regard to trend direction.",
316 usage: "Use to determine if the market is trending or ranging. ADX values above 25 indicate a strong trend, while values below 20 indicate a weak or non-trending market.",
317 keywords: &["trend", "volatility", "classic", "wilder"],
318 ehlers_summary: "Developed by J. Welles Wilder, the ADX is derived from two other indicators, also developed by Wilder: the Positive Directional Indicator (+DI) and the Negative Directional Indicator (-DI). While +DI and -DI indicate trend direction, ADX measures the strength of that trend. — StockCharts ChartSchool",
319 params: &[ParamDef { name: "timeperiod", default: "14", description: "Lookback period" }],
320 formula_source: "https://www.investopedia.com/terms/a/adx.asp",
321 formula_latex: r#"
322\[
323ADX = 100 \times \frac{\text{EMA}(|(+DI) - (-DI)| / |(+DI) + (-DI)|, n)}{n}
324\]
325"#,
326 gold_standard_file: "adx.json",
327 category: "Classic",
328};
329
330pub const CCI_METADATA: IndicatorMetadata = IndicatorMetadata {
331 name: "Commodity Channel Index (CCI)",
332 description: "A versatile indicator that can be used to identify a new trend or warn of extreme conditions.",
333 usage: "Use to identify cyclical turns in commodities or stocks. Readings above +100 imply a strong uptrend, while readings below -100 imply a strong downtrend.",
334 keywords: &["momentum", "oscillator", "classic", "mean-reversion"],
335 ehlers_summary: "Developed by Donald Lambert in 1980, the CCI measures the current price level relative to an average price level over a given period. CCI is relatively high when prices are far above their average and relatively low when prices are far below their average. — StockCharts ChartSchool",
336 params: &[ParamDef { name: "timeperiod", default: "14", description: "Lookback period" }],
337 formula_source: "https://www.investopedia.com/terms/c/commoditychannelindex.asp",
338 formula_latex: r#"
339\[
340CCI = \frac{Price - SMA}{0.015 \times \text{Mean Deviation}}
341\]
342"#,
343 gold_standard_file: "cci.json",
344 category: "Classic",
345};
346
347pub const WILLR_METADATA: IndicatorMetadata = IndicatorMetadata {
348 name: "Williams %R",
349 description: "A momentum indicator that measures overbought and oversold levels, similar to a stochastic oscillator.",
350 usage: "Use to identify entry and exit points in the market. Readings from 0 to -20 are considered overbought, while readings from -80 to -100 are considered oversold.",
351 keywords: &["momentum", "oscillator", "overbought", "oversold", "classic"],
352 ehlers_summary: "Developed by Larry Williams, %R compares the closing price of a stock to the high-low range over a specific period, typically 14 days. It is used to determine when a stock might be overbought or oversold and to identify potential trend reversals. — StockCharts ChartSchool",
353 params: &[ParamDef { name: "timeperiod", default: "14", description: "Lookback period" }],
354 formula_source: "https://www.investopedia.com/terms/w/williamsr.asp",
355 formula_latex: r#"
356\[
357\%R = \frac{\text{Highest High} - \text{Close}}{\text{Highest High} - \text{Lowest Low}} \times -100
358\]
359"#,
360 gold_standard_file: "willr.json",
361 category: "Classic",
362};
363
364pub const MFI_METADATA: IndicatorMetadata = IndicatorMetadata {
365 name: "Money Flow Index (MFI)",
366 description: "A technical oscillator that uses price and volume data for identifying overbought or oversold signals.",
367 usage: "Use as a volume-weighted RSI. Divergences between MFI and price can signal potential reversals, especially when the MFI is in extreme territory (>80 or <20).",
368 keywords: &["momentum", "volume", "oscillator", "classic"],
369 ehlers_summary: "The Money Flow Index (MFI) is a momentum indicator that measures the inflow and outflow of money into an asset over a specific period of time. It is related to the RSI but incorporates volume, whereas the RSI only considers price. — Investopedia",
370 params: &[ParamDef { name: "timeperiod", default: "14", description: "Lookback period" }],
371 formula_source: "https://www.investopedia.com/terms/m/mfi.asp",
372 formula_latex: r#"
373\[
374\text{Money Ratio} = \frac{\text{Positive Money Flow}}{\text{Negative Money Flow}} \\ MFI = 100 - \frac{100}{1 + \text{Money Ratio}}
375\]
376"#,
377 gold_standard_file: "mfi.json",
378 category: "Classic",
379};
380
381pub const AROON_METADATA: IndicatorMetadata = IndicatorMetadata {
382 name: "Aroon Indicator",
383 description: "An indicator system that identifies when a new trend is beginning and the strength of the trend.",
384 usage: "Use to identify when a security is trending and when it is in a range-bound period. Aroon Up crossing above Aroon Down signals the start of a new uptrend.",
385 keywords: &["trend", "classic", "breakout"],
386 ehlers_summary: "Developed by Tushar Chande in 1995, the Aroon indicator focuses on the time between highs and the time between lows over a given period. The idea is that strong uptrends will regularly see new highs, and strong downtrends will regularly see new lows. — StockCharts ChartSchool",
387 params: &[ParamDef { name: "timeperiod", default: "25", description: "Lookback period" }],
388 formula_source: "https://www.investopedia.com/terms/a/aroon.asp",
389 formula_latex: r#"
390\[
391\text{Aroon Up} = \frac{n - \text{Periods since n-period High}}{n} \times 100
392\]
393"#,
394 gold_standard_file: "aroon.json",
395 category: "Classic",
396};
397
398pub const ULTOSC_METADATA: IndicatorMetadata = IndicatorMetadata {
399 name: "Ultimate Oscillator",
400 description: "A momentum oscillator designed to capture momentum across three different timeframes.",
401 usage: "Use to avoid the pitfalls of oscillators that are limited to a single timeframe. Buy signals are generated when there is bullish divergence between price and the indicator.",
402 keywords: &["momentum", "oscillator", "classic", "multi-timeframe"],
403 ehlers_summary: "Developed by Larry Williams in 1976, the Ultimate Oscillator uses weighted averages of three different timeframes to reduce the volatility and false signals common in other oscillators. It remains a staple for identifying divergence across short, medium, and long-term price action. — StockCharts ChartSchool",
404 params: &[
405 ParamDef { name: "timeperiod1", default: "7", description: "Short period" },
406 ParamDef { name: "timeperiod2", default: "14", description: "Medium period" },
407 ParamDef { name: "timeperiod3", default: "28", description: "Long period" },
408 ],
409 formula_source: "https://www.investopedia.com/terms/u/ultimateoscillator.asp",
410 formula_latex: r#"
411\[
412\text{BP} = \text{Close} - \min(\text{Low}, \text{PrevClose}) \\ \text{TR} = \max(\text{High}, \text{PrevClose}) - \min(\text{Low}, \text{PrevClose})
413\]
414"#,
415 gold_standard_file: "ultosc.json",
416 category: "Classic",
417};
418
419pub const TRIX_METADATA: IndicatorMetadata = IndicatorMetadata {
420 name: "TRIX",
421 description: "A momentum oscillator that shows the percent rate of change of a triple exponentially smoothed moving average.",
422 usage: "Use to filter out market noise and identify trend reversals. TRIX crossings of the zero line or a signal line can provide trade entries.",
423 keywords: &["momentum", "oscillator", "smoothing", "classic"],
424 ehlers_summary: "Developed by Jack Hutson in the early 1980s, TRIX is a powerful momentum oscillator that effectively filters out minor price fluctuations. By triple-smoothing an EMA, it emphasizes the underlying trend and provides a clear signal when the trend changes direction. — StockCharts ChartSchool",
425 params: &[ParamDef { name: "timeperiod", default: "15", description: "Smoothing period" }],
426 formula_source: "https://www.investopedia.com/terms/t/trix.asp",
427 formula_latex: r#"
428\[
429TRIX = \frac{EMA3_t - EMA3_{t-1}}{EMA3_{t-1}} \times 100
430\]
431"#,
432 gold_standard_file: "trix.json",
433 category: "Classic",
434};
435
436pub const MOM_METADATA: IndicatorMetadata = IndicatorMetadata {
437 name: "Momentum (MOM)",
438 description: "A simple indicator that measures the amount that a security's price has changed over a given span of time.",
439 usage: "Use to measure the velocity of price changes. Positive values indicate an uptrend, while negative values indicate a downtrend.",
440 keywords: &["momentum", "classic", "trend"],
441 ehlers_summary: "Momentum is one of the most basic and powerful concepts in technical analysis. It measures the rate of change of an asset's price, providing a clear indication of trend strength and potential exhaustion before the actual price reversal occurs. — StockCharts ChartSchool",
442 params: &[ParamDef { name: "timeperiod", default: "10", description: "Lookback period" }],
443 formula_source: "https://www.investopedia.com/terms/m/momentum.asp",
444 formula_latex: r#"
445\[
446MOM = Price_t - Price_{t-n}
447\]
448"#,
449 gold_standard_file: "mom.json",
450 category: "Classic",
451};
452
453pub const ROC_METADATA: IndicatorMetadata = IndicatorMetadata {
454 name: "Rate of Change (ROC)",
455 description: "A momentum-based technical indicator that measures the percentage change in price between the current price and the price n periods ago.",
456 usage: "Use to measure the speed at which price is changing. It is often used to identify overbought/oversold conditions and trend reversals.",
457 keywords: &["momentum", "classic", "oscillator"],
458 ehlers_summary: "The Rate of Change (ROC) indicator is a pure momentum oscillator that measures the percentage change in price from one period to the next. It is highly effective at identifying the velocity of a move and anticipating when that velocity is slowing down. — StockCharts ChartSchool",
459 params: &[ParamDef { name: "timeperiod", default: "10", description: "Lookback period" }],
460 formula_source: "https://www.investopedia.com/terms/r/rateofchange.asp",
461 formula_latex: r#"
462\[
463ROC = \frac{Price_t - Price_{t-n}}{Price_{t-n}} \times 100
464\]
465"#,
466 gold_standard_file: "roc.json",
467 category: "Classic",
468};
469
470pub const CMO_METADATA: IndicatorMetadata = IndicatorMetadata {
471 name: "Chande Momentum Oscillator (CMO)",
472 description: "An advanced momentum oscillator developed by Tushar Chande that measures the difference between up and down days.",
473 usage: "Use to identify extreme overbought and oversold conditions. CMO is more sensitive to price action than RSI as it uses unsmoothed data in its internal calculations.",
474 keywords: &["momentum", "oscillator", "classic", "overbought", "oversold"],
475 ehlers_summary: "Developed by Tushar Chande in 1994, the CMO is similar to the RSI but uses the net sum of up and down moves in both the numerator and denominator. This makes it more sensitive to price movements and useful for identifying short-term overextensions in the market. — The New Technical Trader",
476 params: &[ParamDef { name: "timeperiod", default: "14", description: "Lookback period" }],
477 formula_source: "https://www.investopedia.com/terms/c/chandemomentumoscillator.asp",
478 formula_latex: r#"
479\[
480CMO = 100 \times \frac{S_u - S_d}{S_u + S_d}
481\]
482"#,
483 gold_standard_file: "cmo.json",
484 category: "Classic",
485};
486
487pub const APO_METADATA: IndicatorMetadata = IndicatorMetadata {
488 name: "Absolute Price Oscillator (APO)",
489 description: "Shows the absolute difference between two moving averages of different periods.",
490 usage: "Use to identify trend crossovers and momentum. It is essentially a MACD without the signal line, showing the raw distance between fast and slow averages.",
491 keywords: &["trend", "momentum", "moving-average", "classic"],
492 ehlers_summary: "The Absolute Price Oscillator (APO) is based on the difference between two exponential moving averages. It is a trend-following indicator that signals a change in direction when the fast EMA crosses the slow EMA, providing a clear visual of trend development. — TA-Lib Documentation",
493 params: &[
494 ParamDef { name: "fastperiod", default: "12", description: "Fast period" },
495 ParamDef { name: "slowperiod", default: "26", description: "Slow period" },
496 ],
497 formula_source: "https://www.tradingview.com/support/solutions/43000501826-absolute-price-oscillator-apo/",
498 formula_latex: r#"
499\[
500APO = EMA(fast) - EMA(slow)
501\]
502"#,
503 gold_standard_file: "apo.json",
504 category: "Classic",
505};
506
507pub const PPO_METADATA: IndicatorMetadata = IndicatorMetadata {
508 name: "Percentage Price Oscillator (PPO)",
509 description: "A momentum oscillator that measures the difference between two moving averages as a percentage of the larger moving average.",
510 usage: "Use to compare trend momentum across different securities with varying price levels. PPO is the percentage version of MACD.",
511 keywords: &["trend", "momentum", "moving-average", "classic", "normalization"],
512 ehlers_summary: "The Percentage Price Oscillator (PPO) is identical to the MACD, except that it measures the difference between two moving averages as a percentage. This allows for comparison across different stocks regardless of their price, making it a superior tool for relative strength analysis. — StockCharts ChartSchool",
513 params: &[
514 ParamDef { name: "fastperiod", default: "12", description: "Fast period" },
515 ParamDef { name: "slowperiod", default: "26", description: "Slow period" },
516 ],
517 formula_source: "https://www.investopedia.com/terms/p/ppo.asp",
518 formula_latex: r#"
519\[
520PPO = \frac{EMA(12) - EMA(26)}{EMA(26)} \times 100
521\]
522"#,
523 gold_standard_file: "ppo.json",
524 category: "Classic",
525};