ta_lib_in_rust/indicators/math/
mod.rs1use polars::prelude::*;
2
3pub fn calculate_add(df: &DataFrame, col1: &str, col2: &str) -> PolarsResult<Series> {
15 if !df.schema().contains(col1) || !df.schema().contains(col2) {
16 return Err(PolarsError::ComputeError(
17 format!("Addition requires both {col1} and {col2} columns").into(),
18 ));
19 }
20
21 let series1 = df.column(col1)?.f64()?;
22 let series2 = df.column(col2)?.f64()?;
23
24 let result = series1 + series2;
25
26 Ok(result.with_name(format!("{col1}_add_{col2}").into()).into())
27}
28
29pub fn calculate_sub(df: &DataFrame, col1: &str, col2: &str) -> PolarsResult<Series> {
41 if !df.schema().contains(col1) || !df.schema().contains(col2) {
42 return Err(PolarsError::ComputeError(
43 format!("Subtraction requires both {col1} and {col2} columns").into(),
44 ));
45 }
46
47 let series1 = df.column(col1)?.f64()?;
48 let series2 = df.column(col2)?.f64()?;
49
50 let result = series1 - series2;
51
52 Ok(result.with_name(format!("{col1}_sub_{col2}").into()).into())
53}
54
55pub fn calculate_mult(df: &DataFrame, col1: &str, col2: &str) -> PolarsResult<Series> {
67 if !df.schema().contains(col1) || !df.schema().contains(col2) {
68 return Err(PolarsError::ComputeError(
69 format!("Multiplication requires both {col1} and {col2} columns").into(),
70 ));
71 }
72
73 let series1 = df.column(col1)?.f64()?;
74 let series2 = df.column(col2)?.f64()?;
75
76 let result = series1 * series2;
77
78 Ok(result
79 .with_name(format!("{col1}_mult_{col2}").into())
80 .into())
81}
82
83pub fn calculate_div(df: &DataFrame, col1: &str, col2: &str) -> PolarsResult<Series> {
95 if !df.schema().contains(col1) || !df.schema().contains(col2) {
96 return Err(PolarsError::ComputeError(
97 format!("Division requires both {col1} and {col2} columns").into(),
98 ));
99 }
100
101 let series1 = df.column(col1)?.f64()?;
102 let series2 = df.column(col2)?.f64()?;
103
104 let mut div_values = Vec::with_capacity(df.height());
106
107 for i in 0..df.height() {
108 let num = series1.get(i).unwrap_or(f64::NAN);
109 let denom = series2.get(i).unwrap_or(f64::NAN);
110
111 if denom != 0.0 && !denom.is_nan() && !num.is_nan() {
112 div_values.push(num / denom);
113 } else {
114 div_values.push(f64::NAN);
115 }
116 }
117
118 Ok(Series::new(format!("{col1}_div_{col2}").into(), div_values))
119}
120
121pub fn calculate_max(df: &DataFrame, column: &str, window: usize) -> PolarsResult<Series> {
133 if !df.schema().contains(column) {
134 return Err(PolarsError::ComputeError(
135 format!("MAX calculation requires {column} column").into(),
136 ));
137 }
138
139 let series = df.column(column)?.f64()?;
140
141 let mut max_values = Vec::with_capacity(df.height());
142
143 for _i in 0..window - 1 {
145 max_values.push(f64::NAN);
146 }
147
148 for i in window - 1..df.height() {
150 let mut max_val = f64::NEG_INFINITY;
151 let mut all_nan = true;
152
153 for j in 0..window {
154 let val = series.get(i - j).unwrap_or(f64::NAN);
155 if !val.is_nan() {
156 max_val = max_val.max(val);
157 all_nan = false;
158 }
159 }
160
161 if all_nan {
162 max_values.push(f64::NAN);
163 } else {
164 max_values.push(max_val);
165 }
166 }
167
168 Ok(Series::new(
169 format!("{column}_max_{window}").into(),
170 max_values,
171 ))
172}
173
174pub fn calculate_min(df: &DataFrame, column: &str, window: usize) -> PolarsResult<Series> {
186 if !df.schema().contains(column) {
187 return Err(PolarsError::ComputeError(
188 format!("MIN calculation requires {column} column").into(),
189 ));
190 }
191
192 let series = df.column(column)?.f64()?;
193
194 let mut min_values = Vec::with_capacity(df.height());
195
196 for _i in 0..window - 1 {
198 min_values.push(f64::NAN);
199 }
200
201 for i in window - 1..df.height() {
203 let mut min_val = f64::INFINITY;
204 let mut all_nan = true;
205
206 for j in 0..window {
207 let val = series.get(i - j).unwrap_or(f64::NAN);
208 if !val.is_nan() {
209 min_val = min_val.min(val);
210 all_nan = false;
211 }
212 }
213
214 if all_nan {
215 min_values.push(f64::NAN);
216 } else {
217 min_values.push(min_val);
218 }
219 }
220
221 Ok(Series::new(
222 format!("{column}_min_{window}").into(),
223 min_values,
224 ))
225}
226
227pub fn calculate_sum(df: &DataFrame, column: &str, window: usize) -> PolarsResult<Series> {
239 if !df.schema().contains(column) {
240 return Err(PolarsError::ComputeError(
241 format!("SUM calculation requires {column} column").into(),
242 ));
243 }
244
245 let series = df.column(column)?.f64()?;
246
247 let mut sum_values = Vec::with_capacity(df.height());
248
249 for _i in 0..window - 1 {
251 sum_values.push(f64::NAN);
252 }
253
254 for i in window - 1..df.height() {
256 let mut sum = 0.0;
257 let mut all_nan = true;
258
259 for j in 0..window {
260 let val = series.get(i - j).unwrap_or(f64::NAN);
261 if !val.is_nan() {
262 sum += val;
263 all_nan = false;
264 }
265 }
266
267 if all_nan {
268 sum_values.push(f64::NAN);
269 } else {
270 sum_values.push(sum);
271 }
272 }
273
274 Ok(Series::new(
275 format!("{column}_sum_{window}").into(),
276 sum_values,
277 ))
278}
279
280pub fn calculate_rolling_sum(
292 df: &DataFrame,
293 column_name: &str,
294 window: usize,
295) -> PolarsResult<Series> {
296 let column = df.column(column_name)?.f64()?;
298 let n = column.len();
299
300 let mut result = Vec::with_capacity(n);
302
303 for _i in 0..window - 1 {
305 result.push(f64::NAN);
306 }
307
308 for i in window - 1..n {
310 let mut sum = 0.0;
311 for j in 0..window {
312 sum += column.get(i - j).unwrap_or(0.0);
313 }
314 result.push(sum);
315 }
316
317 Ok(Series::new(
319 format!("{}_sum{}", column_name, window).into(),
320 result,
321 ))
322}
323
324pub fn calculate_rolling_avg(
336 df: &DataFrame,
337 column_name: &str,
338 window: usize,
339) -> PolarsResult<Series> {
340 let column = df.column(column_name)?.f64()?;
342 let n = column.len();
343
344 let mut result = Vec::with_capacity(n);
346
347 for _i in 0..window - 1 {
349 result.push(f64::NAN);
350 }
351
352 for i in window - 1..n {
354 let mut sum = 0.0;
355 for j in 0..window {
356 sum += column.get(i - j).unwrap_or(0.0);
357 }
358 result.push(sum / window as f64);
359 }
360
361 Ok(Series::new(
363 format!("{}_avg{}", column_name, window).into(),
364 result,
365 ))
366}
367
368pub fn calculate_rolling_std(
380 df: &DataFrame,
381 column_name: &str,
382 window: usize,
383) -> PolarsResult<Series> {
384 let column = df.column(column_name)?.f64()?;
386 let n = column.len();
387
388 let mut result = Vec::with_capacity(n);
390
391 for _i in 0..window - 1 {
393 result.push(f64::NAN);
394 }
395
396 for i in window - 1..n {
398 let mut sum = 0.0;
399 let mut sum_sq = 0.0;
400
401 for j in 0..window {
402 let value = column.get(i - j).unwrap_or(0.0);
403 sum += value;
404 sum_sq += value * value;
405 }
406
407 let avg = sum / window as f64;
408 let variance = if window > 1 {
409 (sum_sq - sum * avg) / (window as f64 - 1.0)
410 } else {
411 0.0
412 };
413
414 if variance < 0.0 {
415 result.push(0.0);
418 } else {
419 result.push(variance.sqrt());
420 }
421 }
422
423 Ok(Series::new(
425 format!("{}_std{}", column_name, window).into(),
426 result,
427 ))
428}
429
430pub fn calculate_rate_of_change(
442 df: &DataFrame,
443 column_name: &str,
444 period: usize,
445) -> PolarsResult<Series> {
446 let column = df.column(column_name)?.f64()?;
448 let n = column.len();
449
450 let mut result = Vec::with_capacity(n);
452
453 for _ in 0..period {
455 result.push(f64::NAN);
456 }
457
458 for i in period..n {
460 let current_value = column.get(i).unwrap_or(0.0);
461 let previous_value = column.get(i - period).unwrap_or(1.0); if previous_value == 0.0 {
464 result.push(0.0); } else {
466 let roc = ((current_value - previous_value) / previous_value) * 100.0;
467 result.push(roc);
468 }
469 }
470
471 Ok(Series::new(
473 format!("{}_roc{}", column_name, period).into(),
474 result,
475 ))
476}