1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2pub use crate::indicators::incremental::macd::MACD;
3pub use crate::indicators::incremental::rsi::RSI;
4impl From<usize> for RSI {
5 fn from(p: usize) -> Self {
6 Self::new(p)
7 }
8}
9pub use crate::indicators::incremental::macd_ext::{MACDEXT, MACDFIX};
10
11pub use crate::indicators::incremental::stoch::{STOCH, STOCHF, STOCHRSI};
12pub use crate::indicators::incremental::dmi::{ADX, ADXR, DX, MINUS_DI, PLUS_DI};
13impl From<usize> for ADX {
14 fn from(p: usize) -> Self {
15 Self::new(p)
16 }
17}
18impl From<usize> for ADXR {
19 fn from(p: usize) -> Self {
20 Self::new(p)
21 }
22}
23pub use crate::indicators::incremental::cci::CCI;
24impl From<usize> for CCI {
25 fn from(p: usize) -> Self {
26 Self::new(p)
27 }
28}
29pub use crate::indicators::incremental::mom::{MOM, ROC, ROCP, ROCR, ROCR100};
30impl From<usize> for MOM {
31 fn from(p: usize) -> Self {
32 Self::new(p)
33 }
34}
35impl From<usize> for ROC {
36 fn from(p: usize) -> Self {
37 Self::new(p)
38 }
39}
40impl From<usize> for ROCP {
41 fn from(p: usize) -> Self {
42 Self::new(p)
43 }
44}
45impl From<usize> for ROCR {
46 fn from(p: usize) -> Self {
47 Self::new(p)
48 }
49}
50impl From<usize> for ROCR100 {
51 fn from(p: usize) -> Self {
52 Self::new(p)
53 }
54}
55pub use crate::indicators::incremental::willr::WILLR;
56impl From<usize> for WILLR {
57 fn from(p: usize) -> Self {
58 Self::new(p)
59 }
60}
61pub use crate::indicators::incremental::apo::{APO, PPO};
62pub use crate::indicators::incremental::simple::BOP;
63pub use crate::indicators::incremental::cmo::CMO;
64impl From<usize> for CMO {
65 fn from(p: usize) -> Self {
66 Self::new(p)
67 }
68}
69pub use crate::indicators::incremental::aroon::{AROON, AROONOSC};
70impl From<usize> for AROON {
71 fn from(p: usize) -> Self {
72 Self::new(p)
73 }
74}
75impl From<usize> for AROONOSC {
76 fn from(p: usize) -> Self {
77 Self::new(p)
78 }
79}
80pub use crate::indicators::incremental::simple::MFI;
81impl From<usize> for MFI {
82 fn from(p: usize) -> Self {
83 Self::new(p)
84 }
85}
86pub use crate::indicators::incremental::trix::TRIX;
87impl From<usize> for TRIX {
88 fn from(p: usize) -> Self {
89 Self::new(p)
90 }
91}
92pub use crate::indicators::incremental::ultosc::ULTOSC;
93impl From<usize> for DX {
94 fn from(p: usize) -> Self {
95 Self::new(p)
96 }
97}
98impl From<usize> for PLUS_DI {
99 fn from(p: usize) -> Self {
100 Self::new(p)
101 }
102}
103impl From<usize> for MINUS_DI {
104 fn from(p: usize) -> Self {
105 Self::new(p)
106 }
107}
108pub use crate::indicators::incremental::dm::{MINUS_DM, PLUS_DM};
109impl From<usize> for PLUS_DM {
110 fn from(p: usize) -> Self {
111 Self::new(p)
112 }
113}
114impl From<usize> for MINUS_DM {
115 fn from(p: usize) -> Self {
116 Self::new(p)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::traits::Next;
124 use proptest::prelude::*;
125
126 proptest! {
127 #[test]
128 fn test_rsi_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
129 let period = 14;
130 let mut rsi = RSI::new(period);
131 let streaming_results: Vec<f64> = input.iter().map(|&x| rsi.next(x)).collect();
132 let batch_results = talib_rs::momentum::rsi(&input, period).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
133
134 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
135 if s.is_nan() {
136 assert!(b.is_nan());
137 } else {
138 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
139 }
140 }
141 }
142
143 #[test]
144 fn test_macd_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
145 let fast = 12;
146 let slow = 26;
147 let signal = 9;
148 let mut macd = MACD::new(fast, slow, signal);
149 let streaming_results: Vec<(f64, f64, f64)> = input.iter().map(|&x| macd.next(x)).collect();
150 let (b_macd, b_signal, b_hist) = talib_rs::momentum::macd(&input, fast, slow, signal).unwrap_or_else(|_| {
151 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
152 });
153
154 for (i, (s_macd, s_signal, s_hist)) in streaming_results.into_iter().enumerate() {
155 if s_macd.is_nan() {
156 assert!(b_macd[i].is_nan());
157 } else {
158 approx::assert_relative_eq!(s_macd, b_macd[i], epsilon = 1e-6);
159 }
160 if s_signal.is_nan() {
161 assert!(b_signal[i].is_nan());
162 } else {
163 approx::assert_relative_eq!(s_signal, b_signal[i], epsilon = 1e-6);
164 }
165 if s_hist.is_nan() {
166 assert!(b_hist[i].is_nan());
167 } else {
168 approx::assert_relative_eq!(s_hist, b_hist[i], epsilon = 1e-6);
169 }
170 }
171 }
172
173 #[test]
174 fn test_stoch_parity(
175 h in prop::collection::vec(1.0..100.0, 1..100),
176 l in prop::collection::vec(1.0..100.0, 1..100),
177 c in prop::collection::vec(1.0..100.0, 1..100)
178 ) {
179 let len = h.len().min(l.len()).min(c.len());
180 if len == 0 { return Ok(()); }
181 let mut high = Vec::with_capacity(len);
182 let mut low = Vec::with_capacity(len);
183 let mut close = Vec::with_capacity(len);
184 for i in 0..len {
185 let val_h: f64 = h[i];
186 let val_l: f64 = l[i];
187 let val_c: f64 = c[i];
188 let max: f64 = val_h.max(val_l).max(val_c);
189 let min: f64 = val_h.min(val_l).min(val_c);
190 high.push(max);
191 low.push(min);
192 close.push(val_c);
193 }
194
195 let fastk = 5;
196 let slowk = 3;
197 let slowk_ma = talib_rs::MaType::Sma;
198 let slowd = 3;
199 let slowd_ma = talib_rs::MaType::Sma;
200
201 let mut stoch = STOCH::new(fastk, slowk, slowk_ma, slowd, slowd_ma);
202 let streaming_results: Vec<(f64, f64)> = (0..len).map(|i| stoch.next((high[i], low[i], close[i]))).collect();
203 let (b_k, b_d) = talib_rs::momentum::stoch(&high, &low, &close, fastk, slowk, slowk_ma, slowd, slowd_ma).unwrap_or_else(|_| {
204 (vec![f64::NAN; len], vec![f64::NAN; len])
205 });
206
207 for (i, (s_k, s_d)) in streaming_results.into_iter().enumerate() {
208 if s_k.is_nan() {
209 assert!(b_k[i].is_nan());
210 } else {
211 approx::assert_relative_eq!(s_k, b_k[i], epsilon = 1e-6);
212 }
213 if s_d.is_nan() {
214 assert!(b_d[i].is_nan());
215 } else {
216 approx::assert_relative_eq!(s_d, b_d[i], epsilon = 1e-6);
217 }
218 }
219 }
220
221 #[test]
222 fn test_adx_parity(
223 h in prop::collection::vec(1.0..100.0, 1..100),
224 l in prop::collection::vec(1.0..100.0, 1..100),
225 c in prop::collection::vec(1.0..100.0, 1..100)
226 ) {
227 let len = h.len().min(l.len()).min(c.len());
228 if len == 0 { return Ok(()); }
229 let mut high = Vec::with_capacity(len);
230 let mut low = Vec::with_capacity(len);
231 let mut close = Vec::with_capacity(len);
232 for i in 0..len {
233 let val_h: f64 = h[i];
234 let val_l: f64 = l[i];
235 let val_c: f64 = c[i];
236 let max: f64 = val_h.max(val_l).max(val_c);
237 let min: f64 = val_h.min(val_l).min(val_c);
238 high.push(max);
239 low.push(min);
240 close.push(val_c);
241 }
242
243 let period = 14;
244 let mut adx = ADX::new(period);
245 let streaming_results: Vec<f64> = (0..len).map(|i| adx.next((high[i], low[i], close[i]))).collect();
246 let batch_results = talib_rs::momentum::adx(&high, &low, &close, period).unwrap_or_else(|_| vec![f64::NAN; len]);
247
248 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
249 if s.is_nan() {
250 assert!(b.is_nan());
251 } else {
252 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
253 }
254 }
255 }
256 }
257}
258
259pub const RSI_METADATA: IndicatorMetadata = IndicatorMetadata {
260 name: "Relative Strength Index (RSI)",
261 description: "A momentum oscillator that measures the speed and change of price movements.",
262 usage: "Use to identify overbought (>70) and oversold (<30) conditions. RSI divergences against price often signal impending trend reversals.",
263 keywords: &[
264 "momentum",
265 "oscillator",
266 "overbought",
267 "oversold",
268 "classic",
269 ],
270 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.",
271 params: &[ParamDef {
272 name: "timeperiod",
273 default: "14",
274 description: "Lookback period",
275 }],
276 formula_source: "https://www.investopedia.com/terms/r/rsi.asp",
277 formula_latex: r#"
278\[
279RS = \frac{Average Gain}{Average Loss} \\ RSI = 100 - \frac{100}{1 + RS}
280\]
281"#,
282 gold_standard_file: "rsi.json",
283 category: "Classic",
284};
285
286pub const MACD_METADATA: IndicatorMetadata = IndicatorMetadata {
287 name: "Moving Average Convergence Divergence (MACD)",
288 description: "A trend-following momentum indicator that shows the relationship between two moving averages.",
289 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.",
290 keywords: &["trend", "momentum", "moving-average", "classic"],
291 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",
292 params: &[
293 ParamDef {
294 name: "fastperiod",
295 default: "12",
296 description: "Fast EMA period",
297 },
298 ParamDef {
299 name: "slowperiod",
300 default: "26",
301 description: "Slow EMA period",
302 },
303 ParamDef {
304 name: "signalperiod",
305 default: "9",
306 description: "Signal EMA period",
307 },
308 ],
309 formula_source: "https://www.investopedia.com/terms/m/macd.asp",
310 formula_latex: r#"
311\[
312MACD = EMA(12) - EMA(26) \\ Signal = EMA(MACD, 9)
313\]
314"#,
315 gold_standard_file: "macd.json",
316 category: "Classic",
317};
318
319pub const STOCH_METADATA: IndicatorMetadata = IndicatorMetadata {
320 name: "Stochastic Oscillator",
321 description: "A momentum indicator comparing a particular closing price of a security to a range of its prices over a certain period of time.",
322 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.",
323 keywords: &[
324 "momentum",
325 "oscillator",
326 "overbought",
327 "oversold",
328 "classic",
329 ],
330 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",
331 params: &[
332 ParamDef {
333 name: "fastk_period",
334 default: "5",
335 description: "Fast %K period",
336 },
337 ParamDef {
338 name: "slowk_period",
339 default: "3",
340 description: "Slow %K period",
341 },
342 ParamDef {
343 name: "slowd_period",
344 default: "3",
345 description: "Slow %D period",
346 },
347 ],
348 formula_source: "https://www.investopedia.com/terms/s/stochasticoscillator.asp",
349 formula_latex: r#"
350\[
351\%K = 100 \times \frac{C - L14}{H14 - L14} \\ \%D = 3\text{-period SMA of } \%K
352\]
353"#,
354 gold_standard_file: "stoch.json",
355 category: "Classic",
356};
357
358pub const ADX_METADATA: IndicatorMetadata = IndicatorMetadata {
359 name: "Average Directional Index (ADX)",
360 description: "An indicator used to quantify trend strength without regard to trend direction.",
361 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.",
362 keywords: &["trend", "volatility", "classic", "wilder"],
363 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",
364 params: &[ParamDef {
365 name: "timeperiod",
366 default: "14",
367 description: "Lookback period",
368 }],
369 formula_source: "https://www.investopedia.com/terms/a/adx.asp",
370 formula_latex: r#"
371\[
372ADX = 100 \times \frac{\text{EMA}(|(+DI) - (-DI)| / |(+DI) + (-DI)|, n)}{n}
373\]
374"#,
375 gold_standard_file: "adx.json",
376 category: "Classic",
377};
378
379pub const CCI_METADATA: IndicatorMetadata = IndicatorMetadata {
380 name: "Commodity Channel Index (CCI)",
381 description: "A versatile indicator that can be used to identify a new trend or warn of extreme conditions.",
382 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.",
383 keywords: &["momentum", "oscillator", "classic", "mean-reversion"],
384 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",
385 params: &[ParamDef {
386 name: "timeperiod",
387 default: "14",
388 description: "Lookback period",
389 }],
390 formula_source: "https://www.investopedia.com/terms/c/commoditychannelindex.asp",
391 formula_latex: r#"
392\[
393CCI = \frac{Price - SMA}{0.015 \times \text{Mean Deviation}}
394\]
395"#,
396 gold_standard_file: "cci.json",
397 category: "Classic",
398};
399
400pub const WILLR_METADATA: IndicatorMetadata = IndicatorMetadata {
401 name: "Williams %R",
402 description: "A momentum indicator that measures overbought and oversold levels, similar to a stochastic oscillator.",
403 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.",
404 keywords: &[
405 "momentum",
406 "oscillator",
407 "overbought",
408 "oversold",
409 "classic",
410 ],
411 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",
412 params: &[ParamDef {
413 name: "timeperiod",
414 default: "14",
415 description: "Lookback period",
416 }],
417 formula_source: "https://www.investopedia.com/terms/w/williamsr.asp",
418 formula_latex: r#"
419\[
420\%R = \frac{\text{Highest High} - \text{Close}}{\text{Highest High} - \text{Lowest Low}} \times -100
421\]
422"#,
423 gold_standard_file: "willr.json",
424 category: "Classic",
425};
426
427pub const MFI_METADATA: IndicatorMetadata = IndicatorMetadata {
428 name: "Money Flow Index (MFI)",
429 description: "A technical oscillator that uses price and volume data for identifying overbought or oversold signals.",
430 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).",
431 keywords: &["momentum", "volume", "oscillator", "classic"],
432 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",
433 params: &[ParamDef {
434 name: "timeperiod",
435 default: "14",
436 description: "Lookback period",
437 }],
438 formula_source: "https://www.investopedia.com/terms/m/mfi.asp",
439 formula_latex: r#"
440\[
441\text{Money Ratio} = \frac{\text{Positive Money Flow}}{\text{Negative Money Flow}} \\ MFI = 100 - \frac{100}{1 + \text{Money Ratio}}
442\]
443"#,
444 gold_standard_file: "mfi.json",
445 category: "Classic",
446};
447
448pub const AROON_METADATA: IndicatorMetadata = IndicatorMetadata {
449 name: "Aroon Indicator",
450 description: "An indicator system that identifies when a new trend is beginning and the strength of the trend.",
451 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.",
452 keywords: &["trend", "classic", "breakout"],
453 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",
454 params: &[ParamDef {
455 name: "timeperiod",
456 default: "25",
457 description: "Lookback period",
458 }],
459 formula_source: "https://www.investopedia.com/terms/a/aroon.asp",
460 formula_latex: r#"
461\[
462\text{Aroon Up} = \frac{n - \text{Periods since n-period High}}{n} \times 100
463\]
464"#,
465 gold_standard_file: "aroon.json",
466 category: "Classic",
467};
468
469pub const ULTOSC_METADATA: IndicatorMetadata = IndicatorMetadata {
470 name: "Ultimate Oscillator",
471 description: "A momentum oscillator designed to capture momentum across three different timeframes.",
472 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.",
473 keywords: &["momentum", "oscillator", "classic", "multi-timeframe"],
474 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",
475 params: &[
476 ParamDef {
477 name: "timeperiod1",
478 default: "7",
479 description: "Short period",
480 },
481 ParamDef {
482 name: "timeperiod2",
483 default: "14",
484 description: "Medium period",
485 },
486 ParamDef {
487 name: "timeperiod3",
488 default: "28",
489 description: "Long period",
490 },
491 ],
492 formula_source: "https://www.investopedia.com/terms/u/ultimateoscillator.asp",
493 formula_latex: r#"
494\[
495\text{BP} = \text{Close} - \min(\text{Low}, \text{PrevClose}) \\ \text{TR} = \max(\text{High}, \text{PrevClose}) - \min(\text{Low}, \text{PrevClose})
496\]
497"#,
498 gold_standard_file: "ultosc.json",
499 category: "Classic",
500};
501
502pub const TRIX_METADATA: IndicatorMetadata = IndicatorMetadata {
503 name: "TRIX",
504 description: "A momentum oscillator that shows the percent rate of change of a triple exponentially smoothed moving average.",
505 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.",
506 keywords: &["momentum", "oscillator", "smoothing", "classic"],
507 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",
508 params: &[ParamDef {
509 name: "timeperiod",
510 default: "15",
511 description: "Smoothing period",
512 }],
513 formula_source: "https://www.investopedia.com/terms/t/trix.asp",
514 formula_latex: r#"
515\[
516TRIX = \frac{EMA3_t - EMA3_{t-1}}{EMA3_{t-1}} \times 100
517\]
518"#,
519 gold_standard_file: "trix.json",
520 category: "Classic",
521};
522
523pub const MOM_METADATA: IndicatorMetadata = IndicatorMetadata {
524 name: "Momentum (MOM)",
525 description: "A simple indicator that measures the amount that a security's price has changed over a given span of time.",
526 usage: "Use to measure the velocity of price changes. Positive values indicate an uptrend, while negative values indicate a downtrend.",
527 keywords: &["momentum", "classic", "trend"],
528 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",
529 params: &[ParamDef {
530 name: "timeperiod",
531 default: "10",
532 description: "Lookback period",
533 }],
534 formula_source: "https://www.investopedia.com/terms/m/momentum.asp",
535 formula_latex: r#"
536\[
537MOM = Price_t - Price_{t-n}
538\]
539"#,
540 gold_standard_file: "mom.json",
541 category: "Classic",
542};
543
544pub const ROC_METADATA: IndicatorMetadata = IndicatorMetadata {
545 name: "Rate of Change (ROC)",
546 description: "A momentum-based technical indicator that measures the percentage change in price between the current price and the price n periods ago.",
547 usage: "Use to measure the speed at which price is changing. It is often used to identify overbought/oversold conditions and trend reversals.",
548 keywords: &["momentum", "classic", "oscillator"],
549 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",
550 params: &[ParamDef {
551 name: "timeperiod",
552 default: "10",
553 description: "Lookback period",
554 }],
555 formula_source: "https://www.investopedia.com/terms/r/rateofchange.asp",
556 formula_latex: r#"
557\[
558ROC = \frac{Price_t - Price_{t-n}}{Price_{t-n}} \times 100
559\]
560"#,
561 gold_standard_file: "roc.json",
562 category: "Classic",
563};
564
565pub const CMO_METADATA: IndicatorMetadata = IndicatorMetadata {
566 name: "Chande Momentum Oscillator (CMO)",
567 description: "An advanced momentum oscillator developed by Tushar Chande that measures the difference between up and down days.",
568 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.",
569 keywords: &[
570 "momentum",
571 "oscillator",
572 "classic",
573 "overbought",
574 "oversold",
575 ],
576 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",
577 params: &[ParamDef {
578 name: "timeperiod",
579 default: "14",
580 description: "Lookback period",
581 }],
582 formula_source: "https://www.investopedia.com/terms/c/chandemomentumoscillator.asp",
583 formula_latex: r#"
584\[
585CMO = 100 \times \frac{S_u - S_d}{S_u + S_d}
586\]
587"#,
588 gold_standard_file: "cmo.json",
589 category: "Classic",
590};
591
592pub const APO_METADATA: IndicatorMetadata = IndicatorMetadata {
593 name: "Absolute Price Oscillator (APO)",
594 description: "Shows the absolute difference between two moving averages of different periods.",
595 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.",
596 keywords: &["trend", "momentum", "moving-average", "classic"],
597 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",
598 params: &[
599 ParamDef {
600 name: "fastperiod",
601 default: "12",
602 description: "Fast period",
603 },
604 ParamDef {
605 name: "slowperiod",
606 default: "26",
607 description: "Slow period",
608 },
609 ],
610 formula_source: "https://www.tradingview.com/support/solutions/43000501826-absolute-price-oscillator-apo/",
611 formula_latex: r#"
612\[
613APO = EMA(fast) - EMA(slow)
614\]
615"#,
616 gold_standard_file: "apo.json",
617 category: "Classic",
618};
619
620pub const PPO_METADATA: IndicatorMetadata = IndicatorMetadata {
621 name: "Percentage Price Oscillator (PPO)",
622 description: "A momentum oscillator that measures the difference between two moving averages as a percentage of the larger moving average.",
623 usage: "Use to compare trend momentum across different securities with varying price levels. PPO is the percentage version of MACD.",
624 keywords: &[
625 "trend",
626 "momentum",
627 "moving-average",
628 "classic",
629 "normalization",
630 ],
631 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",
632 params: &[
633 ParamDef {
634 name: "fastperiod",
635 default: "12",
636 description: "Fast period",
637 },
638 ParamDef {
639 name: "slowperiod",
640 default: "26",
641 description: "Slow period",
642 },
643 ],
644 formula_source: "https://www.investopedia.com/terms/p/ppo.asp",
645 formula_latex: r#"
646\[
647PPO = \frac{EMA(12) - EMA(26)}{EMA(26)} \times 100
648\]
649"#,
650 gold_standard_file: "ppo.json",
651 category: "Classic",
652};