egui_charts/studies/
helpers.rs1use std::collections::VecDeque;
9
10pub fn sma(values: &[f64], period: usize) -> Vec<f64> {
12 windows(values, period.max(1), |window| {
13 window.iter().sum::<f64>() / window.len() as f64
14 })
15}
16
17pub fn sum(values: &[f64], period: usize) -> Vec<f64> {
19 windows(values, period.max(1), |window| window.iter().sum())
20}
21
22pub fn ema(values: &[f64], period: usize) -> Vec<f64> {
24 average(values, 1, 2.0 / (period.max(1) as f64 + 1.0))
25}
26
27pub fn ema_sma(values: &[f64], period: usize) -> Vec<f64> {
30 average(values, period.max(1), 2.0 / (period.max(1) as f64 + 1.0))
31}
32
33pub fn rma(values: &[f64], period: usize) -> Vec<f64> {
36 average(values, period.max(1), 1.0 / period.max(1) as f64)
37}
38
39fn average(values: &[f64], seed: usize, alpha: f64) -> Vec<f64> {
40 let (mut count, mut average) = (0, 0.0);
41 values
42 .iter()
43 .map(|&x| {
44 if !x.is_finite() {
45 return f64::NAN;
46 }
47 if count < seed {
48 count += 1;
49 average += x;
50 if count < seed {
51 return f64::NAN;
52 }
53 average /= seed as f64;
54 } else {
55 average += (x - average) * alpha;
56 }
57 average
58 })
59 .collect()
60}
61
62pub fn wma(values: &[f64], period: usize) -> Vec<f64> {
64 let period = period.max(1);
65 let denominator = period as f64 * (period as f64 + 1.0) / 2.0;
66 windows(values, period, |window| {
67 window
68 .iter()
69 .rev()
70 .enumerate()
71 .map(|(j, v)| v * (period - j) as f64)
72 .sum::<f64>()
73 / denominator
74 })
75}
76
77pub fn stdev(values: &[f64], period: usize) -> Vec<f64> {
79 windows(values, period.max(1), |window| {
80 let mean = window.iter().sum::<f64>() / window.len() as f64;
81 (window.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / window.len() as f64).sqrt()
82 })
83}
84
85fn windows(values: &[f64], period: usize, compute: impl Fn(&[f64]) -> f64) -> Vec<f64> {
86 (0..values.len())
87 .map(|i| {
88 if i + 1 < period {
89 return f64::NAN;
90 }
91 let window = &values[i + 1 - period..=i];
92 if window.iter().all(|v| v.is_finite()) {
93 compute(window)
94 } else {
95 f64::NAN
96 }
97 })
98 .collect()
99}
100
101pub fn highest(values: impl IntoIterator<Item = f64>, period: usize) -> Vec<f64> {
104 rolling(values, period.max(1), f64::NEG_INFINITY, |kept, new| {
105 kept <= new
106 })
107}
108
109pub fn lowest(values: impl IntoIterator<Item = f64>, period: usize) -> Vec<f64> {
112 rolling(values, period.max(1), f64::INFINITY, |kept, new| {
113 kept >= new
114 })
115}
116
117fn rolling(
118 values: impl IntoIterator<Item = f64>,
119 period: usize,
120 empty: f64,
121 beaten: impl Fn(f64, f64) -> bool,
122) -> Vec<f64> {
123 let mut window = VecDeque::<(usize, f64)>::new();
124 values
125 .into_iter()
126 .enumerate()
127 .map(|(i, v)| {
128 if window.front().is_some_and(|&(j, _)| i - j >= period) {
129 window.pop_front();
130 }
131 if !v.is_nan() {
132 while window.back().is_some_and(|&(_, kept)| beaten(kept, v)) {
133 window.pop_back();
134 }
135 window.push_back((i, v));
136 }
137 window.front().map_or(empty, |&(_, best)| best)
138 })
139 .collect()
140}
141
142pub fn change(values: &[f64], period: usize) -> Vec<f64> {
144 values
145 .iter()
146 .enumerate()
147 .map(|(i, &value)| {
148 i.checked_sub(period)
149 .filter(|&j| value.is_finite() && values[j].is_finite())
150 .map_or(f64::NAN, |j| value - values[j])
151 })
152 .collect()
153}
154
155#[cfg(feature = "studies")]
157pub(crate) fn calculate_roc(values: &[f64], period: usize) -> Vec<f64> {
158 let mut roc = Vec::with_capacity(values.len());
159
160 for i in 0..values.len() {
161 if i < period || values[i - period] == 0.0 {
162 roc.push(f64::NAN);
163 } else {
164 let change = (values[i] - values[i - period]) / values[i - period] * 100.0;
165 roc.push(change);
166 }
167 }
168
169 roc
170}
171
172pub fn crossover(a: &[f64], b: &[f64]) -> Vec<bool> {
176 crossings(a, b, std::cmp::Ordering::Greater)
177}
178
179pub fn crossunder(a: &[f64], b: &[f64]) -> Vec<bool> {
181 crossings(a, b, std::cmp::Ordering::Less)
182}
183
184fn crossings(a: &[f64], b: &[f64], direction: std::cmp::Ordering) -> Vec<bool> {
185 let mut previous = None;
186 a.iter()
187 .enumerate()
188 .map(|(i, &a)| {
189 let Some(&b) = b.get(i).filter(|b| a.is_finite() && b.is_finite()) else {
190 previous = None;
191 return false;
192 };
193 let order = a.partial_cmp(&b).unwrap();
194 if order == std::cmp::Ordering::Equal {
195 return false;
196 }
197 let crosses = order == direction && previous.is_some_and(|p| p != order);
198 previous = Some(order);
199 crosses
200 })
201 .collect()
202}