Skip to content

Commit dae0f66

Browse files
authored
⚡️ Optimize single-output indicator paths (#9)
* optimize single-output indicator paths * hide internal bband helper * reuse slow stochastic percent k
1 parent b2893c1 commit dae0f66

5 files changed

Lines changed: 164 additions & 42 deletions

File tree

core/src/indicators/bband.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ pub fn bband(
1212
(upper_band, center, lower_band)
1313
}
1414

15+
pub fn bband_middle(data: &[f64], period: usize) -> Vec<Option<f64>> {
16+
sma(data, period)
17+
}
18+
19+
pub fn bband_upper(data: &[f64], period: usize, sigma: Option<f64>) -> Vec<Option<f64>> {
20+
let (upper_band, _) = bband_bands(data, period, sigma);
21+
upper_band
22+
}
23+
24+
pub fn bband_lower(data: &[f64], period: usize, sigma: Option<f64>) -> Vec<Option<f64>> {
25+
let (_, lower_band) = bband_bands(data, period, sigma);
26+
lower_band
27+
}
28+
1529
fn bband_bands(
1630
data: &[f64],
1731
period: usize,

core/src/indicators/macd.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,39 @@ pub fn macd(
2222
(macd_line, signal_line, histogram)
2323
}
2424

25+
pub fn macd_line(data: &[f64], fast_period: usize, slow_period: usize) -> Vec<Option<f64>> {
26+
calc_macd_line(data, fast_period, slow_period)
27+
}
28+
29+
pub fn macd_signal(
30+
data: &[f64],
31+
fast_period: usize,
32+
slow_period: usize,
33+
signal_period: usize,
34+
) -> Vec<Option<f64>> {
35+
let macd_line = calc_macd_line(data, fast_period, slow_period);
36+
calc_macd_signal(&macd_line, signal_period)
37+
}
38+
39+
pub fn macd_histogram(
40+
data: &[f64],
41+
fast_period: usize,
42+
slow_period: usize,
43+
signal_period: usize,
44+
) -> Vec<Option<f64>> {
45+
let macd_line = calc_macd_line(data, fast_period, slow_period);
46+
let signal_line = calc_macd_signal(&macd_line, signal_period);
47+
48+
macd_line
49+
.iter()
50+
.zip(signal_line.iter())
51+
.map(|(&macd, &signal)| match (macd, signal) {
52+
(Some(m), Some(s)) => Some(m - s),
53+
_ => None,
54+
})
55+
.collect()
56+
}
57+
2558
fn calc_macd_line(data: &[f64], fast_period: usize, slow_period: usize) -> Vec<Option<f64>> {
2659
let mut macd_line = vec![None; data.len()];
2760

core/src/indicators/stochf.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,70 @@
11
use crate::utils::{calc_mean, find_max, find_min};
22

3-
pub fn stochf(
3+
pub fn stochf_percent_k(
44
highs: &[f64],
55
lows: &[f64],
66
closes: &[f64],
77
fastk_period: usize,
8-
fastd_period: usize,
9-
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
8+
) -> Vec<Option<f64>> {
109
let len = closes.len();
1110
let mut percent_k = vec![None; len];
12-
let mut percent_d = vec![None; len];
1311

1412
if len < fastk_period {
15-
return (percent_k, percent_d);
13+
return percent_k;
1614
}
1715

1816
for i in (fastk_period - 1)..len {
1917
let max_high = find_max(&highs[i + 1 - fastk_period..=i]);
2018
let min_low = find_min(&lows[i + 1 - fastk_period..=i]);
2119

22-
let k = if max_high == min_low {
20+
percent_k[i] = if max_high == min_low {
2321
None
2422
} else {
2523
Some(((closes[i] - min_low) / (max_high - min_low)) * 100.0)
2624
};
25+
}
26+
27+
percent_k
28+
}
2729

28-
percent_k[i] = k;
30+
pub fn stochf_percent_d(
31+
percent_k: &[Option<f64>],
32+
fastk_period: usize,
33+
fastd_period: usize,
34+
) -> Vec<Option<f64>> {
35+
let len = percent_k.len();
36+
let mut percent_d = vec![None; len];
2937

38+
if len < fastk_period {
39+
return percent_d;
40+
}
41+
42+
for i in (fastk_period - 1)..len {
3043
if fastd_period == 1 {
31-
percent_d[i] = k;
44+
percent_d[i] = percent_k[i];
3245
} else if i >= fastk_period - 1 + (fastd_period - 1) {
3346
let slice = &percent_k[i + 1 - fastd_period..=i];
3447
let valid_values: Vec<f64> = slice.iter().filter_map(|&x| x).collect();
35-
let d = if valid_values.len() == fastd_period {
48+
percent_d[i] = if valid_values.len() == fastd_period {
3649
Some(calc_mean(&valid_values))
3750
} else {
3851
None
3952
};
40-
percent_d[i] = d;
4153
}
4254
}
4355

56+
percent_d
57+
}
58+
59+
pub fn stochf(
60+
highs: &[f64],
61+
lows: &[f64],
62+
closes: &[f64],
63+
fastk_period: usize,
64+
fastd_period: usize,
65+
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
66+
let percent_k = stochf_percent_k(highs, lows, closes, fastk_period);
67+
let percent_d = stochf_percent_d(&percent_k, fastk_period, fastd_period);
4468
(percent_k, percent_d)
4569
}
4670

core/src/indicators/stochs.rs

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

3-
pub fn stochs(
3+
fn stochs_raw_k(
44
highs: &[f64],
55
lows: &[f64],
66
closes: &[f64],
77
fastk_period: usize,
8-
slowk_period: usize,
9-
slowd_period: usize,
10-
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
8+
) -> Vec<Option<f64>> {
119
let len = closes.len();
12-
let mut percent_k = vec![None; len];
13-
let mut percent_d = vec![None; len];
10+
let mut raw_k = vec![None; len];
1411

1512
if len < fastk_period {
16-
return (percent_k, percent_d);
13+
return raw_k;
1714
}
1815

19-
let mut raw_k = vec![None; len];
2016
for i in (fastk_period - 1)..len {
2117
let max_high = find_max(&highs[i + 1 - fastk_period..=i]);
2218
let min_low = find_min(&lows[i + 1 - fastk_period..=i]);
@@ -27,6 +23,25 @@ pub fn stochs(
2723
Some(((closes[i] - min_low) / (max_high - min_low)) * 100.0)
2824
};
2925
}
26+
27+
raw_k
28+
}
29+
30+
pub fn stoch_percent_k(
31+
highs: &[f64],
32+
lows: &[f64],
33+
closes: &[f64],
34+
fastk_period: usize,
35+
slowk_period: usize,
36+
) -> Vec<Option<f64>> {
37+
let len = closes.len();
38+
let mut percent_k = vec![None; len];
39+
40+
if len < fastk_period {
41+
return percent_k;
42+
}
43+
44+
let raw_k = stochs_raw_k(highs, lows, closes, fastk_period);
3045
for i in (fastk_period + slowk_period - 2)..len {
3146
let slice = &raw_k[i + 1 - slowk_period..=i];
3247
let valid_values: Vec<f64> = slice.iter().filter_map(|&x| x).collect();
@@ -37,6 +52,34 @@ pub fn stochs(
3752
};
3853
}
3954

55+
percent_k
56+
}
57+
58+
pub fn stoch_percent_d(
59+
highs: &[f64],
60+
lows: &[f64],
61+
closes: &[f64],
62+
fastk_period: usize,
63+
slowk_period: usize,
64+
slowd_period: usize,
65+
) -> Vec<Option<f64>> {
66+
let percent_k = stoch_percent_k(highs, lows, closes, fastk_period, slowk_period);
67+
stoch_percent_d_from_k(&percent_k, fastk_period, slowk_period, slowd_period)
68+
}
69+
70+
fn stoch_percent_d_from_k(
71+
percent_k: &[Option<f64>],
72+
fastk_period: usize,
73+
slowk_period: usize,
74+
slowd_period: usize,
75+
) -> Vec<Option<f64>> {
76+
let len = percent_k.len();
77+
let mut percent_d = vec![None; len];
78+
79+
if len < fastk_period {
80+
return percent_d;
81+
}
82+
4083
for i in (fastk_period + slowk_period + slowd_period - 3)..len {
4184
let slice = &percent_k[i + 1 - slowd_period..=i];
4285
let valid_values: Vec<f64> = slice.iter().filter_map(|&x| x).collect();
@@ -47,6 +90,19 @@ pub fn stochs(
4790
};
4891
}
4992

93+
percent_d
94+
}
95+
96+
pub fn stochs(
97+
highs: &[f64],
98+
lows: &[f64],
99+
closes: &[f64],
100+
fastk_period: usize,
101+
slowk_period: usize,
102+
slowd_period: usize,
103+
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
104+
let percent_k = stoch_percent_k(highs, lows, closes, fastk_period, slowk_period);
105+
let percent_d = stoch_percent_d_from_k(&percent_k, fastk_period, slowk_period, slowd_period);
50106
(percent_k, percent_d)
51107
}
52108

polars/src/expressions.rs

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ use polars::prelude::*;
22
use pyo3_polars::derive::polars_expr;
33
use serde::Deserialize;
44
use techr::{
5-
bband as techr_bband, disparity as techr_disparity, ema as techr_ema,
5+
bband_lower as techr_bband_lower, bband_middle as techr_bband_middle,
6+
bband_upper as techr_bband_upper, disparity as techr_disparity, ema as techr_ema,
67
ichimoku_base_line as techr_ichimoku_base_line,
78
ichimoku_conversion_line as techr_ichimoku_conversion_line,
89
ichimoku_lagging_span as techr_ichimoku_lagging_span,
910
ichimoku_leading_span_a as techr_ichimoku_leading_span_a,
10-
ichimoku_leading_span_b as techr_ichimoku_leading_span_b, macd as techr_macd, sma as techr_sma,
11-
stochf as techr_stochf, stochs as techr_stochs, wma as techr_wma,
11+
ichimoku_leading_span_b as techr_ichimoku_leading_span_b,
12+
macd_histogram as techr_macd_histogram, macd_line as techr_macd_line,
13+
macd_signal as techr_macd_signal, sma as techr_sma, stoch_percent_d as techr_stoch_percent_d,
14+
stoch_percent_k as techr_stoch_percent_k, stochf_percent_d as techr_stochf_percent_d,
15+
stochf_percent_k as techr_stochf_percent_k, wma as techr_wma,
1216
};
1317

1418
#[derive(Deserialize)]
@@ -116,19 +120,18 @@ fn disparity(inputs: &[Series], kwargs: PeriodKwargs) -> PolarsResult<Series> {
116120
#[polars_expr(output_type=Float64)]
117121
fn macd(inputs: &[Series], kwargs: FastSlowKwargs) -> PolarsResult<Series> {
118122
let input = series_to_f64_vec(&inputs[0])?;
119-
let (macd_line, _, _) = techr_macd(
123+
let macd_line = techr_macd_line(
120124
&input,
121125
kwargs.fast_period as usize,
122126
kwargs.slow_period as usize,
123-
9,
124127
);
125128
Ok(option_vec_to_series(macd_line))
126129
}
127130

128131
#[polars_expr(output_type=Float64)]
129132
fn macd_signal(inputs: &[Series], kwargs: FastSlowSignalKwargs) -> PolarsResult<Series> {
130133
let input = series_to_f64_vec(&inputs[0])?;
131-
let (_, signal_line, _) = techr_macd(
134+
let signal_line = techr_macd_signal(
132135
&input,
133136
kwargs.fast_period as usize,
134137
kwargs.slow_period as usize,
@@ -140,7 +143,7 @@ fn macd_signal(inputs: &[Series], kwargs: FastSlowSignalKwargs) -> PolarsResult<
140143
#[polars_expr(output_type=Float64)]
141144
fn macd_hist(inputs: &[Series], kwargs: FastSlowSignalKwargs) -> PolarsResult<Series> {
142145
let input = series_to_f64_vec(&inputs[0])?;
143-
let (_, _, histogram) = techr_macd(
146+
let histogram = techr_macd_histogram(
144147
&input,
145148
kwargs.fast_period as usize,
146149
kwargs.slow_period as usize,
@@ -152,21 +155,21 @@ fn macd_hist(inputs: &[Series], kwargs: FastSlowSignalKwargs) -> PolarsResult<Se
152155
#[polars_expr(output_type=Float64)]
153156
fn bband_middle(inputs: &[Series], kwargs: PeriodKwargs) -> PolarsResult<Series> {
154157
let input = series_to_f64_vec(&inputs[0])?;
155-
let (_, middle, _) = techr_bband(&input, kwargs.period as usize, None);
158+
let middle = techr_bband_middle(&input, kwargs.period as usize);
156159
Ok(option_vec_to_series(middle))
157160
}
158161

159162
#[polars_expr(output_type=Float64)]
160163
fn bband_lower(inputs: &[Series], kwargs: BBandKwargs) -> PolarsResult<Series> {
161164
let input = series_to_f64_vec(&inputs[0])?;
162-
let (_, _, lower) = techr_bband(&input, kwargs.period as usize, Some(kwargs.sigma));
165+
let lower = techr_bband_lower(&input, kwargs.period as usize, Some(kwargs.sigma));
163166
Ok(option_vec_to_series(lower))
164167
}
165168

166169
#[polars_expr(output_type=Float64)]
167170
fn bband_upper(inputs: &[Series], kwargs: BBandKwargs) -> PolarsResult<Series> {
168171
let input = series_to_f64_vec(&inputs[0])?;
169-
let (upper, _, _) = techr_bband(&input, kwargs.period as usize, Some(kwargs.sigma));
172+
let upper = techr_bband_upper(&input, kwargs.period as usize, Some(kwargs.sigma));
170173
Ok(option_vec_to_series(upper))
171174
}
172175

@@ -175,13 +178,7 @@ fn stochf_percent_k(inputs: &[Series], kwargs: StochFKwargs) -> PolarsResult<Ser
175178
let highs = series_to_f64_vec(&inputs[0])?;
176179
let lows = series_to_f64_vec(&inputs[1])?;
177180
let closes = series_to_f64_vec(&inputs[2])?;
178-
let (percent_k, _) = techr_stochf(
179-
&highs,
180-
&lows,
181-
&closes,
182-
kwargs.fastk_period as usize,
183-
kwargs.fastd_period as usize,
184-
);
181+
let percent_k = techr_stochf_percent_k(&highs, &lows, &closes, kwargs.fastk_period as usize);
185182
Ok(option_vec_to_series(percent_k))
186183
}
187184

@@ -190,10 +187,9 @@ fn stochf_percent_d(inputs: &[Series], kwargs: StochFKwargs) -> PolarsResult<Ser
190187
let highs = series_to_f64_vec(&inputs[0])?;
191188
let lows = series_to_f64_vec(&inputs[1])?;
192189
let closes = series_to_f64_vec(&inputs[2])?;
193-
let (_, percent_d) = techr_stochf(
194-
&highs,
195-
&lows,
196-
&closes,
190+
let percent_k = techr_stochf_percent_k(&highs, &lows, &closes, kwargs.fastk_period as usize);
191+
let percent_d = techr_stochf_percent_d(
192+
&percent_k,
197193
kwargs.fastk_period as usize,
198194
kwargs.fastd_period as usize,
199195
);
@@ -205,13 +201,12 @@ fn stoch_percent_k(inputs: &[Series], kwargs: StochKwargs) -> PolarsResult<Serie
205201
let highs = series_to_f64_vec(&inputs[0])?;
206202
let lows = series_to_f64_vec(&inputs[1])?;
207203
let closes = series_to_f64_vec(&inputs[2])?;
208-
let (percent_k, _) = techr_stochs(
204+
let percent_k = techr_stoch_percent_k(
209205
&highs,
210206
&lows,
211207
&closes,
212208
kwargs.fastk_period as usize,
213209
kwargs.slowk_period as usize,
214-
kwargs.slowd_period as usize,
215210
);
216211
Ok(option_vec_to_series(percent_k))
217212
}
@@ -221,7 +216,7 @@ fn stoch_percent_d(inputs: &[Series], kwargs: StochKwargs) -> PolarsResult<Serie
221216
let highs = series_to_f64_vec(&inputs[0])?;
222217
let lows = series_to_f64_vec(&inputs[1])?;
223218
let closes = series_to_f64_vec(&inputs[2])?;
224-
let (_, percent_d) = techr_stochs(
219+
let percent_d = techr_stoch_percent_d(
225220
&highs,
226221
&lows,
227222
&closes,

0 commit comments

Comments
 (0)