Skip to content

Commit 6828519

Browse files
authored
⚡ Optimize stochastic rolling helpers (#8)
* Optimize rolling extrema calculations in indicators * Optimize rolling window indicators * 📝 Add docs for rolling window utils * ♻️ Rename rolling arg index helper * fix: psar 공개 export 연결
1 parent dae0f66 commit 6828519

8 files changed

Lines changed: 312 additions & 100 deletions

File tree

core/src/indicators/aroon.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::utils::{find_max, find_min};
1+
use crate::utils::rolling_argmax_argmin;
22

33
pub fn aroon(highs: &[f64], lows: &[f64], period: usize) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
44
let mut aroon_up = vec![None; highs.len()];
@@ -8,14 +8,16 @@ pub fn aroon(highs: &[f64], lows: &[f64], period: usize) -> (Vec<Option<f64>>, V
88
return (aroon_up, aroon_down);
99
}
1010

11-
for i in period..highs.len() {
12-
let high_slice = &highs[i - period..=i];
13-
let low_slice = &lows[i - period..=i];
14-
let max_high = find_max(high_slice);
15-
let min_low = find_min(low_slice);
11+
let window = period + 1;
12+
let (max_indices, min_indices) = rolling_argmax_argmin(highs, lows, window);
1613

17-
let max_index = high_slice.iter().rposition(|&x| x == max_high).unwrap();
18-
let min_index = low_slice.iter().rposition(|&x| x == min_low).unwrap();
14+
for i in period..highs.len() {
15+
let window_start = i - period;
16+
let (Some(max_index), Some(min_index)) = (max_indices[i], min_indices[i]) else {
17+
continue;
18+
};
19+
let max_index = max_index - window_start;
20+
let min_index = min_index - window_start;
1921

2022
let aroon_up_point = (max_index as f64 * 100.0) / period as f64;
2123
let aroon_down_point = (min_index as f64 * 100.0) / period as f64;

core/src/indicators/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub use nvi::*;
7070
pub use obv::*;
7171
pub use pchan::*;
7272
pub use ppo::*;
73+
pub use psar::*;
7374
pub use psl::*;
7475
pub use pvi::*;
7576
pub use pvo::*;

core/src/indicators/pchan.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::utils::{find_max, find_min};
1+
use crate::utils::rolling_max_min;
22

33
pub fn pchan(
44
highs: &[f64],
@@ -10,13 +10,17 @@ pub fn pchan(
1010
let mut lower = vec![None; len];
1111
let mut middle = vec![None; len];
1212

13-
if len < period {
13+
if period == 0 || len < period {
1414
return (upper, middle, lower);
1515
}
1616

17+
let (rolling_highs, rolling_lows) =
18+
rolling_max_min(&highs[..len - 1], &lows[..len - 1], period);
19+
1720
for i in period..len {
18-
let max_high = find_max(&highs[i - period..i]);
19-
let min_low = find_min(&lows[i - period..i]);
21+
let (Some(max_high), Some(min_low)) = (rolling_highs[i - 1], rolling_lows[i - 1]) else {
22+
continue;
23+
};
2024

2125
upper[i] = Some(max_high);
2226
lower[i] = Some(min_low);

core/src/indicators/stochf.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::utils::{calc_mean, find_max, find_min};
1+
use crate::utils::{rolling_max_min, rolling_mean_strict};
22

33
pub fn stochf_percent_k(
44
highs: &[f64],
@@ -13,9 +13,12 @@ pub fn stochf_percent_k(
1313
return percent_k;
1414
}
1515

16+
let (rolling_highs, rolling_lows) = rolling_max_min(highs, lows, fastk_period);
17+
1618
for i in (fastk_period - 1)..len {
17-
let max_high = find_max(&highs[i + 1 - fastk_period..=i]);
18-
let min_low = find_min(&lows[i + 1 - fastk_period..=i]);
19+
let (Some(max_high), Some(min_low)) = (rolling_highs[i], rolling_lows[i]) else {
20+
continue;
21+
};
1922

2023
percent_k[i] = if max_high == min_low {
2124
None
@@ -33,27 +36,16 @@ pub fn stochf_percent_d(
3336
fastd_period: usize,
3437
) -> Vec<Option<f64>> {
3538
let len = percent_k.len();
36-
let mut percent_d = vec![None; len];
3739

3840
if len < fastk_period {
39-
return percent_d;
41+
return vec![None; len];
4042
}
4143

42-
for i in (fastk_period - 1)..len {
43-
if fastd_period == 1 {
44-
percent_d[i] = percent_k[i];
45-
} else if i >= fastk_period - 1 + (fastd_period - 1) {
46-
let slice = &percent_k[i + 1 - fastd_period..=i];
47-
let valid_values: Vec<f64> = slice.iter().filter_map(|&x| x).collect();
48-
percent_d[i] = if valid_values.len() == fastd_period {
49-
Some(calc_mean(&valid_values))
50-
} else {
51-
None
52-
};
53-
}
44+
if fastd_period == 1 {
45+
percent_k.to_vec()
46+
} else {
47+
rolling_mean_strict(percent_k, fastd_period)
5448
}
55-
56-
percent_d
5749
}
5850

5951
pub fn stochf(

core/src/indicators/stochrsi.rs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::indicators::rsi;
2-
use crate::utils::{calc_mean, find_max, find_min};
2+
use crate::utils::{rolling_max_min, rolling_mean_strict};
33

44
pub fn stochrsi(
55
closes: &[f64],
@@ -9,45 +9,48 @@ pub fn stochrsi(
99
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
1010
let len = closes.len();
1111
let mut percent_k = vec![None; len];
12-
let mut percent_d = vec![None; len];
1312

1413
if len < period_rsi + period_k || period_rsi <= 1 || period_k <= 1 {
15-
return (percent_k, percent_d);
14+
return (percent_k, vec![None; len]);
1615
}
1716

1817
let rsi_values = rsi(closes, period_rsi);
18+
let rsi_values_with_nan: Vec<f64> = rsi_values
19+
.iter()
20+
.map(|value| value.unwrap_or(f64::NAN))
21+
.collect();
22+
let (rolling_max, rolling_min) =
23+
rolling_max_min(&rsi_values_with_nan, &rsi_values_with_nan, period_k);
1924

2025
for i in (period_rsi + period_k - 1)..len {
21-
let slice = &rsi_values[i + 1 - period_k..=i];
22-
let valid_values: Vec<f64> = slice.iter().filter_map(|&x| x).collect();
26+
let valid_values: Vec<f64> = rsi_values[i + 1 - period_k..=i]
27+
.iter()
28+
.filter_map(|&x| x)
29+
.collect();
2330

24-
if valid_values.len() == period_k {
25-
let rsi_max = find_max(&valid_values);
26-
let rsi_min = find_min(&valid_values);
27-
28-
let k = if rsi_max == rsi_min {
29-
None
30-
} else {
31-
rsi_values[i].map(|rsi| ((rsi - rsi_min) / (rsi_max - rsi_min)) * 100.0)
32-
};
31+
if valid_values.len() != period_k {
32+
continue;
33+
}
3334

34-
percent_k[i] = k;
35+
let (Some(rsi), Some(rsi_max), Some(rsi_min)) =
36+
(rsi_values[i], rolling_max[i], rolling_min[i])
37+
else {
38+
continue;
39+
};
3540

36-
if period_d == 1 {
37-
percent_d[i] = k;
38-
} else if i >= period_rsi + period_k - 1 + (period_d - 1) {
39-
let d_slice = &percent_k[i + 1 - period_d..=i];
40-
let valid_d_values: Vec<f64> = d_slice.iter().filter_map(|&x| x).collect();
41-
let d = if valid_d_values.len() == period_d {
42-
Some(calc_mean(&valid_d_values))
43-
} else {
44-
None
45-
};
46-
percent_d[i] = d;
47-
}
48-
}
41+
percent_k[i] = if rsi_max == rsi_min {
42+
None
43+
} else {
44+
Some(((rsi - rsi_min) / (rsi_max - rsi_min)) * 100.0)
45+
};
4946
}
5047

48+
let percent_d = if period_d == 1 {
49+
percent_k.clone()
50+
} else {
51+
rolling_mean_strict(&percent_k, period_d)
52+
};
53+
5154
(percent_k, percent_d)
5255
}
5356

core/src/indicators/stochs.rs

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::utils::{calc_mean, find_max, find_min};
1+
use crate::utils::{rolling_max_min, rolling_mean_strict};
22

33
fn stochs_raw_k(
44
highs: &[f64],
@@ -13,9 +13,12 @@ fn stochs_raw_k(
1313
return raw_k;
1414
}
1515

16+
let (rolling_highs, rolling_lows) = rolling_max_min(highs, lows, fastk_period);
17+
1618
for i in (fastk_period - 1)..len {
17-
let max_high = find_max(&highs[i + 1 - fastk_period..=i]);
18-
let min_low = find_min(&lows[i + 1 - fastk_period..=i]);
19+
let (Some(max_high), Some(min_low)) = (rolling_highs[i], rolling_lows[i]) else {
20+
continue;
21+
};
1922

2023
raw_k[i] = if max_high == min_low {
2124
None
@@ -35,24 +38,13 @@ pub fn stoch_percent_k(
3538
slowk_period: usize,
3639
) -> Vec<Option<f64>> {
3740
let len = closes.len();
38-
let mut percent_k = vec![None; len];
3941

4042
if len < fastk_period {
41-
return percent_k;
43+
return vec![None; len];
4244
}
4345

4446
let raw_k = stochs_raw_k(highs, lows, closes, fastk_period);
45-
for i in (fastk_period + slowk_period - 2)..len {
46-
let slice = &raw_k[i + 1 - slowk_period..=i];
47-
let valid_values: Vec<f64> = slice.iter().filter_map(|&x| x).collect();
48-
percent_k[i] = if valid_values.len() == slowk_period {
49-
Some(calc_mean(&valid_values))
50-
} else {
51-
None
52-
};
53-
}
54-
55-
percent_k
47+
rolling_mean_strict(&raw_k, slowk_period)
5648
}
5749

5850
pub fn stoch_percent_d(
@@ -70,27 +62,20 @@ pub fn stoch_percent_d(
7062
fn stoch_percent_d_from_k(
7163
percent_k: &[Option<f64>],
7264
fastk_period: usize,
73-
slowk_period: usize,
65+
_slowk_period: usize,
7466
slowd_period: usize,
7567
) -> Vec<Option<f64>> {
7668
let len = percent_k.len();
77-
let mut percent_d = vec![None; len];
7869

7970
if len < fastk_period {
80-
return percent_d;
71+
return vec![None; len];
8172
}
8273

83-
for i in (fastk_period + slowk_period + slowd_period - 3)..len {
84-
let slice = &percent_k[i + 1 - slowd_period..=i];
85-
let valid_values: Vec<f64> = slice.iter().filter_map(|&x| x).collect();
86-
percent_d[i] = if valid_values.len() == slowd_period {
87-
Some(calc_mean(&valid_values))
88-
} else {
89-
None
90-
};
74+
if slowd_period == 1 {
75+
percent_k.to_vec()
76+
} else {
77+
rolling_mean_strict(percent_k, slowd_period)
9178
}
92-
93-
percent_d
9479
}
9580

9681
pub fn stochs(

core/src/indicators/willr.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::utils::{find_max, find_min};
1+
use crate::utils::rolling_max_min;
22

33
pub fn willr(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> Vec<Option<f64>> {
44
let len = closes.len();
@@ -8,9 +8,12 @@ pub fn willr(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> Vec<
88
return result;
99
}
1010

11+
let (rolling_highs, rolling_lows) = rolling_max_min(highs, lows, period);
12+
1113
for i in period - 1..len {
12-
let max_high = find_max(&highs[i + 1 - period..=i]);
13-
let min_low = find_min(&lows[i + 1 - period..=i]);
14+
let (Some(max_high), Some(min_low)) = (rolling_highs[i], rolling_lows[i]) else {
15+
continue;
16+
};
1417

1518
let cc = closes[i];
1619
if max_high == min_low {

0 commit comments

Comments
 (0)