quantwave_core/indicators/
price_transform.rs1use crate::indicators::metadata::IndicatorMetadata;
2use crate::traits::Next;
3
4pub use crate::indicators::incremental::price_transform::{AVGPRICE, MEDPRICE, TYPPRICE, WCLPRICE};
5
6#[derive(Debug, Clone, Default)]
11pub struct OC2;
12
13impl OC2 {
14 pub fn new() -> Self {
15 Self
16 }
17}
18
19impl Next<(f64, f64)> for OC2 {
20 type Output = f64;
21 fn next(&mut self, input: (f64, f64)) -> Self::Output {
22 (input.0 + input.1) / 2.0
23 }
24}
25
26pub const AVGPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
27 name: "Average Price (AVGPRICE)",
28 description: "The simple average of the Open, High, Low, and Close prices for a given period.",
29 usage: "Use as a smoothed price input for other indicators. It provides a more balanced view of the period's price action than the Close price alone.",
30 keywords: &["price-transform", "classic", "smoothing"],
31 ehlers_summary: "Average Price is the arithmetic mean of the four key price points in a bar. In technical analysis, using Average Price instead of Close can help filter out erratic price spikes and provide a more stable foundation for trend-following algorithms. — TA-Lib Documentation",
32 params: &[],
33 formula_source: "https://www.tradingview.com/support/solutions/43000502588-average-price-avgprice/",
34 formula_latex: r#"
35\[
36AVGPRICE = \frac{Open + High + Low + Close}{4}
37\]
38"#,
39 gold_standard_file: "avgprice.json",
40 category: "Classic",
41};
42
43pub const MEDPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
44 name: "Median Price (MEDPRICE)",
45 description: "The midpoint between the High and Low prices for a given period.",
46 usage: "Use to identify the central tendency of a bar's range. It is the basis for many oscillators and trend-following indicators like the Bill Williams Alligator.",
47 keywords: &["price-transform", "classic", "midpoint"],
48 ehlers_summary: "Median Price represents the 50% retracement level of the current period's range. By focusing on the High-Low midpoint, it removes the 'bias' of the closing price, which can often be manipulated by end-of-day positioning. — TA-Lib Documentation",
49 params: &[],
50 formula_source: "https://www.tradingview.com/support/solutions/43000502589-median-price-medprice/",
51 formula_latex: r#"
52\[
53MEDPRICE = \frac{High + Low}{2}
54\]
55"#,
56 gold_standard_file: "medprice.json",
57 category: "Classic",
58};
59
60pub const TYPPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
61 name: "Typical Price (TYPPRICE)",
62 description: "An average of the High, Low, and Close prices.",
63 usage: "Use as the primary price input for the Money Flow Index (MFI) and Commodity Channel Index (CCI). It provides a representative price level for the entire bar.",
64 keywords: &["price-transform", "classic"],
65 ehlers_summary: "Typical Price is a simple average of the High, Low, and Close. It is widely used in indicators that measure the relationship between price and volume, as it offers a more comprehensive view of the day's activity than the Close price alone. — StockCharts ChartSchool",
66 params: &[],
67 formula_source: "https://www.investopedia.com/terms/t/typicalprice.asp",
68 formula_latex: r#"
69\[
70TYPPRICE = \frac{High + Low + Close}{3}
71\]
72"#,
73 gold_standard_file: "typprice.json",
74 category: "Classic",
75};
76
77pub const WCLPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
78 name: "Weighted Close Price (WCLPRICE)",
79 description: "An average of the High, Low, and Close prices, with double weight given to the Close price.",
80 usage: "Use to emphasize the importance of the closing price while still accounting for the total range of the bar.",
81 keywords: &["price-transform", "classic", "weighted"],
82 ehlers_summary: "Weighted Close Price gives additional significance to the Close, reflecting the widely held belief that the closing price is the most important data point in a trading session. It provides a more nuanced input for smoothing algorithms. — TA-Lib Documentation",
83 params: &[],
84 formula_source: "https://www.tradingview.com/support/solutions/43000502590-weighted-close-wclprice/",
85 formula_latex: r#"
86\[
87WCLPRICE = \frac{High + Low + 2 \times Close}{4}
88\]
89"#,
90 gold_standard_file: "wclprice.json",
91 category: "Classic",
92};
93
94pub const OC2_METADATA: IndicatorMetadata = IndicatorMetadata {
95 name: "Open-Close Average (OC2)",
96 description: "A simple average of the Open and Close prices.",
97 usage: "Use to reduce noise in technical indicators. Based on John Ehlers' recent research, averaging the open and close can significantly improve signal-to-noise ratios in DSP-based indicators.",
98 keywords: &["price-transform", "ehlers", "smoothing", "dsp"],
99 ehlers_summary: "In his 2023 paper 'Every Little Bit Helps', John Ehlers demonstrates that using the average of the Open and Close as an input can enhance the performance of various filters and oscillators by providing a cleaner signal with reduced aliasing. — John Ehlers",
100 params: &[],
101 formula_source: "Every Little Bit Helps (John Ehlers, 2023)",
102 formula_latex: r#"
103\[
104OC2 = \frac{Open + Close}{2}
105\]
106"#,
107 gold_standard_file: "oc2.json",
108 category: "Ehlers DSP",
109};
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::traits::Next;
115 use proptest::prelude::*;
116
117 proptest! {
118 #[test]
119 fn test_avgprice_parity(
120 o in prop::collection::vec(0.1..100.0, 1..100),
121 h in prop::collection::vec(0.1..100.0, 1..100),
122 l in prop::collection::vec(0.1..100.0, 1..100),
123 c in prop::collection::vec(0.1..100.0, 1..100)
124 ) {
125 let len = o.len().min(h.len()).min(l.len()).min(c.len());
126 if len == 0 { return Ok(()); }
127
128 let mut avgprice = AVGPRICE::new();
129 let streaming_results: Vec<f64> = (0..len).map(|i| avgprice.next((o[i], h[i], l[i], c[i]))).collect();
130 let batch_results = talib_rs::price_transform::avgprice(&o[..len], &h[..len], &l[..len], &c[..len]).unwrap_or_else(|_| vec![f64::NAN; len]);
131
132 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
133 if s.is_nan() {
134 assert!(b.is_nan());
135 } else {
136 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
137 }
138 }
139 }
140
141 #[test]
142 fn test_medprice_parity(
143 h in prop::collection::vec(0.1..100.0, 1..100),
144 l in prop::collection::vec(0.1..100.0, 1..100)
145 ) {
146 let len = h.len().min(l.len());
147 if len == 0 { return Ok(()); }
148
149 let mut medprice = MEDPRICE::new();
150 let streaming_results: Vec<f64> = (0..len).map(|i| medprice.next((h[i], l[i]))).collect();
151 let batch_results = talib_rs::price_transform::medprice(&h[..len], &l[..len]).unwrap_or_else(|_| vec![f64::NAN; len]);
152
153 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
154 if s.is_nan() {
155 assert!(b.is_nan());
156 } else {
157 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
158 }
159 }
160 }
161
162 #[test]
163 fn test_oc2_parity(
164 o in prop::collection::vec(0.1..100.0, 1..100),
165 c in prop::collection::vec(0.1..100.0, 1..100)
166 ) {
167 let len = o.len().min(c.len());
168 if len == 0 { return Ok(()); }
169
170 let mut oc2 = OC2::new();
171 let streaming_results: Vec<f64> = (0..len).map(|i| oc2.next((o[i], c[i]))).collect();
172 let batch_results: Vec<f64> = (0..len).map(|i| (o[i] + c[i]) / 2.0).collect();
173
174 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
175 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
176 }
177 }
178 }
179}