quantwave_core/indicators/
price_transform.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3
4talib_4_in_1_out!(AVGPRICE, talib_rs::price_transform::avgprice);
5impl Default for AVGPRICE {
6 fn default() -> Self {
7 Self::new()
8 }
9}
10talib_2_in_1_out!(MEDPRICE, talib_rs::price_transform::medprice);
11impl Default for MEDPRICE {
12 fn default() -> Self {
13 Self::new()
14 }
15}
16talib_3_in_1_out!(TYPPRICE, talib_rs::price_transform::typprice);
17impl Default for TYPPRICE {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22talib_3_in_1_out!(WCLPRICE, talib_rs::price_transform::wclprice);
23impl Default for WCLPRICE {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29#[derive(Debug, Clone, Default)]
34pub struct OC2;
35
36impl OC2 {
37 pub fn new() -> Self {
38 Self
39 }
40}
41
42impl Next<(f64, f64)> for OC2 {
43 type Output = f64;
44 fn next(&mut self, input: (f64, f64)) -> Self::Output {
45 (input.0 + input.1) / 2.0
46 }
47}
48
49pub const AVGPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
50 name: "Average Price (AVGPRICE)",
51 description: "The simple average of the Open, High, Low, and Close prices for a given period.",
52 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.",
53 keywords: &["price-transform", "classic", "smoothing"],
54 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",
55 params: &[],
56 formula_source: "https://www.tradingview.com/support/solutions/43000502588-average-price-avgprice/",
57 formula_latex: r#"
58\[
59AVGPRICE = \frac{Open + High + Low + Close}{4}
60\]
61"#,
62 gold_standard_file: "avgprice.json",
63 category: "Classic",
64};
65
66pub const MEDPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
67 name: "Median Price (MEDPRICE)",
68 description: "The midpoint between the High and Low prices for a given period.",
69 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.",
70 keywords: &["price-transform", "classic", "midpoint"],
71 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",
72 params: &[],
73 formula_source: "https://www.tradingview.com/support/solutions/43000502589-median-price-medprice/",
74 formula_latex: r#"
75\[
76MEDPRICE = \frac{High + Low}{2}
77\]
78"#,
79 gold_standard_file: "medprice.json",
80 category: "Classic",
81};
82
83pub const TYPPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
84 name: "Typical Price (TYPPRICE)",
85 description: "An average of the High, Low, and Close prices.",
86 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.",
87 keywords: &["price-transform", "classic"],
88 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",
89 params: &[],
90 formula_source: "https://www.investopedia.com/terms/t/typicalprice.asp",
91 formula_latex: r#"
92\[
93TYPPRICE = \frac{High + Low + Close}{3}
94\]
95"#,
96 gold_standard_file: "typprice.json",
97 category: "Classic",
98};
99
100pub const WCLPRICE_METADATA: IndicatorMetadata = IndicatorMetadata {
101 name: "Weighted Close Price (WCLPRICE)",
102 description: "An average of the High, Low, and Close prices, with double weight given to the Close price.",
103 usage: "Use to emphasize the importance of the closing price while still accounting for the total range of the bar.",
104 keywords: &["price-transform", "classic", "weighted"],
105 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",
106 params: &[],
107 formula_source: "https://www.tradingview.com/support/solutions/43000502590-weighted-close-wclprice/",
108 formula_latex: r#"
109\[
110WCLPRICE = \frac{High + Low + 2 \times Close}{4}
111\]
112"#,
113 gold_standard_file: "wclprice.json",
114 category: "Classic",
115};
116
117pub const OC2_METADATA: IndicatorMetadata = IndicatorMetadata {
118 name: "Open-Close Average (OC2)",
119 description: "A simple average of the Open and Close prices.",
120 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.",
121 keywords: &["price-transform", "ehlers", "smoothing", "dsp"],
122 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",
123 params: &[],
124 formula_source: "Every Little Bit Helps (John Ehlers, 2023)",
125 formula_latex: r#"
126\[
127OC2 = \frac{Open + Close}{2}
128\]
129"#,
130 gold_standard_file: "oc2.json",
131 category: "Ehlers DSP",
132};
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::traits::Next;
138 use proptest::prelude::*;
139
140 proptest! {
141 #[test]
142 fn test_avgprice_parity(
143 o in prop::collection::vec(0.1..100.0, 1..100),
144 h in prop::collection::vec(0.1..100.0, 1..100),
145 l in prop::collection::vec(0.1..100.0, 1..100),
146 c in prop::collection::vec(0.1..100.0, 1..100)
147 ) {
148 let len = o.len().min(h.len()).min(l.len()).min(c.len());
149 if len == 0 { return Ok(()); }
150
151 let mut avgprice = AVGPRICE::new();
152 let streaming_results: Vec<f64> = (0..len).map(|i| avgprice.next((o[i], h[i], l[i], c[i]))).collect();
153 let batch_results = talib_rs::price_transform::avgprice(&o[..len], &h[..len], &l[..len], &c[..len]).unwrap_or_else(|_| vec![f64::NAN; len]);
154
155 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
156 if s.is_nan() {
157 assert!(b.is_nan());
158 } else {
159 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
160 }
161 }
162 }
163
164 #[test]
165 fn test_medprice_parity(
166 h in prop::collection::vec(0.1..100.0, 1..100),
167 l in prop::collection::vec(0.1..100.0, 1..100)
168 ) {
169 let len = h.len().min(l.len());
170 if len == 0 { return Ok(()); }
171
172 let mut medprice = MEDPRICE::new();
173 let streaming_results: Vec<f64> = (0..len).map(|i| medprice.next((h[i], l[i]))).collect();
174 let batch_results = talib_rs::price_transform::medprice(&h[..len], &l[..len]).unwrap_or_else(|_| vec![f64::NAN; len]);
175
176 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
177 if s.is_nan() {
178 assert!(b.is_nan());
179 } else {
180 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
181 }
182 }
183 }
184
185 #[test]
186 fn test_oc2_parity(
187 o in prop::collection::vec(0.1..100.0, 1..100),
188 c in prop::collection::vec(0.1..100.0, 1..100)
189 ) {
190 let len = o.len().min(c.len());
191 if len == 0 { return Ok(()); }
192
193 let mut oc2 = OC2::new();
194 let streaming_results: Vec<f64> = (0..len).map(|i| oc2.next((o[i], c[i]))).collect();
195 let batch_results: Vec<f64> = (0..len).map(|i| (o[i] + c[i]) / 2.0).collect();
196
197 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
198 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
199 }
200 }
201 }
202}