quantedge_ta/
price_source.rs1use crate::{Ohlcv, Price};
2
3use std::fmt::{Debug, Display};
4
5#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug)]
11pub enum PriceSource {
12 Open,
14 High,
16 #[default]
18 Close,
19 Low,
21 HL2,
23 HLC3,
25 OHLC4,
27 HLCC4,
29 TrueRange,
33}
34
35impl Display for PriceSource {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{self:?}")
38 }
39}
40
41impl PriceSource {
42 #[inline]
43 pub(crate) fn extract(self, ohlcv: &impl Ohlcv, prev_close: Option<Price>) -> Price {
44 match self {
45 Self::Open => ohlcv.open(),
46 Self::High => ohlcv.high(),
47 Self::Close => ohlcv.close(),
48 Self::Low => ohlcv.low(),
49 Self::HL2 => f64::midpoint(ohlcv.high(), ohlcv.low()),
50 Self::HLC3 => (ohlcv.high() + ohlcv.low() + ohlcv.close()) / 3.0,
51 Self::OHLC4 => (ohlcv.open() + ohlcv.high() + ohlcv.low() + ohlcv.close()) / 4.0,
52 Self::HLCC4 => (ohlcv.high() + ohlcv.low() + ohlcv.close() + ohlcv.close()) / 4.0,
53 Self::TrueRange => {
54 let hl = ohlcv.high() - ohlcv.low();
55
56 match prev_close {
57 Some(prev_close) => {
58 let hc = (ohlcv.high() - prev_close).abs();
59 let lc = (ohlcv.low() - prev_close).abs();
60 hl.max(hc).max(lc)
61 }
62 None => hl,
63 }
64 }
65 }
66 }
67}
68
69#[cfg(test)]
70#[allow(clippy::float_cmp)]
71mod tests {
72 use super::*;
73 use crate::test_util::{Bar, assert_approx};
74
75 fn bar() -> Bar {
76 Bar::new(10.0, 30.0, 5.0, 20.0)
77 }
78
79 #[test]
80 fn extract_open() {
81 assert_eq!(PriceSource::Open.extract(&bar(), None), 10.0);
82 }
83
84 #[test]
85 fn extract_high() {
86 assert_eq!(PriceSource::High.extract(&bar(), None), 30.0);
87 }
88
89 #[test]
90 fn extract_low() {
91 assert_eq!(PriceSource::Low.extract(&bar(), None), 5.0);
92 }
93
94 #[test]
95 fn extract_close() {
96 assert_eq!(PriceSource::Close.extract(&bar(), None), 20.0);
97 }
98
99 #[test]
100 fn extract_hl2() {
101 assert_eq!(PriceSource::HL2.extract(&bar(), None), 17.5);
103 }
104
105 #[test]
106 fn extract_hlc3() {
107 let result = PriceSource::HLC3.extract(&bar(), None);
109 assert_approx!(result, 55.0 / 3.0);
110 }
111
112 #[test]
113 fn extract_ohlc4() {
114 assert_eq!(PriceSource::OHLC4.extract(&bar(), None), 16.25);
116 }
117
118 #[test]
119 fn extract_hlcc4() {
120 assert_eq!(PriceSource::HLCC4.extract(&bar(), None), 18.75);
122 }
123
124 #[test]
127 fn true_range_without_prev_close_falls_back_to_hl() {
128 assert_eq!(PriceSource::TrueRange.extract(&bar(), None), 25.0);
130 }
131
132 #[test]
133 fn true_range_hl_wins() {
134 let b = bar();
137 assert_eq!(PriceSource::TrueRange.extract(&b, Some(15.0)), 25.0);
138 }
139
140 #[test]
141 fn true_range_high_vs_prev_close_wins() {
142 let b = bar();
145 assert_eq!(PriceSource::TrueRange.extract(&b, Some(-10.0)), 40.0);
146 }
147
148 #[test]
149 fn true_range_low_vs_prev_close_wins() {
150 let b = bar();
153 assert_eq!(PriceSource::TrueRange.extract(&b, Some(50.0)), 45.0);
154 }
155}