1use polars::prelude::*;
2use quantwave_core::traits::Next;
3use quantwave_core::*;
4
5pub mod prelude {
6 pub use crate::{QuantWaveExt, QuantWaveNamespace};
7}
8
9pub trait QuantWaveExt {
10 fn ta(&self) -> QuantWaveNamespace<'_>;
11}
12
13pub struct QuantWaveNamespace<'a>(&'a LazyFrame);
14
15impl<'a> QuantWaveNamespace<'a> {
16 pub fn acos(self, name: &str) -> LazyFrame {
17 self.math_transform_1_in_1_out::<ACOS>(name, "acos")
18 }
19 pub fn asin(self, name: &str) -> LazyFrame {
20 self.math_transform_1_in_1_out::<ASIN>(name, "asin")
21 }
22 pub fn atan(self, name: &str) -> LazyFrame {
23 self.math_transform_1_in_1_out::<ATAN>(name, "atan")
24 }
25 pub fn ceil(self, name: &str) -> LazyFrame {
26 self.math_transform_1_in_1_out::<CEIL>(name, "ceil")
27 }
28 pub fn cos(self, name: &str) -> LazyFrame {
29 self.math_transform_1_in_1_out::<COS>(name, "cos")
30 }
31 pub fn cosh(self, name: &str) -> LazyFrame {
32 self.math_transform_1_in_1_out::<COSH>(name, "cosh")
33 }
34 pub fn exp(self, name: &str) -> LazyFrame {
35 self.math_transform_1_in_1_out::<EXP>(name, "exp")
36 }
37 pub fn floor(self, name: &str) -> LazyFrame {
38 self.math_transform_1_in_1_out::<FLOOR>(name, "floor")
39 }
40 pub fn ln(self, name: &str) -> LazyFrame {
41 self.math_transform_1_in_1_out::<LN>(name, "ln")
42 }
43 pub fn log10(self, name: &str) -> LazyFrame {
44 self.math_transform_1_in_1_out::<LOG10>(name, "log10")
45 }
46 pub fn sin(self, name: &str) -> LazyFrame {
47 self.math_transform_1_in_1_out::<SIN>(name, "sin")
48 }
49 pub fn sinh(self, name: &str) -> LazyFrame {
50 self.math_transform_1_in_1_out::<SINH>(name, "sinh")
51 }
52 pub fn sqrt(self, name: &str) -> LazyFrame {
53 self.math_transform_1_in_1_out::<SQRT>(name, "sqrt")
54 }
55 pub fn tan(self, name: &str) -> LazyFrame {
56 self.math_transform_1_in_1_out::<TAN>(name, "tan")
57 }
58 pub fn tanh(self, name: &str) -> LazyFrame {
59 self.math_transform_1_in_1_out::<TANH>(name, "tanh")
60 }
61
62 pub fn add(self, in1: &str, in2: &str) -> LazyFrame {
63 self.math_operator_2_in_1_out::<ADD>(in1, in2, "add")
64 }
65 pub fn sub(self, in1: &str, in2: &str) -> LazyFrame {
66 self.math_operator_2_in_1_out::<SUB>(in1, in2, "sub")
67 }
68 pub fn mult(self, in1: &str, in2: &str) -> LazyFrame {
69 self.math_operator_2_in_1_out::<MULT>(in1, in2, "mult")
70 }
71 pub fn div(self, in1: &str, in2: &str) -> LazyFrame {
72 self.math_operator_2_in_1_out::<DIV>(in1, in2, "div")
73 }
74
75 pub fn max(self, name: &str, period: usize) -> LazyFrame {
76 self.math_operator_1_in_1_out_period::<MAX>(name, period, "max")
77 }
78 pub fn maxindex(self, name: &str, period: usize) -> LazyFrame {
79 self.math_operator_1_in_1_out_period::<MAXINDEX>(name, period, "maxindex")
80 }
81 pub fn min(self, name: &str, period: usize) -> LazyFrame {
82 self.math_operator_1_in_1_out_period::<MIN>(name, period, "min")
83 }
84 pub fn minindex(self, name: &str, period: usize) -> LazyFrame {
85 self.math_operator_1_in_1_out_period::<MININDEX>(name, period, "minindex")
86 }
87 pub fn sum(self, name: &str, period: usize) -> LazyFrame {
88 self.math_operator_1_in_1_out_period::<SUM>(name, period, "sum")
89 }
90
91 pub fn sma(self, name: &str, period: usize) -> LazyFrame {
92 self.math_operator_1_in_1_out_period::<SMA>(name, period, "sma")
93 }
94 pub fn ema(self, name: &str, period: usize) -> LazyFrame {
95 self.math_operator_1_in_1_out_period::<EMA>(name, period, "ema")
96 }
97 pub fn wma(self, name: &str, period: usize) -> LazyFrame {
98 self.math_operator_1_in_1_out_period::<WMA>(name, period, "wma")
99 }
100 pub fn dema(self, name: &str, period: usize) -> LazyFrame {
101 self.math_operator_1_in_1_out_period::<DEMA>(name, period, "dema")
102 }
103 pub fn trima(self, name: &str, period: usize) -> LazyFrame {
104 self.math_operator_1_in_1_out_period::<TRIMA>(name, period, "trima")
105 }
106 pub fn kama(self, name: &str, period: usize) -> LazyFrame {
107 self.math_operator_1_in_1_out_period::<KAMA>(name, period, "kama")
108 }
109 pub fn midpoint(self, name: &str, period: usize) -> LazyFrame {
110 self.math_operator_1_in_1_out_period::<MIDPOINT>(name, period, "midpoint")
111 }
112 pub fn ht_trendline(self, name: &str) -> LazyFrame {
113 self.math_transform_1_in_1_out::<HT_TRENDLINE>(name, "ht_trendline")
114 }
115 pub fn midprice(self, high: &str, low: &str, period: usize) -> LazyFrame {
116 self.math_operator_2_in_1_out_period::<MIDPRICE>(high, low, period, "midprice")
117 }
118
119 pub fn rsi(self, name: &str, period: usize) -> LazyFrame {
120 self.math_operator_1_in_1_out_period::<RSI>(name, period, "rsi")
121 }
122 pub fn mom(self, name: &str, period: usize) -> LazyFrame {
123 self.math_operator_1_in_1_out_period::<MOM>(name, period, "mom")
124 }
125 pub fn roc(self, name: &str, period: usize) -> LazyFrame {
126 self.math_operator_1_in_1_out_period::<ROC>(name, period, "roc")
127 }
128 pub fn rocp(self, name: &str, period: usize) -> LazyFrame {
129 self.math_operator_1_in_1_out_period::<ROCP>(name, period, "rocp")
130 }
131 pub fn rocr(self, name: &str, period: usize) -> LazyFrame {
132 self.math_operator_1_in_1_out_period::<ROCR>(name, period, "rocr")
133 }
134 pub fn rocr100(self, name: &str, period: usize) -> LazyFrame {
135 self.math_operator_1_in_1_out_period::<ROCR100>(name, period, "rocr100")
136 }
137 pub fn trix(self, name: &str, period: usize) -> LazyFrame {
138 self.math_operator_1_in_1_out_period::<TRIX>(name, period, "trix")
139 }
140 pub fn cmo(self, name: &str, period: usize) -> LazyFrame {
141 self.math_operator_1_in_1_out_period::<CMO>(name, period, "cmo")
142 }
143
144 pub fn adx(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
145 self.ta_3_in_1_out_period::<ADX>(high, low, close, period, "adx")
146 }
147 pub fn adxr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
148 self.ta_3_in_1_out_period::<ADXR>(high, low, close, period, "adxr")
149 }
150 pub fn cci(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
151 self.ta_3_in_1_out_period::<CCI>(high, low, close, period, "cci")
152 }
153 pub fn willr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
154 self.ta_3_in_1_out_period::<WILLR>(high, low, close, period, "willr")
155 }
156 pub fn dx(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
157 self.ta_3_in_1_out_period::<DX>(high, low, close, period, "dx")
158 }
159 pub fn plus_di(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
160 self.ta_3_in_1_out_period::<PLUS_DI>(high, low, close, period, "plus_di")
161 }
162 pub fn minus_di(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
163 self.ta_3_in_1_out_period::<MINUS_DI>(high, low, close, period, "minus_di")
164 }
165
166 pub fn ta_atr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
167 self.ta_3_in_1_out_period::<TaATR>(high, low, close, period, "ta_atr")
168 }
169 pub fn ta_natr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
170 self.ta_3_in_1_out_period::<TaNATR>(high, low, close, period, "ta_natr")
171 }
172 pub fn ta_trange(self, high: &str, low: &str, close: &str) -> LazyFrame {
173 self.ta_3_in_1_out_default::<TaTRANGE>(high, low, close, "ta_trange")
174 }
175
176 pub fn obv(self, close: &str, volume: &str) -> LazyFrame {
177 self.math_operator_2_in_1_out::<OBV>(close, volume, "obv")
178 }
179 pub fn ad(self, high: &str, low: &str, close: &str, volume: &str) -> LazyFrame {
180 self.ta_4_in_1_out_default::<AD>(high, low, close, volume, "ad")
181 }
182 pub fn adosc(
183 self,
184 high: &str,
185 low: &str,
186 close: &str,
187 volume: &str,
188 fast: usize,
189 slow: usize,
190 ) -> LazyFrame {
191 let high_str = high.to_string();
192 let low_str = low.to_string();
193 let close_str = close.to_string();
194 let volume_str = volume.to_string();
195 self.0.clone().with_columns([as_struct(vec![
196 col(&high_str),
197 col(&low_str),
198 col(&close_str),
199 col(&volume_str),
200 ])
201 .map(
202 move |s| {
203 let ca = s.struct_()?;
204 let s_h = ca.field_by_name(&high_str)?;
205 let s_l = ca.field_by_name(&low_str)?;
206 let s_c = ca.field_by_name(&close_str)?;
207 let s_v = ca.field_by_name(&volume_str)?;
208
209 let high = s_h.f64()?;
210 let low = s_l.f64()?;
211 let close = s_c.f64()?;
212 let volume = s_v.f64()?;
213
214 let mut indicator = ADOSC::new(fast, slow);
215 let mut values = Vec::with_capacity(s.len());
216
217 for i in 0..s.len() {
218 let h = high.get(i).unwrap_or(f64::NAN);
219 let l = low.get(i).unwrap_or(f64::NAN);
220 let c = close.get(i).unwrap_or(f64::NAN);
221 let v = volume.get(i).unwrap_or(f64::NAN);
222 values.push(indicator.next((h, l, c, v)));
223 }
224
225 Ok(Some(Column::from(Series::new("adosc".into(), values))))
226 },
227 GetOutput::from_type(DataType::Float64),
228 )
229 .alias("adosc")])
230 }
231
232 pub fn aroon(self, high: &str, low: &str, period: usize) -> LazyFrame {
233 let high_str = high.to_string();
234 let low_str = low.to_string();
235 self.0
236 .clone()
237 .with_columns([as_struct(vec![col(&high_str), col(&low_str)])
238 .map(
239 move |s| {
240 let ca = s.struct_()?;
241 let s_h = ca.field_by_name(&high_str)?;
242 let s_l = ca.field_by_name(&low_str)?;
243 let high = s_h.f64()?;
244 let low = s_l.f64()?;
245
246 let mut indicator = AROON::new(period);
247 let mut up_vals = Vec::with_capacity(s.len());
248 let mut down_vals = Vec::with_capacity(s.len());
249
250 for i in 0..s.len() {
251 let h = high.get(i).unwrap_or(f64::NAN);
252 let l = low.get(i).unwrap_or(f64::NAN);
253 let (up, down) = indicator.next((h, l));
254 up_vals.push(up);
255 down_vals.push(down);
256 }
257
258 let s_up = Series::new("aroon_up".into(), up_vals);
259 let s_down = Series::new("aroon_down".into(), down_vals);
260 let struct_series = StructChunked::from_series(
261 "aroon_result".into(),
262 s.len(),
263 [s_up, s_down].iter(),
264 )?;
265 Ok(Some(Column::from(struct_series.into_series())))
266 },
267 GetOutput::from_type(DataType::Struct(vec![
268 Field::new("aroon_up".into(), DataType::Float64),
269 Field::new("aroon_down".into(), DataType::Float64),
270 ])),
271 )
272 .alias("aroon")])
273 }
274
275 #[allow(clippy::too_many_arguments)]
276 pub fn stoch(
277 self,
278 high: &str,
279 low: &str,
280 close: &str,
281 fastk: usize,
282 slowk: usize,
283 slowk_matype: talib::MaType,
284 slowd: usize,
285 slowd_matype: talib::MaType,
286 ) -> LazyFrame {
287 let high_str = high.to_string();
288 let low_str = low.to_string();
289 let close_str = close.to_string();
290 self.0.clone().with_columns([as_struct(vec![
291 col(&high_str),
292 col(&low_str),
293 col(&close_str),
294 ])
295 .map(
296 move |s| {
297 let ca = s.struct_()?;
298 let s_h = ca.field_by_name(&high_str)?;
299 let s_l = ca.field_by_name(&low_str)?;
300 let s_c = ca.field_by_name(&close_str)?;
301 let high = s_h.f64()?;
302 let low = s_l.f64()?;
303 let close = s_c.f64()?;
304
305 let mut indicator = STOCH::new(fastk, slowk, slowk_matype, slowd, slowd_matype);
306 let mut k_vals = Vec::with_capacity(s.len());
307 let mut d_vals = Vec::with_capacity(s.len());
308
309 for i in 0..s.len() {
310 let h = high.get(i).unwrap_or(f64::NAN);
311 let l = low.get(i).unwrap_or(f64::NAN);
312 let c = close.get(i).unwrap_or(f64::NAN);
313 let (k, d) = indicator.next((h, l, c));
314 k_vals.push(k);
315 d_vals.push(d);
316 }
317
318 let s_k = Series::new("slowk".into(), k_vals);
319 let s_d = Series::new("slowd".into(), d_vals);
320 let struct_series =
321 StructChunked::from_series("stoch_result".into(), s.len(), [s_k, s_d].iter())?;
322 Ok(Some(Column::from(struct_series.into_series())))
323 },
324 GetOutput::from_type(DataType::Struct(vec![
325 Field::new("slowk".into(), DataType::Float64),
326 Field::new("slowd".into(), DataType::Float64),
327 ])),
328 )
329 .alias("stoch")])
330 }
331
332 pub fn avgprice(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
333 self.ta_4_in_1_out_default::<AVGPRICE>(open, high, low, close, "avgprice")
334 }
335 pub fn medprice(self, high: &str, low: &str) -> LazyFrame {
336 self.math_operator_2_in_1_out::<MEDPRICE>(high, low, "medprice")
337 }
338 pub fn typprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
339 self.ta_3_in_1_out_default::<TYPPRICE>(high, low, close, "typprice")
340 }
341 pub fn wclprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
342 self.ta_3_in_1_out_default::<WCLPRICE>(high, low, close, "wclprice")
343 }
344
345 pub fn ht_dcperiod(self, name: &str) -> LazyFrame {
346 self.math_transform_1_in_1_out::<HT_DCPERIOD>(name, "ht_dcperiod")
347 }
348 pub fn ht_dcphase(self, name: &str) -> LazyFrame {
349 self.math_transform_1_in_1_out::<HT_DCPHASE>(name, "ht_dcphase")
350 }
351 pub fn ht_trendmode(self, name: &str) -> LazyFrame {
352 self.math_transform_1_in_1_out::<HT_TRENDMODE>(name, "ht_trendmode")
353 }
354
355 pub fn ta_stddev(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
356 let name_str = name.to_string();
357 self.0.clone().with_columns([col(&name_str)
358 .map(
359 move |s| {
360 let ca = s.f64()?;
361 let mut indicator = TaSTDDEV::new(period, nbdev);
362 let mut values = Vec::with_capacity(s.len());
363 for i in 0..s.len() {
364 let val = ca.get(i).unwrap_or(f64::NAN);
365 values.push(indicator.next(val));
366 }
367 Ok(Some(Column::from(Series::new("ta_stddev".into(), values))))
368 },
369 GetOutput::from_type(DataType::Float64),
370 )
371 .alias("ta_stddev")])
372 }
373 pub fn ta_var(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
374 let name_str = name.to_string();
375 self.0.clone().with_columns([col(&name_str)
376 .map(
377 move |s| {
378 let ca = s.f64()?;
379 let mut indicator = TaVAR::new(period, nbdev);
380 let mut values = Vec::with_capacity(s.len());
381 for i in 0..s.len() {
382 let val = ca.get(i).unwrap_or(f64::NAN);
383 values.push(indicator.next(val));
384 }
385 Ok(Some(Column::from(Series::new("ta_var".into(), values))))
386 },
387 GetOutput::from_type(DataType::Float64),
388 )
389 .alias("ta_var")])
390 }
391 pub fn ta_beta(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
392 self.math_operator_2_in_1_out_period::<TaBETA>(in1, in2, period, "ta_beta")
393 }
394 pub fn ta_correl(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
395 self.math_operator_2_in_1_out_period::<TaCORREL>(in1, in2, period, "ta_correl")
396 }
397 pub fn ta_linearreg(self, name: &str, period: usize) -> LazyFrame {
398 self.math_operator_1_in_1_out_period::<TaLINEARREG>(name, period, "ta_linearreg")
399 }
400 pub fn ta_linearreg_slope(self, name: &str, period: usize) -> LazyFrame {
401 self.math_operator_1_in_1_out_period::<TaLINEARREG_SLOPE>(
402 name,
403 period,
404 "ta_linearreg_slope",
405 )
406 }
407 pub fn ta_linearreg_intercept(self, name: &str, period: usize) -> LazyFrame {
408 self.math_operator_1_in_1_out_period::<TaLINEARREG_INTERCEPT>(
409 name,
410 period,
411 "ta_linearreg_intercept",
412 )
413 }
414 pub fn ta_linearreg_angle(self, name: &str, period: usize) -> LazyFrame {
415 self.math_operator_1_in_1_out_period::<TaLINEARREG_ANGLE>(
416 name,
417 period,
418 "ta_linearreg_angle",
419 )
420 }
421 pub fn ta_tsf(self, name: &str, period: usize) -> LazyFrame {
422 self.math_operator_1_in_1_out_period::<TaTSF>(name, period, "ta_tsf")
423 }
424
425 pub fn cdl_2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
426 self.ta_4_in_1_out_default::<CDL2CROWS>(open, high, low, close, "cdl_2crows")
427 }
428 pub fn cdl_3blackcrows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
429 self.ta_4_in_1_out_default::<CDL3BLACKCROWS>(open, high, low, close, "cdl_3blackcrows")
430 }
431 pub fn cdl_3inside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
432 self.ta_4_in_1_out_default::<CDL3INSIDE>(open, high, low, close, "cdl_3inside")
433 }
434 pub fn cdl_3linestrike(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
435 self.ta_4_in_1_out_default::<CDL3LINESTRIKE>(open, high, low, close, "cdl_3linestrike")
436 }
437 pub fn cdl_3outside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
438 self.ta_4_in_1_out_default::<CDL3OUTSIDE>(open, high, low, close, "cdl_3outside")
439 }
440 pub fn cdl_3starsinsouth(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
441 self.ta_4_in_1_out_default::<CDL3STARSINSOUTH>(open, high, low, close, "cdl_3starsinsouth")
442 }
443 pub fn cdl_3whitesoldiers(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
444 self.ta_4_in_1_out_default::<CDL3WHITESOLDIERS>(
445 open,
446 high,
447 low,
448 close,
449 "cdl_3whitesoldiers",
450 )
451 }
452 pub fn cdl_abandonedbaby(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
453 self.ta_4_in_1_out_default::<CDLABANDONEDBABY>(open, high, low, close, "cdl_abandonedbaby")
454 }
455 pub fn cdl_advanceblock(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
456 self.ta_4_in_1_out_default::<CDLADVANCEBLOCK>(open, high, low, close, "cdl_advanceblock")
457 }
458 pub fn cdl_belthold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
459 self.ta_4_in_1_out_default::<CDLBELTHOLD>(open, high, low, close, "cdl_belthold")
460 }
461 pub fn cdl_breakaway(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
462 self.ta_4_in_1_out_default::<CDLBREAKAWAY>(open, high, low, close, "cdl_breakaway")
463 }
464 pub fn cdl_closingmarubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
465 self.ta_4_in_1_out_default::<CDLCLOSINGMARUBOZU>(
466 open,
467 high,
468 low,
469 close,
470 "cdl_closingmarubozu",
471 )
472 }
473 pub fn cdl_concealbabyswall(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
474 self.ta_4_in_1_out_default::<CDLCONCEALBABYSWALL>(
475 open,
476 high,
477 low,
478 close,
479 "cdl_concealbabyswall",
480 )
481 }
482 pub fn cdl_counterattack(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
483 self.ta_4_in_1_out_default::<CDLCOUNTERATTACK>(open, high, low, close, "cdl_counterattack")
484 }
485 pub fn cdl_darkcloudcover(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
486 self.ta_4_in_1_out_default::<CDLDARKCLOUDCOVER>(
487 open,
488 high,
489 low,
490 close,
491 "cdl_darkcloudcover",
492 )
493 }
494 pub fn cdl_doji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
495 self.ta_4_in_1_out_default::<CDLDOJI>(open, high, low, close, "cdl_doji")
496 }
497 pub fn cdl_dojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
498 self.ta_4_in_1_out_default::<CDLDOJISTAR>(open, high, low, close, "cdl_dojistar")
499 }
500 pub fn cdl_dragonflydoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
501 self.ta_4_in_1_out_default::<CDLDRAGONFLYDOJI>(open, high, low, close, "cdl_dragonflydoji")
502 }
503 pub fn cdl_engulfing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
504 self.ta_4_in_1_out_default::<CDLENGULFING>(open, high, low, close, "cdl_engulfing")
505 }
506 pub fn cdl_eveningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
507 self.ta_4_in_1_out_default::<CDLEVENINGDOJISTAR>(
508 open,
509 high,
510 low,
511 close,
512 "cdl_eveningdojistar",
513 )
514 }
515 pub fn cdl_eveningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
516 self.ta_4_in_1_out_default::<CDLEVENINGSTAR>(open, high, low, close, "cdl_eveningstar")
517 }
518 pub fn cdl_gapsidesidewhite(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
519 self.ta_4_in_1_out_default::<CDLGAPSIDESIDEWHITE>(
520 open,
521 high,
522 low,
523 close,
524 "cdl_gapsidesidewhite",
525 )
526 }
527 pub fn cdl_gravestonedoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
528 self.ta_4_in_1_out_default::<CDLGRAVESTONEDOJI>(
529 open,
530 high,
531 low,
532 close,
533 "cdl_gravestonedoji",
534 )
535 }
536 pub fn cdl_hammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
537 self.ta_4_in_1_out_default::<CDLHAMMER>(open, high, low, close, "cdl_hammer")
538 }
539 pub fn cdl_hangingman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
540 self.ta_4_in_1_out_default::<CDLHANGINGMAN>(open, high, low, close, "cdl_hangingman")
541 }
542 pub fn cdl_harami(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
543 self.ta_4_in_1_out_default::<CDLHARAMI>(open, high, low, close, "cdl_harami")
544 }
545 pub fn cdl_haramicross(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
546 self.ta_4_in_1_out_default::<CDLHARAMICROSS>(open, high, low, close, "cdl_haramicross")
547 }
548 pub fn cdl_highwave(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
549 self.ta_4_in_1_out_default::<CDLHIGHWAVE>(open, high, low, close, "cdl_highwave")
550 }
551 pub fn cdl_hikkake(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
552 self.ta_4_in_1_out_default::<CDLHIKKAKE>(open, high, low, close, "cdl_hikkake")
553 }
554 pub fn cdl_hikkakemod(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
555 self.ta_4_in_1_out_default::<CDLHIKKAKEMOD>(open, high, low, close, "cdl_hikkakemod")
556 }
557 pub fn cdl_homingpigeon(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
558 self.ta_4_in_1_out_default::<CDLHOMINGPIGEON>(open, high, low, close, "cdl_homingpigeon")
559 }
560 pub fn cdl_identical3crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
561 self.ta_4_in_1_out_default::<CDLIDENTICAL3CROWS>(
562 open,
563 high,
564 low,
565 close,
566 "cdl_identical3crows",
567 )
568 }
569 pub fn cdl_inneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
570 self.ta_4_in_1_out_default::<CDLINNECK>(open, high, low, close, "cdl_inneck")
571 }
572 pub fn cdl_invertedhammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
573 self.ta_4_in_1_out_default::<CDLINVERTEDHAMMER>(
574 open,
575 high,
576 low,
577 close,
578 "cdl_invertedhammer",
579 )
580 }
581 pub fn cdl_kicking(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
582 self.ta_4_in_1_out_default::<CDLKICKING>(open, high, low, close, "cdl_kicking")
583 }
584 pub fn cdl_kickingbylength(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
585 self.ta_4_in_1_out_default::<CDLKICKINGBYLENGTH>(
586 open,
587 high,
588 low,
589 close,
590 "cdl_kickingbylength",
591 )
592 }
593 pub fn cdl_ladderbottom(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
594 self.ta_4_in_1_out_default::<CDLLADDERBOTTOM>(open, high, low, close, "cdl_ladderbottom")
595 }
596 pub fn cdl_longleggeddoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
597 self.ta_4_in_1_out_default::<CDLLONGLEGGEDDOJI>(
598 open,
599 high,
600 low,
601 close,
602 "cdl_longleggeddoji",
603 )
604 }
605 pub fn cdl_longline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
606 self.ta_4_in_1_out_default::<CDLLONGLINE>(open, high, low, close, "cdl_longline")
607 }
608 pub fn cdl_marubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
609 self.ta_4_in_1_out_default::<CDLMARUBOZU>(open, high, low, close, "cdl_marubozu")
610 }
611 pub fn cdl_matchinglow(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
612 self.ta_4_in_1_out_default::<CDLMATCHINGLOW>(open, high, low, close, "cdl_matchinglow")
613 }
614 pub fn cdl_mathold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
615 self.ta_4_in_1_out_default::<CDLMATHOLD>(open, high, low, close, "cdl_mathold")
616 }
617 pub fn cdl_morningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
618 self.ta_4_in_1_out_default::<CDLMORNINGDOJISTAR>(
619 open,
620 high,
621 low,
622 close,
623 "cdl_morningdojistar",
624 )
625 }
626 pub fn cdl_morningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
627 self.ta_4_in_1_out_default::<CDLMORNINGSTAR>(open, high, low, close, "cdl_morningstar")
628 }
629 pub fn cdl_onneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
630 self.ta_4_in_1_out_default::<CDLONNECK>(open, high, low, close, "cdl_onneck")
631 }
632 pub fn cdl_piercing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
633 self.ta_4_in_1_out_default::<CDLPIERCING>(open, high, low, close, "cdl_piercing")
634 }
635 pub fn cdl_rickshawman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
636 self.ta_4_in_1_out_default::<CDLRICKSHAWMAN>(open, high, low, close, "cdl_rickshawman")
637 }
638 pub fn cdl_risefall3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
639 self.ta_4_in_1_out_default::<CDLRISEFALL3METHODS>(
640 open,
641 high,
642 low,
643 close,
644 "cdl_risefall3methods",
645 )
646 }
647 pub fn cdl_separatinglines(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
648 self.ta_4_in_1_out_default::<CDLSEPARATINGLINES>(
649 open,
650 high,
651 low,
652 close,
653 "cdl_separatinglines",
654 )
655 }
656 pub fn cdl_shootingstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
657 self.ta_4_in_1_out_default::<CDLSHOOTINGSTAR>(open, high, low, close, "cdl_shootingstar")
658 }
659 pub fn cdl_shortline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
660 self.ta_4_in_1_out_default::<CDLSHORTLINE>(open, high, low, close, "cdl_shortline")
661 }
662 pub fn cdl_spinningtop(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
663 self.ta_4_in_1_out_default::<CDLSPINNINGTOP>(open, high, low, close, "cdl_spinningtop")
664 }
665 pub fn cdl_stalledpattern(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
666 self.ta_4_in_1_out_default::<CDLSTALLEDPATTERN>(
667 open,
668 high,
669 low,
670 close,
671 "cdl_stalledpattern",
672 )
673 }
674 pub fn cdl_sticksandwich(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
675 self.ta_4_in_1_out_default::<CDLSTICKSANDWICH>(open, high, low, close, "cdl_sticksandwich")
676 }
677 pub fn cdl_takuri(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
678 self.ta_4_in_1_out_default::<CDLTAKURI>(open, high, low, close, "cdl_takuri")
679 }
680 pub fn cdl_tasukigap(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
681 self.ta_4_in_1_out_default::<CDLTASUKIGAP>(open, high, low, close, "cdl_tasukigap")
682 }
683 pub fn cdl_thrusting(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
684 self.ta_4_in_1_out_default::<CDLTHRUSTING>(open, high, low, close, "cdl_thrusting")
685 }
686 pub fn cdl_tristar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
687 self.ta_4_in_1_out_default::<CDLTRISTAR>(open, high, low, close, "cdl_tristar")
688 }
689 pub fn cdl_unique3river(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
690 self.ta_4_in_1_out_default::<CDLUNIQUE3RIVER>(open, high, low, close, "cdl_unique3river")
691 }
692 pub fn cdl_upsidegap2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
693 self.ta_4_in_1_out_default::<CDLUPSIDEGAP2CROWS>(
694 open,
695 high,
696 low,
697 close,
698 "cdl_upsidegap2crows",
699 )
700 }
701 pub fn cdl_xsidegap3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
702 self.ta_4_in_1_out_default::<CDLXSIDEGAP3METHODS>(
703 open,
704 high,
705 low,
706 close,
707 "cdl_xsidegap3methods",
708 )
709 }
710
711 pub fn macd(self, name: &str, fast: usize, slow: usize, signal: usize) -> LazyFrame {
712 let name_str = name.to_string();
713 self.0.clone().with_columns([col(&name_str)
714 .map(
715 move |s| {
716 let ca = s.f64()?;
717 let mut indicator = MACD::new(fast, slow, signal);
718 let mut macd_vals = Vec::with_capacity(s.len());
719 let mut signal_vals = Vec::with_capacity(s.len());
720 let mut hist_vals = Vec::with_capacity(s.len());
721
722 for i in 0..s.len() {
723 let val = ca.get(i).unwrap_or(f64::NAN);
724 let (m, s_val, h) = indicator.next(val);
725 macd_vals.push(m);
726 signal_vals.push(s_val);
727 hist_vals.push(h);
728 }
729
730 let s_macd = Series::new("macd".into(), macd_vals);
731 let s_signal = Series::new("macd_signal".into(), signal_vals);
732 let s_hist = Series::new("macd_hist".into(), hist_vals);
733
734 let struct_series = StructChunked::from_series(
735 "macd_result".into(),
736 s.len(),
737 [s_macd, s_signal, s_hist].iter(),
738 )?;
739 Ok(Some(Column::from(struct_series.into_series())))
740 },
741 GetOutput::from_type(DataType::Struct(vec![
742 Field::new("macd".into(), DataType::Float64),
743 Field::new("macd_signal".into(), DataType::Float64),
744 Field::new("macd_hist".into(), DataType::Float64),
745 ])),
746 )
747 .alias("macd")])
748 }
749
750 pub fn bbands(
751 self,
752 name: &str,
753 period: usize,
754 nbdevup: f64,
755 nbdevdn: f64,
756 matype: talib::MaType,
757 ) -> LazyFrame {
758 let name_str = name.to_string();
759 self.0.clone().with_columns([col(&name_str)
760 .map(
761 move |s| {
762 let ca = s.f64()?;
763 let mut indicator = BBANDS::new(period, nbdevup, nbdevdn, matype);
764 let mut upper_vals = Vec::with_capacity(s.len());
765 let mut middle_vals = Vec::with_capacity(s.len());
766 let mut lower_vals = Vec::with_capacity(s.len());
767
768 for i in 0..s.len() {
769 let val = ca.get(i).unwrap_or(f64::NAN);
770 let (u, m, l) = indicator.next(val);
771 upper_vals.push(u);
772 middle_vals.push(m);
773 lower_vals.push(l);
774 }
775
776 let s_upper = Series::new("upper".into(), upper_vals);
777 let s_middle = Series::new("middle".into(), middle_vals);
778 let s_lower = Series::new("lower".into(), lower_vals);
779
780 let struct_series = StructChunked::from_series(
781 "bbands_result".into(),
782 s.len(),
783 [s_upper, s_middle, s_lower].iter(),
784 )?;
785 Ok(Some(Column::from(struct_series.into_series())))
786 },
787 GetOutput::from_type(DataType::Struct(vec![
788 Field::new("upper".into(), DataType::Float64),
789 Field::new("middle".into(), DataType::Float64),
790 Field::new("lower".into(), DataType::Float64),
791 ])),
792 )
793 .alias("bbands")])
794 }
795
796 fn ta_3_in_1_out_period<I>(
797 self,
798 in1: &str,
799 in2: &str,
800 in3: &str,
801 period: usize,
802 output_name: &str,
803 ) -> LazyFrame
804 where
805 I: Next<(f64, f64, f64), Output = f64> + Send + Sync + 'static,
806 I: From<usize>,
807 {
808 let in1_str = in1.to_string();
809 let in2_str = in2.to_string();
810 let in3_str = in3.to_string();
811 let output_name_str = output_name.to_string();
812 let output_name_for_closure = output_name_str.clone();
813 self.0.clone().with_columns(
814 [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
815 .map(
816 move |s| {
817 let ca = s.struct_()?;
818 let s1 = ca.field_by_name(&in1_str)?;
819 let s2 = ca.field_by_name(&in2_str)?;
820 let s3 = ca.field_by_name(&in3_str)?;
821
822 let ca1 = s1.f64()?;
823 let ca2 = s2.f64()?;
824 let ca3 = s3.f64()?;
825
826 let mut indicator = I::from(period);
827 let mut values = Vec::with_capacity(s.len());
828
829 for i in 0..s.len() {
830 let v1 = ca1.get(i).unwrap_or(f64::NAN);
831 let v2 = ca2.get(i).unwrap_or(f64::NAN);
832 let v3 = ca3.get(i).unwrap_or(f64::NAN);
833 values.push(indicator.next((v1, v2, v3)));
834 }
835
836 Ok(Some(Column::from(Series::new(
837 output_name_for_closure.clone().into(),
838 values,
839 ))))
840 },
841 GetOutput::from_type(DataType::Float64),
842 )
843 .alias(&output_name_str)],
844 )
845 }
846
847 fn ta_3_in_1_out_default<I>(
848 self,
849 in1: &str,
850 in2: &str,
851 in3: &str,
852 output_name: &str,
853 ) -> LazyFrame
854 where
855 I: Next<(f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
856 {
857 let in1_str = in1.to_string();
858 let in2_str = in2.to_string();
859 let in3_str = in3.to_string();
860 let output_name_str = output_name.to_string();
861 let output_name_for_closure = output_name_str.clone();
862 self.0.clone().with_columns(
863 [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
864 .map(
865 move |s| {
866 let ca = s.struct_()?;
867 let s1 = ca.field_by_name(&in1_str)?;
868 let s2 = ca.field_by_name(&in2_str)?;
869 let s3 = ca.field_by_name(&in3_str)?;
870
871 let ca1 = s1.f64()?;
872 let ca2 = s2.f64()?;
873 let ca3 = s3.f64()?;
874
875 let mut indicator = I::default();
876 let mut values = Vec::with_capacity(s.len());
877
878 for i in 0..s.len() {
879 let v1 = ca1.get(i).unwrap_or(f64::NAN);
880 let v2 = ca2.get(i).unwrap_or(f64::NAN);
881 let v3 = ca3.get(i).unwrap_or(f64::NAN);
882 values.push(indicator.next((v1, v2, v3)));
883 }
884
885 Ok(Some(Column::from(Series::new(
886 output_name_for_closure.clone().into(),
887 values,
888 ))))
889 },
890 GetOutput::from_type(DataType::Float64),
891 )
892 .alias(&output_name_str)],
893 )
894 }
895
896 fn ta_4_in_1_out_default<I>(
897 self,
898 in1: &str,
899 in2: &str,
900 in3: &str,
901 in4: &str,
902 output_name: &str,
903 ) -> LazyFrame
904 where
905 I: Next<(f64, f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
906 {
907 let in1_str = in1.to_string();
908 let in2_str = in2.to_string();
909 let in3_str = in3.to_string();
910 let in4_str = in4.to_string();
911 let output_name_str = output_name.to_string();
912 let output_name_for_closure = output_name_str.clone();
913 self.0.clone().with_columns([as_struct(vec![
914 col(&in1_str),
915 col(&in2_str),
916 col(&in3_str),
917 col(&in4_str),
918 ])
919 .map(
920 move |s| {
921 let ca = s.struct_()?;
922 let s1 = ca.field_by_name(&in1_str)?;
923 let s2 = ca.field_by_name(&in2_str)?;
924 let s3 = ca.field_by_name(&in3_str)?;
925 let s4 = ca.field_by_name(&in4_str)?;
926
927 let ca1 = s1.f64()?;
928 let ca2 = s2.f64()?;
929 let ca3 = s3.f64()?;
930 let ca4 = s4.f64()?;
931
932 let mut indicator = I::default();
933 let mut values = Vec::with_capacity(s.len());
934
935 for i in 0..s.len() {
936 let v1 = ca1.get(i).unwrap_or(f64::NAN);
937 let v2 = ca2.get(i).unwrap_or(f64::NAN);
938 let v3 = ca3.get(i).unwrap_or(f64::NAN);
939 let v4 = ca4.get(i).unwrap_or(f64::NAN);
940 values.push(indicator.next((v1, v2, v3, v4)));
941 }
942
943 Ok(Some(Column::from(Series::new(
944 output_name_for_closure.clone().into(),
945 values,
946 ))))
947 },
948 GetOutput::from_type(DataType::Float64),
949 )
950 .alias(&output_name_str)])
951 }
952
953 fn math_transform_1_in_1_out<I>(self, name: &str, output_name: &str) -> LazyFrame
954 where
955 I: Next<f64, Output = f64> + Default + Send + Sync + 'static,
956 {
957 let name = name.to_string();
958 let output_name_str = output_name.to_string();
959 let output_name_for_closure = output_name_str.clone();
960 self.0.clone().with_columns([col(&name)
961 .map(
962 move |s| {
963 let ca = s.f64()?;
964 let mut indicator = I::default();
965 let mut values = Vec::with_capacity(s.len());
966
967 for i in 0..s.len() {
968 let val = ca.get(i).unwrap_or(f64::NAN);
969 values.push(indicator.next(val));
970 }
971
972 Ok(Some(Column::from(Series::new(
973 output_name_for_closure.clone().into(),
974 values,
975 ))))
976 },
977 GetOutput::from_type(DataType::Float64),
978 )
979 .alias(&output_name_str)])
980 }
981
982 fn math_operator_2_in_1_out<I>(self, in1: &str, in2: &str, output_name: &str) -> LazyFrame
983 where
984 I: Next<(f64, f64), Output = f64> + Default + Send + Sync + 'static,
985 {
986 let in1_str = in1.to_string();
987 let in2_str = in2.to_string();
988 let output_name_str = output_name.to_string();
989 let output_name_for_closure = output_name_str.clone();
990 self.0
991 .clone()
992 .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
993 .map(
994 move |s| {
995 let ca = s.struct_()?;
996 let s1 = ca.field_by_name(&in1_str)?;
997 let s2 = ca.field_by_name(&in2_str)?;
998
999 let ca1 = s1.f64()?;
1000 let ca2 = s2.f64()?;
1001
1002 let mut indicator = I::default();
1003 let mut values = Vec::with_capacity(s.len());
1004
1005 for i in 0..s.len() {
1006 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1007 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1008 values.push(indicator.next((v1, v2)));
1009 }
1010
1011 Ok(Some(Column::from(Series::new(
1012 output_name_for_closure.clone().into(),
1013 values,
1014 ))))
1015 },
1016 GetOutput::from_type(DataType::Float64),
1017 )
1018 .alias(&output_name_str)])
1019 }
1020
1021 fn math_operator_1_in_1_out_period<I>(
1022 self,
1023 name: &str,
1024 period: usize,
1025 output_name: &str,
1026 ) -> LazyFrame
1027 where
1028 I: Next<f64, Output = f64> + Send + Sync + 'static,
1029 I: From<usize>,
1030 {
1031 let name = name.to_string();
1032 let output_name_str = output_name.to_string();
1033 let output_name_for_closure = output_name_str.clone();
1034 self.0.clone().with_columns([col(&name)
1035 .map(
1036 move |s| {
1037 let ca = s.f64()?;
1038 let mut indicator = I::from(period);
1039 let mut values = Vec::with_capacity(s.len());
1040
1041 for i in 0..s.len() {
1042 let val = ca.get(i).unwrap_or(f64::NAN);
1043 values.push(indicator.next(val));
1044 }
1045
1046 Ok(Some(Column::from(Series::new(
1047 output_name_for_closure.clone().into(),
1048 values,
1049 ))))
1050 },
1051 GetOutput::from_type(DataType::Float64),
1052 )
1053 .alias(&output_name_str)])
1054 }
1055
1056 fn math_operator_2_in_1_out_period<I>(
1057 self,
1058 in1: &str,
1059 in2: &str,
1060 period: usize,
1061 output_name: &str,
1062 ) -> LazyFrame
1063 where
1064 I: Next<(f64, f64), Output = f64> + Send + Sync + 'static,
1065 I: From<usize>,
1066 {
1067 let in1_str = in1.to_string();
1068 let in2_str = in2.to_string();
1069 let output_name_str = output_name.to_string();
1070 let output_name_for_closure = output_name_str.clone();
1071 self.0
1072 .clone()
1073 .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1074 .map(
1075 move |s| {
1076 let ca = s.struct_()?;
1077 let s1 = ca.field_by_name(&in1_str)?;
1078 let s2 = ca.field_by_name(&in2_str)?;
1079
1080 let ca1 = s1.f64()?;
1081 let ca2 = s2.f64()?;
1082
1083 let mut indicator = I::from(period);
1084 let mut values = Vec::with_capacity(s.len());
1085
1086 for i in 0..s.len() {
1087 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1088 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1089 values.push(indicator.next((v1, v2)));
1090 }
1091
1092 Ok(Some(Column::from(Series::new(
1093 output_name_for_closure.clone().into(),
1094 values,
1095 ))))
1096 },
1097 GetOutput::from_type(DataType::Float64),
1098 )
1099 .alias(&output_name_str)])
1100 }
1101
1102 pub fn supertrend(self, period: usize, multiplier: f64) -> LazyFrame {
1103 self.0
1104 .clone()
1105 .with_columns([as_struct(vec![col("high"), col("low"), col("close")])
1106 .map(
1107 move |s| {
1108 let ca = s.struct_()?;
1109 let s_high = ca.field_by_name("high")?;
1110 let s_low = ca.field_by_name("low")?;
1111 let s_close = ca.field_by_name("close")?;
1112
1113 let high = s_high.f64()?;
1114 let low = s_low.f64()?;
1115 let close = s_close.f64()?;
1116
1117 let mut st = SuperTrend::new(period, multiplier);
1118 let mut values = Vec::with_capacity(s.len());
1119 let mut directions = Vec::with_capacity(s.len());
1120
1121 for i in 0..s.len() {
1122 let h = high.get(i).unwrap_or(0.0);
1123 let l = low.get(i).unwrap_or(0.0);
1124 let c = close.get(i).unwrap_or(0.0);
1125 let (val, dir) = st.next((h, l, c));
1126 values.push(val);
1127 directions.push(dir as f64);
1128 }
1129
1130 let st_series = Series::new("supertrend".into(), values);
1131 let dir_series = Series::new("supertrend_direction".into(), directions);
1132
1133 let out = StructChunked::from_series(
1134 "supertrend_output".into(),
1135 s.len(),
1136 [st_series, dir_series].iter(),
1137 )?;
1138 Ok(Some(Column::from(out.into_series())))
1139 },
1140 GetOutput::from_type(DataType::Struct(vec![
1141 Field::new("supertrend".into(), DataType::Float64),
1142 Field::new("supertrend_direction".into(), DataType::Float64),
1143 ])),
1144 )
1145 .alias("supertrend_data")])
1146 }
1147
1148 pub fn anchored_vwap(self, price: &str, volume: &str, anchor: &str) -> LazyFrame {
1149 let price = price.to_string();
1150 let volume = volume.to_string();
1151 let anchor = anchor.to_string();
1152
1153 self.0
1154 .clone()
1155 .with_columns([as_struct(vec![col(&price), col(&volume), col(&anchor)])
1156 .map(
1157 move |s| {
1158 let ca = s.struct_()?;
1159 let s_price = ca.field_by_name(&price)?;
1160 let s_volume = ca.field_by_name(&volume)?;
1161 let s_anchor = ca.field_by_name(&anchor)?;
1162
1163 let price = s_price.f64()?;
1164 let volume = s_volume.f64()?;
1165 let anchor = s_anchor.bool()?;
1166
1167 let mut avwap = quantwave_core::AnchoredVWAP::new();
1168 let mut values = Vec::with_capacity(s.len());
1169
1170 for i in 0..s.len() {
1171 let p = price.get(i).unwrap_or(0.0);
1172 let v = volume.get(i).unwrap_or(0.0);
1173 let a = anchor.get(i).unwrap_or(false);
1174 values.push(avwap.next((p, v, a)));
1175 }
1176
1177 Ok(Some(Column::from(Series::new(
1178 "anchored_vwap".into(),
1179 values,
1180 ))))
1181 },
1182 GetOutput::from_type(DataType::Float64),
1183 )
1184 .alias("avwap")])
1185 }
1186
1187 pub fn hma(self, name: &str, period: usize) -> LazyFrame {
1188 let name = name.to_string();
1189 self.0.clone().with_columns([col(&name)
1190 .map(
1191 move |s| {
1192 let ca = s.f64()?;
1193 let mut hma = quantwave_core::HMA::new(period);
1194 let mut values = Vec::with_capacity(s.len());
1195
1196 for i in 0..s.len() {
1197 let val = ca.get(i).unwrap_or(0.0);
1198 values.push(hma.next(val));
1199 }
1200
1201 Ok(Some(Column::from(Series::new("hma".into(), values))))
1202 },
1203 GetOutput::from_type(DataType::Float64),
1204 )
1205 .alias("hma")])
1206 }
1207
1208 pub fn vpn(
1209 self,
1210 high: &str,
1211 low: &str,
1212 close: &str,
1213 volume: &str,
1214 period: usize,
1215 smooth_period: usize,
1216 ) -> LazyFrame {
1217 let high_str = high.to_string();
1218 let low_str = low.to_string();
1219 let close_str = close.to_string();
1220 let volume_str = volume.to_string();
1221
1222 self.0.clone().with_columns([as_struct(vec![
1223 col(&high_str),
1224 col(&low_str),
1225 col(&close_str),
1226 col(&volume_str),
1227 ])
1228 .map(
1229 move |s| {
1230 let ca = s.struct_()?;
1231 let s_h = ca.field_by_name(&high_str)?;
1232 let s_l = ca.field_by_name(&low_str)?;
1233 let s_c = ca.field_by_name(&close_str)?;
1234 let s_v = ca.field_by_name(&volume_str)?;
1235
1236 let high = s_h.f64()?;
1237 let low = s_l.f64()?;
1238 let close = s_c.f64()?;
1239 let volume = s_v.f64()?;
1240
1241 let mut indicator = quantwave_core::VPNIndicator::new(period, smooth_period);
1242 let mut values = Vec::with_capacity(s.len());
1243
1244 for i in 0..s.len() {
1245 let h = high.get(i).unwrap_or(f64::NAN);
1246 let l = low.get(i).unwrap_or(f64::NAN);
1247 let c = close.get(i).unwrap_or(f64::NAN);
1248 let v = volume.get(i).unwrap_or(f64::NAN);
1249 values.push(indicator.next((h, l, c, v)));
1250 }
1251
1252 Ok(Some(Column::from(Series::new("vpn".into(), values))))
1253 },
1254 GetOutput::from_type(DataType::Float64),
1255 )
1256 .alias("vpn")])
1257 }
1258
1259 pub fn gap_momentum(
1260 self,
1261 open: &str,
1262 close: &str,
1263 period: usize,
1264 signal_period: usize,
1265 ) -> LazyFrame {
1266 let open_str = open.to_string();
1267 let close_str = close.to_string();
1268
1269 self.0.clone().with_columns([as_struct(vec![
1270 col(&open_str),
1271 col(&close_str),
1272 ])
1273 .map(
1274 move |s| {
1275 let ca = s.struct_()?;
1276 let s_o = ca.field_by_name(&open_str)?;
1277 let s_c = ca.field_by_name(&close_str)?;
1278
1279 let open = s_o.f64()?;
1280 let close = s_c.f64()?;
1281
1282 let mut indicator = quantwave_core::GapMomentum::new(period, signal_period);
1283 let mut ratio_vals = Vec::with_capacity(s.len());
1284 let mut signal_vals = Vec::with_capacity(s.len());
1285
1286 for i in 0..s.len() {
1287 let o = open.get(i).unwrap_or(f64::NAN);
1288 let c = close.get(i).unwrap_or(f64::NAN);
1289 let (ratio, signal) = indicator.next((o, c));
1290 ratio_vals.push(ratio);
1291 signal_vals.push(signal);
1292 }
1293
1294 let s_ratio = Series::new("gap_ratio".into(), ratio_vals);
1295 let s_signal = Series::new("gap_signal".into(), signal_vals);
1296 let struct_series = StructChunked::from_series(
1297 "gap_momentum_result".into(),
1298 s.len(),
1299 [s_ratio, s_signal].iter(),
1300 )?;
1301 Ok(Some(Column::from(struct_series.into_series())))
1302 },
1303 GetOutput::from_type(DataType::Struct(vec![
1304 Field::new("gap_ratio".into(), DataType::Float64),
1305 Field::new("gap_signal".into(), DataType::Float64),
1306 ])),
1307 )
1308 .alias("gap_momentum")])
1309 }
1310
1311 pub fn autotune_filter(self, name: &str, window: usize, bandwidth: f64) -> LazyFrame {
1312 let name_str = name.to_string();
1313 self.0.clone().with_columns([col(&name_str)
1314 .map(
1315 move |s| {
1316 let ca = s.f64()?;
1317 let mut indicator = quantwave_core::AutoTuneFilter::new(window, bandwidth);
1318 let mut values = Vec::with_capacity(s.len());
1319
1320 for i in 0..s.len() {
1321 let val = ca.get(i).unwrap_or(f64::NAN);
1322 values.push(indicator.next(val));
1323 }
1324
1325 Ok(Some(Column::from(Series::new("autotune".into(), values))))
1326 },
1327 GetOutput::from_type(DataType::Float64),
1328 )
1329 .alias("autotune")])
1330 }
1331
1332 pub fn adaptive_ema(
1333 self,
1334 high: &str,
1335 low: &str,
1336 close: &str,
1337 period: usize,
1338 pds: usize,
1339 ) -> LazyFrame {
1340 let h_str = high.to_string();
1341 let l_str = low.to_string();
1342 let c_str = close.to_string();
1343
1344 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1345 .map(
1346 move |s| {
1347 let ca = s.struct_()?;
1348 let f_h = ca.field_by_name(&h_str)?;
1349 let high = f_h.f64()?;
1350 let f_l = ca.field_by_name(&l_str)?;
1351 let low = f_l.f64()?;
1352 let f_c = ca.field_by_name(&c_str)?;
1353 let close = f_c.f64()?;
1354
1355 let mut indicator = quantwave_core::AdaptiveEMA::new(period, pds);
1356 let mut values = Vec::with_capacity(s.len());
1357
1358 for i in 0..s.len() {
1359 let h = high.get(i).unwrap_or(f64::NAN);
1360 let l = low.get(i).unwrap_or(f64::NAN);
1361 let c = close.get(i).unwrap_or(f64::NAN);
1362 values.push(indicator.next((h, l, c)));
1363 }
1364
1365 Ok(Some(Column::from(Series::new("adaptive_ema".into(), values))))
1366 },
1367 GetOutput::from_type(DataType::Float64),
1368 )
1369 .alias("adaptive_ema")])
1370 }
1371
1372 pub fn tradj_ema(
1373 self,
1374 high: &str,
1375 low: &str,
1376 close: &str,
1377 period: usize,
1378 pds: usize,
1379 mltp: f64,
1380 ) -> LazyFrame {
1381 let h_str = high.to_string();
1382 let l_str = low.to_string();
1383 let c_str = close.to_string();
1384
1385 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1386 .map(
1387 move |s| {
1388 let ca = s.struct_()?;
1389 let f_h = ca.field_by_name(&h_str)?;
1390 let high = f_h.f64()?;
1391 let f_l = ca.field_by_name(&l_str)?;
1392 let low = f_l.f64()?;
1393 let f_c = ca.field_by_name(&c_str)?;
1394 let close = f_c.f64()?;
1395
1396 let mut indicator = quantwave_core::TRAdjEMA::new(period, pds, mltp);
1397 let mut values = Vec::with_capacity(s.len());
1398
1399 for i in 0..s.len() {
1400 let h = high.get(i).unwrap_or(f64::NAN);
1401 let l = low.get(i).unwrap_or(f64::NAN);
1402 let c = close.get(i).unwrap_or(f64::NAN);
1403 values.push(indicator.next((h, l, c)));
1404 }
1405
1406 Ok(Some(Column::from(Series::new("tradj_ema".into(), values))))
1407 },
1408 GetOutput::from_type(DataType::Float64),
1409 )
1410 .alias("tradj_ema")])
1411 }
1412
1413 pub fn obvm(
1414 self,
1415 high: &str,
1416 low: &str,
1417 close: &str,
1418 volume: &str,
1419 obvm_period: usize,
1420 signal_period: usize,
1421 ) -> LazyFrame {
1422 let h_str = high.to_string();
1423 let l_str = low.to_string();
1424 let c_str = close.to_string();
1425 let v_str = volume.to_string();
1426
1427 self.0.clone().with_columns([as_struct(vec![
1428 col(&h_str),
1429 col(&l_str),
1430 col(&c_str),
1431 col(&v_str),
1432 ])
1433 .map(
1434 move |s| {
1435 let ca = s.struct_()?;
1436 let f_h = ca.field_by_name(&h_str)?;
1437 let high = f_h.f64()?;
1438 let f_l = ca.field_by_name(&l_str)?;
1439 let low = f_l.f64()?;
1440 let f_c = ca.field_by_name(&c_str)?;
1441 let close = f_c.f64()?;
1442 let f_v = ca.field_by_name(&v_str)?;
1443 let volume = f_v.f64()?;
1444
1445 let mut indicator = quantwave_core::Obvm::new(obvm_period, signal_period);
1446 let mut obvm_vals = Vec::with_capacity(s.len());
1447 let mut signal_vals = Vec::with_capacity(s.len());
1448
1449 for i in 0..s.len() {
1450 let h = high.get(i).unwrap_or(f64::NAN);
1451 let l = low.get(i).unwrap_or(f64::NAN);
1452 let c = close.get(i).unwrap_or(f64::NAN);
1453 let v = volume.get(i).unwrap_or(f64::NAN);
1454 let (o, sig) = indicator.next((h, l, c, v));
1455 obvm_vals.push(o);
1456 signal_vals.push(sig);
1457 }
1458
1459 let s_obvm = Series::new("obvm".into(), obvm_vals);
1460 let s_signal = Series::new("signal".into(), signal_vals);
1461 let struct_series = StructChunked::from_series(
1462 "obvm_data".into(),
1463 s.len(),
1464 [s_obvm, s_signal].iter(),
1465 )?;
1466 Ok(Some(Column::from(struct_series.into_series())))
1467 },
1468 GetOutput::from_type(DataType::Struct(vec![
1469 Field::new("obvm".into(), DataType::Float64),
1470 Field::new("signal".into(), DataType::Float64),
1471 ])),
1472 )
1473 .alias("obvm_data")])
1474 }
1475
1476 pub fn vfi(
1477 self,
1478 high: &str,
1479 low: &str,
1480 close: &str,
1481 volume: &str,
1482 period: usize,
1483 coef: f64,
1484 vcoef: f64,
1485 smoothing_period: usize,
1486 ) -> LazyFrame {
1487 let h_str = high.to_string();
1488 let l_str = low.to_string();
1489 let c_str = close.to_string();
1490 let v_str = volume.to_string();
1491
1492 self.0.clone().with_columns([as_struct(vec![
1493 col(&h_str),
1494 col(&l_str),
1495 col(&c_str),
1496 col(&v_str),
1497 ])
1498 .map(
1499 move |s| {
1500 let ca = s.struct_()?;
1501 let f_h = ca.field_by_name(&h_str)?;
1502 let high = f_h.f64()?;
1503 let f_l = ca.field_by_name(&l_str)?;
1504 let low = f_l.f64()?;
1505 let f_c = ca.field_by_name(&c_str)?;
1506 let close = f_c.f64()?;
1507 let f_v = ca.field_by_name(&v_str)?;
1508 let volume = f_v.f64()?;
1509
1510 let mut indicator = quantwave_core::Vfi::new(period, coef, vcoef, smoothing_period);
1511 let mut values = Vec::with_capacity(s.len());
1512
1513 for i in 0..s.len() {
1514 let h = high.get(i).unwrap_or(f64::NAN);
1515 let l = low.get(i).unwrap_or(f64::NAN);
1516 let c = close.get(i).unwrap_or(f64::NAN);
1517 let v = volume.get(i).unwrap_or(f64::NAN);
1518 values.push(indicator.next((h, l, c, v)));
1519 }
1520
1521 Ok(Some(Column::from(Series::new("vfi".into(), values))))
1522 },
1523 GetOutput::from_type(DataType::Float64),
1524 )
1525 .alias("vfi")])
1526 }
1527
1528 pub fn sve_volatility_bands(
1529 self,
1530 high: &str,
1531 low: &str,
1532 close: &str,
1533 bands_period: usize,
1534 bands_deviation: f64,
1535 low_band_adjust: f64,
1536 mid_line_length: usize,
1537 ) -> LazyFrame {
1538 let h_str = high.to_string();
1539 let l_str = low.to_string();
1540 let c_str = close.to_string();
1541
1542 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1543 .map(
1544 move |s| {
1545 let ca = s.struct_()?;
1546 let f_h = ca.field_by_name(&h_str)?;
1547 let high = f_h.f64()?;
1548 let f_l = ca.field_by_name(&l_str)?;
1549 let low = f_l.f64()?;
1550 let f_c = ca.field_by_name(&c_str)?;
1551 let close = f_c.f64()?;
1552
1553 let mut indicator = quantwave_core::SVEVolatilityBands::new(
1554 bands_period,
1555 bands_deviation,
1556 low_band_adjust,
1557 mid_line_length,
1558 );
1559 let mut upper_vals = Vec::with_capacity(s.len());
1560 let mut mid_vals = Vec::with_capacity(s.len());
1561 let mut lower_vals = Vec::with_capacity(s.len());
1562
1563 for i in 0..s.len() {
1564 let h = high.get(i).unwrap_or(f64::NAN);
1565 let l = low.get(i).unwrap_or(f64::NAN);
1566 let c = close.get(i).unwrap_or(f64::NAN);
1567 let (upper, mid, lower) = indicator.next((h, l, c));
1568 upper_vals.push(upper);
1569 mid_vals.push(mid);
1570 lower_vals.push(lower);
1571 }
1572
1573 let s_upper = Series::new("upper".into(), upper_vals);
1574 let s_mid = Series::new("middle".into(), mid_vals);
1575 let s_lower = Series::new("lower".into(), lower_vals);
1576 let struct_series = StructChunked::from_series(
1577 "sve_bands_data".into(),
1578 s.len(),
1579 [s_upper, s_mid, s_lower].iter(),
1580 )?;
1581 Ok(Some(Column::from(struct_series.into_series())))
1582 },
1583 GetOutput::from_type(DataType::Struct(vec![
1584 Field::new("upper".into(), DataType::Float64),
1585 Field::new("middle".into(), DataType::Float64),
1586 Field::new("lower".into(), DataType::Float64),
1587 ])),
1588 )
1589 .alias("sve_bands_data")])
1590 }
1591
1592 pub fn exp_dev_bands(
1593 self,
1594 name: &str,
1595 period: usize,
1596 multiplier: f64,
1597 use_sma: bool,
1598 ) -> LazyFrame {
1599 let name_str = name.to_string();
1600 self.0.clone().with_columns([col(&name_str)
1601 .map(
1602 move |s| {
1603 let ca = s.f64()?;
1604 let mut indicator = quantwave_core::ExpDevBands::new(period, multiplier, use_sma);
1605 let mut upper_vals = Vec::with_capacity(s.len());
1606 let mut mid_vals = Vec::with_capacity(s.len());
1607 let mut lower_vals = Vec::with_capacity(s.len());
1608
1609 for i in 0..s.len() {
1610 let val = ca.get(i).unwrap_or(f64::NAN);
1611 let (upper, mid, lower) = indicator.next(val);
1612 upper_vals.push(upper);
1613 mid_vals.push(mid);
1614 lower_vals.push(lower);
1615 }
1616
1617 let s_upper = Series::new("upper".into(), upper_vals);
1618 let s_mid = Series::new("middle".into(), mid_vals);
1619 let s_lower = Series::new("lower".into(), lower_vals);
1620 let struct_series = StructChunked::from_series(
1621 "exp_dev_bands_data".into(),
1622 s.len(),
1623 [s_upper, s_mid, s_lower].iter(),
1624 )?;
1625 Ok(Some(Column::from(struct_series.into_series())))
1626 },
1627 GetOutput::from_type(DataType::Struct(vec![
1628 Field::new("upper".into(), DataType::Float64),
1629 Field::new("middle".into(), DataType::Float64),
1630 Field::new("lower".into(), DataType::Float64),
1631 ])),
1632 )
1633 .alias("exp_dev_bands_data")])
1634 }
1635
1636 pub fn sdo(
1637 self,
1638 name: &str,
1639 lookback_period: usize,
1640 period: usize,
1641 ema_pds: usize,
1642 ) -> LazyFrame {
1643 let name_str = name.to_string();
1644 self.0.clone().with_columns([col(&name_str)
1645 .map(
1646 move |s| {
1647 let ca = s.f64()?;
1648 let mut indicator = quantwave_core::SDO::new(lookback_period, period, ema_pds);
1649 let mut values = Vec::with_capacity(s.len());
1650
1651 for i in 0..s.len() {
1652 let val = ca.get(i).unwrap_or(f64::NAN);
1653 values.push(indicator.next(val));
1654 }
1655
1656 Ok(Some(Column::from(Series::new("sdo".into(), values))))
1657 },
1658 GetOutput::from_type(DataType::Float64),
1659 )
1660 .alias("sdo")])
1661 }
1662
1663 pub fn rsmk(self, price: &str, benchmark: &str, length: usize, ema_length: usize) -> LazyFrame {
1664 let p_str = price.to_string();
1665 let b_str = benchmark.to_string();
1666
1667 self.0.clone().with_columns([as_struct(vec![col(&p_str), col(&b_str)])
1668 .map(
1669 move |s| {
1670 let ca = s.struct_()?;
1671 let f_p = ca.field_by_name(&p_str)?;
1672 let price = f_p.f64()?;
1673 let f_b = ca.field_by_name(&b_str)?;
1674 let benchmark = f_b.f64()?;
1675
1676 let mut indicator = quantwave_core::RSMK::new(length, ema_length);
1677 let mut values = Vec::with_capacity(s.len());
1678
1679 for i in 0..s.len() {
1680 let p = price.get(i).unwrap_or(f64::NAN);
1681 let b = benchmark.get(i).unwrap_or(f64::NAN);
1682 values.push(indicator.next((p, b)));
1683 }
1684
1685 Ok(Some(Column::from(Series::new("rsmk".into(), values))))
1686 },
1687 GetOutput::from_type(DataType::Float64),
1688 )
1689 .alias("rsmk")])
1690 }
1691
1692 pub fn rodc(
1693 self,
1694 name: &str,
1695 window_size: usize,
1696 threshold: f64,
1697 smooth_period: usize,
1698 ) -> LazyFrame {
1699 let name_str = name.to_string();
1700 self.0.clone().with_columns([col(&name_str)
1701 .map(
1702 move |s| {
1703 let ca = s.f64()?;
1704 let mut indicator = quantwave_core::RODC::new(window_size, threshold, smooth_period);
1705 let mut values = Vec::with_capacity(s.len());
1706
1707 for i in 0..s.len() {
1708 let val = ca.get(i).unwrap_or(f64::NAN);
1709 values.push(indicator.next(val));
1710 }
1711
1712 Ok(Some(Column::from(Series::new("rodc".into(), values))))
1713 },
1714 GetOutput::from_type(DataType::Float64),
1715 )
1716 .alias("rodc")])
1717 }
1718
1719 pub fn reverse_ema(self, name: &str, alpha: f64) -> LazyFrame {
1720 let name_str = name.to_string();
1721 self.0.clone().with_columns([col(&name_str)
1722 .map(
1723 move |s| {
1724 let ca = s.f64()?;
1725 let mut indicator = quantwave_core::ReverseEMA::new(alpha);
1726 let mut values = Vec::with_capacity(s.len());
1727
1728 for i in 0..s.len() {
1729 let val = ca.get(i).unwrap_or(f64::NAN);
1730 values.push(indicator.next(val));
1731 }
1732
1733 Ok(Some(Column::from(Series::new("reverse_ema".into(), values))))
1734 },
1735 GetOutput::from_type(DataType::Float64),
1736 )
1737 .alias("reverse_ema")])
1738 }
1739
1740 pub fn harrington_adx(
1741 self,
1742 high: &str,
1743 low: &str,
1744 close: &str,
1745 adx_length: usize,
1746 adx_smooth_length: usize,
1747 ) -> LazyFrame {
1748 let h_str = high.to_string();
1749 let l_str = low.to_string();
1750 let c_str = close.to_string();
1751
1752 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1753 .map(
1754 move |s| {
1755 let ca = s.struct_()?;
1756 let f_h = ca.field_by_name(&h_str)?;
1757 let high = f_h.f64()?;
1758 let f_l = ca.field_by_name(&l_str)?;
1759 let low = f_l.f64()?;
1760 let f_c = ca.field_by_name(&c_str)?;
1761 let close = f_c.f64()?;
1762
1763 let mut indicator = quantwave_core::HarringtonADXOscillator::new(adx_length, adx_smooth_length);
1764 let mut values = Vec::with_capacity(s.len());
1765
1766 for i in 0..s.len() {
1767 let h = high.get(i).unwrap_or(f64::NAN);
1768 let l = low.get(i).unwrap_or(f64::NAN);
1769 let c = close.get(i).unwrap_or(f64::NAN);
1770 values.push(indicator.next((h, l, c)));
1771 }
1772
1773 Ok(Some(Column::from(Series::new("harrington_adx".into(), values))))
1774 },
1775 GetOutput::from_type(DataType::Float64),
1776 )
1777 .alias("harrington_adx")])
1778 }
1779
1780 pub fn keltner_channels(
1781 self,
1782 high: &str,
1783 low: &str,
1784 close: &str,
1785 ema_period: usize,
1786 atr_period: usize,
1787 multiplier: f64,
1788 ) -> LazyFrame {
1789 let high = high.to_string();
1790 let low = low.to_string();
1791 let close = close.to_string();
1792
1793 self.0
1794 .clone()
1795 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1796 .map(
1797 move |s| {
1798 let ca = s.struct_()?;
1799 let s_high = ca.field_by_name(&high)?;
1800 let s_low = ca.field_by_name(&low)?;
1801 let s_close = ca.field_by_name(&close)?;
1802
1803 let high = s_high.f64()?;
1804 let low = s_low.f64()?;
1805 let close = s_close.f64()?;
1806
1807 let mut kc = quantwave_core::KeltnerChannels::new(
1808 ema_period, atr_period, multiplier,
1809 );
1810 let mut uppers = Vec::with_capacity(s.len());
1811 let mut middles = Vec::with_capacity(s.len());
1812 let mut lowers = Vec::with_capacity(s.len());
1813
1814 for i in 0..s.len() {
1815 let h = high.get(i).unwrap_or(0.0);
1816 let l = low.get(i).unwrap_or(0.0);
1817 let c = close.get(i).unwrap_or(0.0);
1818 let (upper, middle, lower) = kc.next((h, l, c));
1819 uppers.push(upper);
1820 middles.push(middle);
1821 lowers.push(lower);
1822 }
1823
1824 let upper_series = Series::new("upper".into(), uppers);
1825 let middle_series = Series::new("middle".into(), middles);
1826 let lower_series = Series::new("lower".into(), lowers);
1827
1828 let out = StructChunked::from_series(
1829 "keltner_output".into(),
1830 s.len(),
1831 [upper_series, middle_series, lower_series].iter(),
1832 )?;
1833 Ok(Some(Column::from(out.into_series())))
1834 },
1835 GetOutput::from_type(DataType::Struct(vec![
1836 Field::new("upper".into(), DataType::Float64),
1837 Field::new("middle".into(), DataType::Float64),
1838 Field::new("lower".into(), DataType::Float64),
1839 ])),
1840 )
1841 .alias("keltner_data")])
1842 }
1843
1844 pub fn alma(self, name: &str, period: usize, offset: f64, sigma: f64) -> LazyFrame {
1845 let name = name.to_string();
1846 self.0.clone().with_columns([col(&name)
1847 .map(
1848 move |s| {
1849 let ca = s.f64()?;
1850 let mut alma = quantwave_core::ALMA::new(period, offset, sigma);
1851 let mut values = Vec::with_capacity(s.len());
1852
1853 for i in 0..s.len() {
1854 let val = ca.get(i).unwrap_or(0.0);
1855 values.push(alma.next(val));
1856 }
1857
1858 Ok(Some(Column::from(Series::new("alma".into(), values))))
1859 },
1860 GetOutput::from_type(DataType::Float64),
1861 )
1862 .alias("alma")])
1863 }
1864
1865 pub fn donchian_channels(self, high: &str, low: &str, period: usize) -> LazyFrame {
1866 let high = high.to_string();
1867 let low = low.to_string();
1868
1869 self.0
1870 .clone()
1871 .with_columns([as_struct(vec![col(&high), col(&low)])
1872 .map(
1873 move |s| {
1874 let ca = s.struct_()?;
1875 let s_high = ca.field_by_name(&high)?;
1876 let s_low = ca.field_by_name(&low)?;
1877
1878 let high = s_high.f64()?;
1879 let low = s_low.f64()?;
1880
1881 let mut dc = quantwave_core::DonchianChannels::new(period);
1882 let mut uppers = Vec::with_capacity(s.len());
1883 let mut middles = Vec::with_capacity(s.len());
1884 let mut lowers = Vec::with_capacity(s.len());
1885
1886 for i in 0..s.len() {
1887 let h = high.get(i).unwrap_or(0.0);
1888 let l = low.get(i).unwrap_or(0.0);
1889 let (upper, middle, lower) = dc.next((h, l));
1890 uppers.push(upper);
1891 middles.push(middle);
1892 lowers.push(lower);
1893 }
1894
1895 let upper_series = Series::new("upper".into(), uppers);
1896 let middle_series = Series::new("middle".into(), middles);
1897 let lower_series = Series::new("lower".into(), lowers);
1898
1899 let out = StructChunked::from_series(
1900 "donchian_output".into(),
1901 s.len(),
1902 [upper_series, middle_series, lower_series].iter(),
1903 )?;
1904 Ok(Some(Column::from(out.into_series())))
1905 },
1906 GetOutput::from_type(DataType::Struct(vec![
1907 Field::new("upper".into(), DataType::Float64),
1908 Field::new("middle".into(), DataType::Float64),
1909 Field::new("lower".into(), DataType::Float64),
1910 ])),
1911 )
1912 .alias("donchian_data")])
1913 }
1914
1915 pub fn ttm_squeeze(
1916 self,
1917 high: &str,
1918 low: &str,
1919 close: &str,
1920 period: usize,
1921 multiplier_bb: f64,
1922 multiplier_kc: f64,
1923 ) -> LazyFrame {
1924 let high = high.to_string();
1925 let low = low.to_string();
1926 let close = close.to_string();
1927
1928 self.0
1929 .clone()
1930 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1931 .map(
1932 move |s| {
1933 let ca = s.struct_()?;
1934 let s_high = ca.field_by_name(&high)?;
1935 let s_low = ca.field_by_name(&low)?;
1936 let s_close = ca.field_by_name(&close)?;
1937
1938 let high = s_high.f64()?;
1939 let low = s_low.f64()?;
1940 let close = s_close.f64()?;
1941
1942 let mut ttm =
1943 quantwave_core::TTMSqueeze::new(period, multiplier_bb, multiplier_kc);
1944 let mut histograms = Vec::with_capacity(s.len());
1945 let mut squeezed = Vec::with_capacity(s.len());
1946
1947 for i in 0..s.len() {
1948 let h = high.get(i).unwrap_or(0.0);
1949 let l = low.get(i).unwrap_or(0.0);
1950 let c = close.get(i).unwrap_or(0.0);
1951 let (hist, is_sq) = ttm.next((h, l, c));
1952 histograms.push(hist);
1953 squeezed.push(is_sq);
1954 }
1955
1956 let hist_series = Series::new("histogram".into(), histograms);
1957 let squeezed_series = Series::new("is_squeezed".into(), squeezed);
1958
1959 let out = StructChunked::from_series(
1960 "ttm_squeeze_output".into(),
1961 s.len(),
1962 [hist_series, squeezed_series].iter(),
1963 )?;
1964 Ok(Some(Column::from(out.into_series())))
1965 },
1966 GetOutput::from_type(DataType::Struct(vec![
1967 Field::new("histogram".into(), DataType::Float64),
1968 Field::new("is_squeezed".into(), DataType::Boolean),
1969 ])),
1970 )
1971 .alias("ttm_squeeze_data")])
1972 }
1973
1974 pub fn vortex_indicator(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
1975 let high = high.to_string();
1976 let low = low.to_string();
1977 let close = close.to_string();
1978
1979 self.0
1980 .clone()
1981 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1982 .map(
1983 move |s| {
1984 let ca = s.struct_()?;
1985 let s_high = ca.field_by_name(&high)?;
1986 let s_low = ca.field_by_name(&low)?;
1987 let s_close = ca.field_by_name(&close)?;
1988
1989 let high = s_high.f64()?;
1990 let low = s_low.f64()?;
1991 let close = s_close.f64()?;
1992
1993 let mut vi = quantwave_core::VortexIndicator::new(period);
1994 let mut plus_vals = Vec::with_capacity(s.len());
1995 let mut minus_vals = Vec::with_capacity(s.len());
1996
1997 for i in 0..s.len() {
1998 let h = high.get(i).unwrap_or(0.0);
1999 let l = low.get(i).unwrap_or(0.0);
2000 let c = close.get(i).unwrap_or(0.0);
2001 let (plus, minus) = vi.next((h, l, c));
2002 plus_vals.push(plus);
2003 minus_vals.push(minus);
2004 }
2005
2006 let plus_series = Series::new("vi_plus".into(), plus_vals);
2007 let minus_series = Series::new("vi_minus".into(), minus_vals);
2008
2009 let out = StructChunked::from_series(
2010 "vortex_output".into(),
2011 s.len(),
2012 [plus_series, minus_series].iter(),
2013 )?;
2014 Ok(Some(Column::from(out.into_series())))
2015 },
2016 GetOutput::from_type(DataType::Struct(vec![
2017 Field::new("vi_plus".into(), DataType::Float64),
2018 Field::new("vi_minus".into(), DataType::Float64),
2019 ])),
2020 )
2021 .alias("vortex_data")])
2022 }
2023
2024 pub fn heikin_ashi(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
2025 let open = open.to_string();
2026 let high = high.to_string();
2027 let low = low.to_string();
2028 let close = close.to_string();
2029
2030 self.0.clone().with_columns([as_struct(vec![
2031 col(&open),
2032 col(&high),
2033 col(&low),
2034 col(&close),
2035 ])
2036 .map(
2037 move |s| {
2038 let ca = s.struct_()?;
2039 let s_open = ca.field_by_name(&open)?;
2040 let s_high = ca.field_by_name(&high)?;
2041 let s_low = ca.field_by_name(&low)?;
2042 let s_close = ca.field_by_name(&close)?;
2043
2044 let open = s_open.f64()?;
2045 let high = s_high.f64()?;
2046 let low = s_low.f64()?;
2047 let close = s_close.f64()?;
2048
2049 let mut ha = quantwave_core::HeikinAshi::new();
2050 let mut ha_opens = Vec::with_capacity(s.len());
2051 let mut ha_highs = Vec::with_capacity(s.len());
2052 let mut ha_lows = Vec::with_capacity(s.len());
2053 let mut ha_closes = Vec::with_capacity(s.len());
2054
2055 for i in 0..s.len() {
2056 let o = open.get(i).unwrap_or(0.0);
2057 let h = high.get(i).unwrap_or(0.0);
2058 let l = low.get(i).unwrap_or(0.0);
2059 let c = close.get(i).unwrap_or(0.0);
2060 let (ha_o, ha_h, ha_l, ha_c) = ha.next((o, h, l, c));
2061 ha_opens.push(ha_o);
2062 ha_highs.push(ha_h);
2063 ha_lows.push(ha_l);
2064 ha_closes.push(ha_c);
2065 }
2066
2067 let o_series = Series::new("ha_open".into(), ha_opens);
2068 let h_series = Series::new("ha_high".into(), ha_highs);
2069 let l_series = Series::new("ha_low".into(), ha_lows);
2070 let c_series = Series::new("ha_close".into(), ha_closes);
2071
2072 let out = StructChunked::from_series(
2073 "heikin_ashi_output".into(),
2074 s.len(),
2075 [o_series, h_series, l_series, c_series].iter(),
2076 )?;
2077 Ok(Some(Column::from(out.into_series())))
2078 },
2079 GetOutput::from_type(DataType::Struct(vec![
2080 Field::new("ha_open".into(), DataType::Float64),
2081 Field::new("ha_high".into(), DataType::Float64),
2082 Field::new("ha_low".into(), DataType::Float64),
2083 Field::new("ha_close".into(), DataType::Float64),
2084 ])),
2085 )
2086 .alias("heikin_ashi_data")])
2087 }
2088
2089 pub fn wavetrend(
2090 self,
2091 high: &str,
2092 low: &str,
2093 close: &str,
2094 n1: usize,
2095 n2: usize,
2096 n3: usize,
2097 ) -> LazyFrame {
2098 let high = high.to_string();
2099 let low = low.to_string();
2100 let close = close.to_string();
2101
2102 self.0
2103 .clone()
2104 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2105 .map(
2106 move |s| {
2107 let ca = s.struct_()?;
2108 let s_high = ca.field_by_name(&high)?;
2109 let s_low = ca.field_by_name(&low)?;
2110 let s_close = ca.field_by_name(&close)?;
2111
2112 let high = s_high.f64()?;
2113 let low = s_low.f64()?;
2114 let close = s_close.f64()?;
2115
2116 let mut wt = quantwave_core::WaveTrend::new(n1, n2, n3);
2117 let mut wt1_vals = Vec::with_capacity(s.len());
2118 let mut wt2_vals = Vec::with_capacity(s.len());
2119
2120 for i in 0..s.len() {
2121 let h = high.get(i).unwrap_or(0.0);
2122 let l = low.get(i).unwrap_or(0.0);
2123 let c = close.get(i).unwrap_or(0.0);
2124 let (wt1, wt2) = wt.next((h, l, c));
2125 wt1_vals.push(wt1);
2126 wt2_vals.push(wt2);
2127 }
2128
2129 let wt1_series = Series::new("wt1".into(), wt1_vals);
2130 let wt2_series = Series::new("wt2".into(), wt2_vals);
2131
2132 let out = StructChunked::from_series(
2133 "wavetrend_output".into(),
2134 s.len(),
2135 [wt1_series, wt2_series].iter(),
2136 )?;
2137 Ok(Some(Column::from(out.into_series())))
2138 },
2139 GetOutput::from_type(DataType::Struct(vec![
2140 Field::new("wt1".into(), DataType::Float64),
2141 Field::new("wt2".into(), DataType::Float64),
2142 ])),
2143 )
2144 .alias("wavetrend_data")])
2145 }
2146
2147 pub fn tema(self, name: &str, period: usize) -> LazyFrame {
2148 let name = name.to_string();
2149 self.0.clone().with_columns([col(&name)
2150 .map(
2151 move |s| {
2152 let ca = s.f64()?;
2153 let mut tema = quantwave_core::TEMA::new(period);
2154 let mut values = Vec::with_capacity(s.len());
2155
2156 for i in 0..s.len() {
2157 let val = ca.get(i).unwrap_or(0.0);
2158 values.push(tema.next(val));
2159 }
2160
2161 Ok(Some(Column::from(Series::new("tema".into(), values))))
2162 },
2163 GetOutput::from_type(DataType::Float64),
2164 )
2165 .alias("tema")])
2166 }
2167
2168 pub fn zlema(self, name: &str, period: usize) -> LazyFrame {
2169 let name = name.to_string();
2170 self.0.clone().with_columns([col(&name)
2171 .map(
2172 move |s| {
2173 let ca = s.f64()?;
2174 let mut zlema = quantwave_core::ZLEMA::new(period);
2175 let mut values = Vec::with_capacity(s.len());
2176
2177 for i in 0..s.len() {
2178 let val = ca.get(i).unwrap_or(0.0);
2179 values.push(zlema.next(val));
2180 }
2181
2182 Ok(Some(Column::from(Series::new("zlema".into(), values))))
2183 },
2184 GetOutput::from_type(DataType::Float64),
2185 )
2186 .alias("zlema")])
2187 }
2188
2189 pub fn atr_trailing_stop(
2190 self,
2191 high: &str,
2192 low: &str,
2193 close: &str,
2194 period: usize,
2195 multiplier: f64,
2196 ) -> LazyFrame {
2197 let high = high.to_string();
2198 let low = low.to_string();
2199 let close = close.to_string();
2200
2201 self.0
2202 .clone()
2203 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2204 .map(
2205 move |s| {
2206 let ca = s.struct_()?;
2207 let s_high = ca.field_by_name(&high)?;
2208 let s_low = ca.field_by_name(&low)?;
2209 let s_close = ca.field_by_name(&close)?;
2210
2211 let high = s_high.f64()?;
2212 let low = s_low.f64()?;
2213 let close = s_close.f64()?;
2214
2215 let mut atr_ts = quantwave_core::ATRTrailingStop::new(period, multiplier);
2216 let mut stops = Vec::with_capacity(s.len());
2217 let mut directions = Vec::with_capacity(s.len());
2218
2219 for i in 0..s.len() {
2220 let h = high.get(i).unwrap_or(0.0);
2221 let l = low.get(i).unwrap_or(0.0);
2222 let c = close.get(i).unwrap_or(0.0);
2223 let (stop, dir) = atr_ts.next((h, l, c));
2224 stops.push(stop);
2225 directions.push(dir as f64);
2226 }
2227
2228 let stop_series = Series::new("stop".into(), stops);
2229 let dir_series = Series::new("direction".into(), directions);
2230
2231 let out = StructChunked::from_series(
2232 "atr_ts_output".into(),
2233 s.len(),
2234 [stop_series, dir_series].iter(),
2235 )?;
2236 Ok(Some(Column::from(out.into_series())))
2237 },
2238 GetOutput::from_type(DataType::Struct(vec![
2239 Field::new("stop".into(), DataType::Float64),
2240 Field::new("direction".into(), DataType::Float64),
2241 ])),
2242 )
2243 .alias("atr_ts_data")])
2244 }
2245
2246 pub fn pivot_points(self, high: &str, low: &str, close: &str) -> LazyFrame {
2247 let high = high.to_string();
2248 let low = low.to_string();
2249 let close = close.to_string();
2250
2251 self.0
2252 .clone()
2253 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2254 .map(
2255 move |s| {
2256 let ca = s.struct_()?;
2257 let s_high = ca.field_by_name(&high)?;
2258 let s_low = ca.field_by_name(&low)?;
2259 let s_close = ca.field_by_name(&close)?;
2260
2261 let high = s_high.f64()?;
2262 let low = s_low.f64()?;
2263 let close = s_close.f64()?;
2264
2265 let mut pivot = quantwave_core::PivotPoints::new();
2266 let mut p_vals = Vec::with_capacity(s.len());
2267 let mut r1_vals = Vec::with_capacity(s.len());
2268 let mut s1_vals = Vec::with_capacity(s.len());
2269 let mut r2_vals = Vec::with_capacity(s.len());
2270 let mut s2_vals = Vec::with_capacity(s.len());
2271
2272 for i in 0..s.len() {
2273 let h = high.get(i).unwrap_or(0.0);
2274 let l = low.get(i).unwrap_or(0.0);
2275 let c = close.get(i).unwrap_or(0.0);
2276 let (p, r1, s1, r2, s2) = pivot.next((h, l, c));
2277 p_vals.push(p);
2278 r1_vals.push(r1);
2279 s1_vals.push(s1);
2280 r2_vals.push(r2);
2281 s2_vals.push(s2);
2282 }
2283
2284 let p_series = Series::new("p".into(), p_vals);
2285 let r1_series = Series::new("r1".into(), r1_vals);
2286 let s1_series = Series::new("s1".into(), s1_vals);
2287 let r2_series = Series::new("r2".into(), r2_vals);
2288 let s2_series = Series::new("s2".into(), s2_vals);
2289
2290 let out = StructChunked::from_series(
2291 "pivot_output".into(),
2292 s.len(),
2293 [p_series, r1_series, s1_series, r2_series, s2_series].iter(),
2294 )?;
2295 Ok(Some(Column::from(out.into_series())))
2296 },
2297 GetOutput::from_type(DataType::Struct(vec![
2298 Field::new("p".into(), DataType::Float64),
2299 Field::new("r1".into(), DataType::Float64),
2300 Field::new("s1".into(), DataType::Float64),
2301 Field::new("r2".into(), DataType::Float64),
2302 Field::new("s2".into(), DataType::Float64),
2303 ])),
2304 )
2305 .alias("pivot_points_data")])
2306 }
2307
2308 pub fn bill_williams_fractals(self, high: &str, low: &str) -> LazyFrame {
2309 let high = high.to_string();
2310 let low = low.to_string();
2311
2312 self.0
2313 .clone()
2314 .with_columns([as_struct(vec![col(&high), col(&low)])
2315 .map(
2316 move |s| {
2317 let ca = s.struct_()?;
2318 let s_high = ca.field_by_name(&high)?;
2319 let s_low = ca.field_by_name(&low)?;
2320
2321 let high = s_high.f64()?;
2322 let low = s_low.f64()?;
2323
2324 let mut fractals = quantwave_core::BillWilliamsFractals::new();
2325 let mut bearish_vals = Vec::with_capacity(s.len());
2326 let mut bullish_vals = Vec::with_capacity(s.len());
2327
2328 for i in 0..s.len() {
2329 let h = high.get(i).unwrap_or(0.0);
2330 let l = low.get(i).unwrap_or(0.0);
2331 let (bear, bull) = fractals.next((h, l));
2332 bearish_vals.push(bear);
2333 bullish_vals.push(bull);
2334 }
2335
2336 let bearish_series = Series::new("bearish".into(), bearish_vals);
2337 let bullish_series = Series::new("bullish".into(), bullish_vals);
2338
2339 let out = StructChunked::from_series(
2340 "fractals_output".into(),
2341 s.len(),
2342 [bearish_series, bullish_series].iter(),
2343 )?;
2344 Ok(Some(Column::from(out.into_series())))
2345 },
2346 GetOutput::from_type(DataType::Struct(vec![
2347 Field::new("bearish".into(), DataType::Boolean),
2348 Field::new("bullish".into(), DataType::Boolean),
2349 ])),
2350 )
2351 .alias("fractals_data")])
2352 }
2353
2354 pub fn ichimoku_cloud(
2355 self,
2356 high: &str,
2357 low: &str,
2358 p1: usize,
2359 p2: usize,
2360 p3: usize,
2361 ) -> LazyFrame {
2362 let high = high.to_string();
2363 let low = low.to_string();
2364
2365 self.0
2366 .clone()
2367 .with_columns([as_struct(vec![col(&high), col(&low)])
2368 .map(
2369 move |s| {
2370 let ca = s.struct_()?;
2371 let s_high = ca.field_by_name(&high)?;
2372 let s_low = ca.field_by_name(&low)?;
2373
2374 let high = s_high.f64()?;
2375 let low = s_low.f64()?;
2376
2377 let mut ic = quantwave_core::IchimokuCloud::new(p1, p2, p3);
2378 let mut t_vals = Vec::with_capacity(s.len());
2379 let mut k_vals = Vec::with_capacity(s.len());
2380 let mut sa_vals = Vec::with_capacity(s.len());
2381 let mut sb_vals = Vec::with_capacity(s.len());
2382
2383 for i in 0..s.len() {
2384 let h = high.get(i).unwrap_or(0.0);
2385 let l = low.get(i).unwrap_or(0.0);
2386 let (t, k, sa, sb) = ic.next((h, l));
2387 t_vals.push(t);
2388 k_vals.push(k);
2389 sa_vals.push(sa);
2390 sb_vals.push(sb);
2391 }
2392
2393 let t_series = Series::new("tenkan".into(), t_vals);
2394 let k_series = Series::new("kijun".into(), k_vals);
2395 let sa_series = Series::new("senkou_a".into(), sa_vals);
2396 let sb_series = Series::new("senkou_b".into(), sb_vals);
2397
2398 let out = StructChunked::from_series(
2399 "ichimoku_output".into(),
2400 s.len(),
2401 [t_series, k_series, sa_series, sb_series].iter(),
2402 )?;
2403 Ok(Some(Column::from(out.into_series())))
2404 },
2405 GetOutput::from_type(DataType::Struct(vec![
2406 Field::new("tenkan".into(), DataType::Float64),
2407 Field::new("kijun".into(), DataType::Float64),
2408 Field::new("senkou_a".into(), DataType::Float64),
2409 Field::new("senkou_b".into(), DataType::Float64),
2410 ])),
2411 )
2412 .alias("ichimoku_data")])
2413 }
2414
2415 pub fn volatility_clusterer(
2416 self,
2417 high: &str,
2418 low: &str,
2419 close: &str,
2420 atr_period: usize,
2421 window_size: usize,
2422 k: usize,
2423 ) -> LazyFrame {
2424 let h_str = high.to_string();
2425 let l_str = low.to_string();
2426 let c_str = close.to_string();
2427
2428 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2429 .map(
2430 move |s| {
2431 let ca = s.struct_()?;
2432 let f_h = ca.field_by_name(&h_str)?;
2433 let high = f_h.f64()?;
2434 let f_l = ca.field_by_name(&l_str)?;
2435 let low = f_l.f64()?;
2436 let f_c = ca.field_by_name(&c_str)?;
2437 let close = f_c.f64()?;
2438
2439 let mut clusterer =
2440 quantwave_core::regimes::volatility_clustering::VolatilityClusterer::new(
2441 atr_period,
2442 window_size,
2443 k,
2444 );
2445 let mut values = Vec::with_capacity(s.len());
2446
2447 for i in 0..s.len() {
2448 let h = high.get(i).unwrap_or(f64::NAN);
2449 let l = low.get(i).unwrap_or(f64::NAN);
2450 let c = close.get(i).unwrap_or(f64::NAN);
2451 let regime = clusterer.next((h, l, c));
2452 let val = match regime {
2453 quantwave_core::regimes::MarketRegime::Steady => 0u32,
2454 quantwave_core::regimes::MarketRegime::Bull => 1,
2455 quantwave_core::regimes::MarketRegime::Bear => 2,
2456 quantwave_core::regimes::MarketRegime::Crisis => 3,
2457 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
2458 };
2459 values.push(val);
2460 }
2461
2462 Ok(Some(Column::from(Series::new("volatility_regime".into(), values))))
2463 },
2464 GetOutput::from_type(DataType::UInt32),
2465 )
2466 .alias("volatility_regime")])
2467 }
2468
2469 pub fn hmm_bull_bear(self, name: &str) -> LazyFrame {
2470 let name_str = name.to_string();
2471 self.0.clone().with_columns([col(&name_str)
2472 .map(
2473 move |s| {
2474 let ca = s.f64()?;
2475 let mut hmm = quantwave_core::regimes::hmm::HMM::bull_bear();
2476 let mut values = Vec::with_capacity(s.len());
2477
2478 for i in 0..s.len() {
2479 let val = ca.get(i).unwrap_or(f64::NAN);
2480 let regime = hmm.next(val);
2481 let out = match regime {
2482 quantwave_core::regimes::MarketRegime::Bull => 1u32,
2483 quantwave_core::regimes::MarketRegime::Bear => 2,
2484 _ => 0,
2485 };
2486 values.push(out);
2487 }
2488
2489 Ok(Some(Column::from(Series::new("hmm_regime".into(), values))))
2490 },
2491 GetOutput::from_type(DataType::UInt32),
2492 )
2493 .alias("hmm_regime")])
2494 }
2495
2496 pub fn pelt(self, name: &str, penalty: f64, min_dist: usize) -> LazyFrame {
2497 let name_str = name.to_string();
2498 self.0.clone().with_columns([col(&name_str)
2499 .map(
2500 move |s| {
2501 let ca = s.f64()?;
2502 let data: Vec<f64> = ca.into_iter().map(|v| v.unwrap_or(f64::NAN)).collect();
2503 let pelt = quantwave_core::regimes::pelt::PELT::new(penalty, min_dist);
2504 let cps = pelt.detect(&data);
2505
2506 let mut values = vec![0u32; s.len()];
2507 for cp in cps {
2508 if cp < values.len() {
2509 values[cp] = 1;
2510 }
2511 }
2512
2513 Ok(Some(Column::from(Series::new("changepoints".into(), values))))
2514 },
2515 GetOutput::from_type(DataType::UInt32),
2516 )
2517 .alias("changepoints")])
2518 }
2519
2520 pub fn gmm(self, columns: &[&str], _k: usize) -> LazyFrame {
2521 let cols: Vec<String> = columns.iter().map(|s| s.to_string()).collect();
2522 let col_exprs: Vec<Expr> = cols.iter().map(|c| col(c)).collect();
2523
2524 self.0.clone().with_columns([as_struct(col_exprs)
2525 .map(
2526 move |s| {
2527 let ca = s.struct_()?;
2528 let n_rows = s.len();
2529 let n_dims = cols.len();
2530
2531 let mut data = Vec::with_capacity(n_rows);
2532 for i in 0..n_rows {
2533 let mut row = Vec::with_capacity(n_dims);
2534 for c_name in &cols {
2535 let f = ca.field_by_name(c_name)?;
2536 let val = f.f64()?.get(i).unwrap_or(f64::NAN);
2537 row.push(val);
2538 }
2539 data.push(row);
2540 }
2541
2542 let mut values = Vec::with_capacity(n_rows);
2545 for _ in 0..n_rows {
2546 values.push(0u32);
2547 }
2548
2549 Ok(Some(Column::from(Series::new("gmm_regime".into(), values))))
2550 },
2551 GetOutput::from_type(DataType::UInt32),
2552 )
2553 .alias("gmm_regime")])
2554 }
2555
2556 pub fn regimes_duration_stats(self, regime_col: &str, num_states: usize) -> LazyFrame {
2557 let name_str = regime_col.to_string();
2558 self.0.clone().with_columns([col(&name_str)
2559 .map(
2560 move |s| {
2561 let ca = s.u32()?;
2562 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
2563 let stats = quantwave_core::RegimeAnalytics::duration_stats(&states, num_states);
2564
2565 let mut regime_ids = Vec::new();
2567 let mut means = Vec::new();
2568 let mut medians = Vec::new();
2569 let mut stds = Vec::new();
2570 let mut maxes = Vec::new();
2571 let mut totals = Vec::new();
2572
2573 for stat in stats {
2574 regime_ids.push(stat.regime_id);
2575 means.push(stat.mean_duration);
2576 medians.push(stat.median_duration);
2577 stds.push(stat.std_duration);
2578 maxes.push(stat.max_duration as u32);
2579 totals.push(stat.total_observations as u32);
2580 }
2581
2582 let s_id = Series::new("regime_id".into(), regime_ids);
2583 let s_mean = Series::new("mean_duration".into(), means);
2584 let s_median = Series::new("median_duration".into(), medians);
2585 let s_std = Series::new("std_duration".into(), stds);
2586 let s_max = Series::new("max_duration".into(), maxes);
2587 let s_total = Series::new("total_observations".into(), totals);
2588
2589 let struct_series = StructChunked::from_series(
2590 "duration_stats".into(),
2591 s_id.len(),
2592 [s_id, s_mean, s_median, s_std, s_max, s_total].iter(),
2593 )?;
2594 Ok(Some(Column::from(struct_series.into_series())))
2595 },
2596 GetOutput::from_type(DataType::Struct(vec![
2597 Field::new("regime_id".into(), DataType::UInt32),
2598 Field::new("mean_duration".into(), DataType::Float64),
2599 Field::new("median_duration".into(), DataType::Float64),
2600 Field::new("std_duration".into(), DataType::Float64),
2601 Field::new("max_duration".into(), DataType::UInt32),
2602 Field::new("total_observations".into(), DataType::UInt32),
2603 ])),
2604 )
2605 .alias("regime_duration_stats")])
2606 }
2607
2608 pub fn regimes_transition_matrix(self, regime_col: &str, num_states: usize) -> LazyFrame {
2609 let name_str = regime_col.to_string();
2610 self.0.clone().with_columns([col(&name_str)
2611 .map(
2612 move |s| {
2613 let ca = s.u32()?;
2614 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
2615 let matrix = quantwave_core::RegimeAnalytics::transition_matrix(&states, num_states);
2616
2617 let mut builders = ListPrimitiveChunkedBuilder::<Float64Type>::new(
2619 "transition_matrix".into(),
2620 matrix.len(),
2621 matrix.len() * num_states,
2622 DataType::Float64,
2623 );
2624 for row in matrix {
2625 builders.append_slice(&row);
2626 }
2627
2628 let list_ca = builders.finish();
2629 Ok(Some(Column::from(list_ca.into_series())))
2630 },
2631 GetOutput::from_type(DataType::List(Box::new(DataType::Float64))),
2632 )
2633 .alias("regime_transition_matrix")])
2634 }
2635
2636 pub fn regimes_stability_score(self, regime_col: &str) -> LazyFrame {
2637 let name_str = regime_col.to_string();
2638 self.0.clone().with_columns([col(&name_str)
2639 .map(
2640 move |s| {
2641 let ca = s.u32()?;
2642 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
2643 let score = quantwave_core::RegimeAnalytics::stability_score(&states);
2644
2645 Ok(Some(Column::from(Series::new("stability_score".into(), vec![score; s.len()]))))
2646 },
2647 GetOutput::from_type(DataType::Float64),
2648 )
2649 .alias("regime_stability_score")])
2650 }
2651
2652 pub fn regimes_next_state_prob(self, regime_col: &str, num_states: usize, steps: usize) -> LazyFrame {
2653 let name_str = regime_col.to_string();
2654 self.0.clone().with_columns([col(&name_str)
2655 .map(
2656 move |s| {
2657 let ca = s.u32()?;
2658 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
2659 let matrix = quantwave_core::RegimeAnalytics::transition_matrix(&states, num_states);
2660
2661 let mut builders = ListPrimitiveChunkedBuilder::<Float64Type>::new(
2662 "next_state_probs".into(),
2663 s.len(),
2664 s.len() * num_states,
2665 DataType::Float64,
2666 );
2667
2668 for ¤t in &states {
2669 let probs = quantwave_core::RegimeAnalytics::forecast_state(&matrix, current, steps);
2670 builders.append_slice(&probs);
2671 }
2672
2673 let list_ca = builders.finish();
2674 Ok(Some(Column::from(list_ca.into_series())))
2675 },
2676 GetOutput::from_type(DataType::List(Box::new(DataType::Float64))),
2677 )
2678 .alias("next_state_probs")])
2679 }
2680
2681 pub fn filter_by_regime(self, regime_col: &str, target_regime: u32) -> LazyFrame {
2682 self.0.clone().filter(col(regime_col).eq(lit(target_regime)))
2683 }
2684
2685 pub fn apply_regime_strategy(
2686 self,
2687 regime_col: &str,
2688 signal_col: &str,
2689 regime_weights: std::collections::HashMap<u32, f64>,
2690 ) -> LazyFrame {
2691 let mut case_expr = col(signal_col);
2692 for (regime, weight) in regime_weights {
2693 case_expr = when(col(regime_col).eq(lit(regime)))
2694 .then(col(signal_col) * lit(weight))
2695 .otherwise(case_expr);
2696 }
2697
2698 self.0.clone().with_columns([case_expr.alias("regime_adjusted_signal")])
2699 }
2700
2701 pub fn regimes_ms_garch(self, returns_col: &str) -> LazyFrame {
2702 let name_str = returns_col.to_string();
2703 self.0.clone().with_columns([col(&name_str)
2704 .map(
2705 move |s| {
2706 let ca = s.f64()?;
2707 let mut model = quantwave_core::regimes::ms_garch::MSGarch::low_high_vol();
2708 let mut regimes = Vec::with_capacity(s.len());
2709 let mut vols = Vec::with_capacity(s.len());
2710
2711 for i in 0..s.len() {
2712 let ret = ca.get(i).unwrap_or(0.0);
2713 let (regime, vol) = model.next(ret);
2714
2715 let r_val = match regime {
2716 quantwave_core::regimes::MarketRegime::Steady => 0u32,
2717 quantwave_core::regimes::MarketRegime::Crisis => 1,
2718 quantwave_core::regimes::MarketRegime::Bull => 2,
2719 quantwave_core::regimes::MarketRegime::Bear => 3,
2720 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
2721 };
2722 regimes.push(r_val);
2723 vols.push(vol);
2724 }
2725
2726 let s_regime = Series::new("regime".into(), regimes);
2727 let s_vol = Series::new("estimated_vol".into(), vols);
2728 let struct_series = StructChunked::from_series(
2729 "ms_garch_data".into(),
2730 s.len(),
2731 [s_regime, s_vol].iter(),
2732 )?;
2733 Ok(Some(Column::from(struct_series.into_series())))
2734 },
2735 GetOutput::from_type(DataType::Struct(vec![
2736 Field::new("regime".into(), DataType::UInt32),
2737 Field::new("estimated_vol".into(), DataType::Float64),
2738 ])),
2739 )
2740 .alias("ms_garch_data")])
2741 }
2742
2743 pub fn regimes_ensemble(self, columns: &[&str], weights: &[f64]) -> LazyFrame {
2744 let cols: Vec<String> = columns.iter().map(|s| s.to_string()).collect();
2745 let col_exprs: Vec<Expr> = cols.iter().map(|c| col(c)).collect();
2746 let w_vec = weights.to_vec();
2747
2748 self.0.clone().with_columns([as_struct(col_exprs)
2749 .map(
2750 move |s| {
2751 let ca = s.struct_()?;
2752 let n_rows = s.len();
2753 let n_dims = cols.len();
2754 let ensemble = quantwave_core::regimes::ensemble::RegimeEnsemble::new(w_vec.clone());
2755
2756 let mut results = Vec::with_capacity(n_rows);
2757 for i in 0..n_rows {
2758 let mut row_regimes = Vec::with_capacity(n_dims);
2759 for c_name in &cols {
2760 let f = ca.field_by_name(c_name)?;
2761 let val = f.u32()?.get(i).unwrap_or(0);
2762
2763 let regime = match val {
2764 0 => quantwave_core::regimes::MarketRegime::Steady,
2765 1 => quantwave_core::regimes::MarketRegime::Crisis,
2766 2 => quantwave_core::regimes::MarketRegime::Bull,
2767 3 => quantwave_core::regimes::MarketRegime::Bear,
2768 v if v >= 4 => quantwave_core::regimes::MarketRegime::Cluster((v - 4) as u8),
2769 _ => quantwave_core::regimes::MarketRegime::Steady,
2770 };
2771 row_regimes.push(regime);
2772 }
2773
2774 let consensus = ensemble.vote(&row_regimes);
2775 let out = match consensus {
2776 quantwave_core::regimes::MarketRegime::Steady => 0u32,
2777 quantwave_core::regimes::MarketRegime::Crisis => 1,
2778 quantwave_core::regimes::MarketRegime::Bull => 2,
2779 quantwave_core::regimes::MarketRegime::Bear => 3,
2780 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
2781 };
2782 results.push(out);
2783 }
2784
2785 Ok(Some(Column::from(Series::new("ensemble_regime".into(), results))))
2786 },
2787 GetOutput::from_type(DataType::UInt32),
2788 )
2789 .alias("ensemble_regime")])
2790 }
2791
2792 pub fn regimes_tar(self, signal_col: &str, thresholds: Vec<f64>) -> LazyFrame {
2793 let name_str = signal_col.to_string();
2794 self.0.clone().with_columns([col(&name_str)
2795 .map(
2796 move |s| {
2797 let ca = s.f64()?;
2798 let mut model = quantwave_core::regimes::tar::TAR::multi(thresholds.clone());
2799 let mut results = Vec::with_capacity(s.len());
2800
2801 for i in 0..s.len() {
2802 let val = ca.get(i).unwrap_or(f64::NAN);
2803 let regime = model.next(val);
2804 let out = match regime {
2805 quantwave_core::regimes::MarketRegime::Steady => 0u32,
2806 quantwave_core::regimes::MarketRegime::Crisis => 1,
2807 quantwave_core::regimes::MarketRegime::Bull => 2,
2808 quantwave_core::regimes::MarketRegime::Bear => 3,
2809 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
2810 };
2811 results.push(out);
2812 }
2813
2814 Ok(Some(Column::from(Series::new("tar_regime".into(), results))))
2815 },
2816 GetOutput::from_type(DataType::UInt32),
2817 )
2818 .alias("tar_regime")])
2819 }
2820
2821 pub fn regimes_hsmm(self, name: &str) -> LazyFrame {
2822 let name_str = name.to_string();
2823 self.0.clone().with_columns([col(&name_str)
2824 .map(
2825 move |s| {
2826 let ca = s.f64()?;
2827 let mut model = quantwave_core::regimes::hsmm::HSMM::new(
2829 vec![vec![0.0, 1.0], vec![1.0, 0.0]], vec![0.001, -0.002],
2831 vec![0.01, 0.02],
2832 vec![
2833 quantwave_core::regimes::hsmm::DurationDistribution::Poisson { lambda: 5.0 },
2834 quantwave_core::regimes::hsmm::DurationDistribution::Poisson { lambda: 2.0 },
2835 ],
2836 );
2837 let mut values = Vec::with_capacity(s.len());
2838
2839 for i in 0..s.len() {
2840 let val = ca.get(i).unwrap_or(f64::NAN);
2841 let regime = model.next(val);
2842 let out = match regime {
2843 quantwave_core::regimes::MarketRegime::Steady => 0u32,
2844 quantwave_core::regimes::MarketRegime::Crisis => 1,
2845 _ => 2, };
2847 values.push(out);
2848 }
2849
2850 Ok(Some(Column::from(Series::new("hsmm_regime".into(), values))))
2851 },
2852 GetOutput::from_type(DataType::UInt32),
2853 )
2854 .alias("hsmm_regime")])
2855 }
2856
2857 pub fn regimes_hmm_gas(self, name: &str) -> LazyFrame {
2858 let name_str = name.to_string();
2859 self.0.clone().with_columns([col(&name_str)
2860 .map(
2861 move |s| {
2862 let ca = s.f64()?;
2863 let mut model = quantwave_core::regimes::hmm_gas::HMMGAS::new(
2864 [0.1, 0.05, 0.9], [0.1, 0.05, 0.9], [0.001, -0.002],
2867 [0.01, 0.02],
2868 );
2869 let mut values = Vec::with_capacity(s.len());
2870
2871 for i in 0..s.len() {
2872 let val = ca.get(i).unwrap_or(f64::NAN);
2873 let regime = model.next(val);
2874 let out = match regime {
2875 quantwave_core::regimes::MarketRegime::Steady => 0u32,
2876 quantwave_core::regimes::MarketRegime::Crisis => 1,
2877 _ => 2,
2878 };
2879 values.push(out);
2880 }
2881
2882 Ok(Some(Column::from(Series::new("hmm_gas_regime".into(), values))))
2883 },
2884 GetOutput::from_type(DataType::UInt32),
2885 )
2886 .alias("hmm_gas_regime")])
2887 }
2888
2889 pub fn regimes_conditioned_metrics(
2890 self,
2891 returns_col: &str,
2892 regime_col: &str,
2893 annualization_factor: f64,
2894 ) -> LazyFrame {
2895 let ret_str = returns_col.to_string();
2896 let reg_str = regime_col.to_string();
2897
2898 self.0
2899 .clone()
2900 .group_by([col(®_str)])
2901 .agg([
2902 col(&ret_str).mean().alias("mean_return"),
2903 col(&ret_str).std(1).alias("volatility"),
2904 (col(&ret_str).mean() / col(&ret_str).std(1) * lit(annualization_factor.sqrt()))
2905 .alias("sharpe_ratio"),
2906 col(&ret_str).skew(true).alias("skewness"),
2907 col(&ret_str).kurtosis(true, true).alias("kurtosis"),
2908 (col(&ret_str).mean()
2910 / col(&ret_str).filter(col(&ret_str).lt(lit(0.0))).std(1)
2911 * lit(annualization_factor.sqrt()))
2912 .alias("sortino_ratio"),
2913 ])
2917 }
2918}
2919
2920
2921
2922
2923
2924#[cfg(test)]
2925mod tests {
2926 use super::*;
2927
2928 #[test]
2929 fn test_polars_heikin_ashi() -> PolarsResult<()> {
2930 let df = df![
2931 "open" => [10.0, 11.0],
2932 "high" => [12.0, 13.0],
2933 "low" => [8.0, 10.0],
2934 "close" => [11.0, 12.0]
2935 ]?;
2936
2937 let out = df
2938 .lazy()
2939 .ta()
2940 .heikin_ashi("open", "high", "low", "close")
2941 .collect()?;
2942
2943 let ha = out.column("heikin_ashi_data")?.struct_()?;
2944 assert_eq!(
2945 ha.field_by_name("ha_open".into())?.f64()?.get(0),
2946 Some(10.5)
2947 );
2948 assert_eq!(
2949 ha.field_by_name("ha_close".into())?.f64()?.get(0),
2950 Some(10.25)
2951 );
2952
2953 Ok(())
2954 }
2955
2956 #[test]
2957 fn test_polars_tema_zlema() -> PolarsResult<()> {
2958 let df = df![
2959 "price" => [1.0, 2.0, 3.0, 4.0, 5.0]
2960 ]?;
2961
2962 let out = df.clone().lazy().ta().tema("price", 3).collect()?;
2963
2964 let tema = out.column("tema")?.f64()?;
2965 assert!(tema.get(4).is_some());
2966
2967 let out2 = df.lazy().ta().zlema("price", 3).collect()?;
2968
2969 let zlema = out2.column("zlema")?.f64()?;
2970 assert!(zlema.get(4).is_some());
2971
2972 Ok(())
2973 }
2974
2975 #[test]
2976 fn test_polars_atr_ts() -> PolarsResult<()> {
2977 let df = df![
2978 "high" => [10.0, 12.0, 11.0],
2979 "low" => [8.0, 10.0, 9.0],
2980 "close" => [9.0, 11.0, 10.0]
2981 ]?;
2982
2983 let out = df
2984 .lazy()
2985 .ta()
2986 .atr_trailing_stop("high", "low", "close", 14, 2.5)
2987 .collect()?;
2988
2989 let atr_ts = out.column("atr_ts_data")?.struct_()?;
2990 assert!(atr_ts.field_by_name("stop".into())?.f64()?.get(0).is_some());
2991 assert!(
2992 atr_ts
2993 .field_by_name("direction".into())?
2994 .f64()?
2995 .get(0)
2996 .is_some()
2997 );
2998
2999 Ok(())
3000 }
3001
3002 #[test]
3003 fn test_polars_pivot_points() -> PolarsResult<()> {
3004 let df = df![
3005 "high" => [10.0, 12.0, 11.0],
3006 "low" => [8.0, 10.0, 9.0],
3007 "close" => [9.0, 11.0, 10.0]
3008 ]?;
3009
3010 let out = df
3011 .lazy()
3012 .ta()
3013 .pivot_points("high", "low", "close")
3014 .collect()?;
3015
3016 let pivot = out.column("pivot_points_data")?.struct_()?;
3017 assert!(pivot.field_by_name("p".into())?.f64()?.get(0).is_some());
3018 assert!(pivot.field_by_name("r1".into())?.f64()?.get(0).is_some());
3019
3020 Ok(())
3021 }
3022
3023 #[test]
3024 fn test_polars_fractals() -> PolarsResult<()> {
3025 let df = df![
3026 "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
3027 "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
3028 ]?;
3029
3030 let out = df
3031 .lazy()
3032 .ta()
3033 .bill_williams_fractals("high", "low")
3034 .collect()?;
3035
3036 let fractals = out.column("fractals_data")?.struct_()?;
3037 assert!(
3038 fractals
3039 .field_by_name("bearish".into())?
3040 .bool()?
3041 .get(4)
3042 .unwrap()
3043 );
3044 assert!(
3045 fractals
3046 .field_by_name("bullish".into())?
3047 .bool()?
3048 .get(4)
3049 .unwrap()
3050 );
3051
3052 Ok(())
3053 }
3054
3055 #[test]
3056 fn test_polars_ichimoku() -> PolarsResult<()> {
3057 let df = df![
3058 "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
3059 "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
3060 ]?;
3061
3062 let out = df
3063 .lazy()
3064 .ta()
3065 .ichimoku_cloud("high", "low", 9, 26, 52)
3066 .collect()?;
3067
3068 let ichimoku = out.column("ichimoku_data")?.struct_()?;
3069 assert!(
3070 ichimoku
3071 .field_by_name("tenkan".into())?
3072 .f64()?
3073 .get(4)
3074 .is_some()
3075 );
3076 assert!(
3077 ichimoku
3078 .field_by_name("kijun".into())?
3079 .f64()?
3080 .get(4)
3081 .is_some()
3082 );
3083
3084 Ok(())
3085 }
3086
3087 #[test]
3088 fn test_polars_wavetrend() -> PolarsResult<()> {
3089 let df = df![
3090 "high" => [10.0, 12.0, 11.0],
3091 "low" => [8.0, 10.0, 9.0],
3092 "close" => [9.0, 11.0, 10.0]
3093 ]?;
3094
3095 let out = df
3096 .lazy()
3097 .ta()
3098 .wavetrend("high", "low", "close", 10, 21, 4)
3099 .collect()?;
3100
3101 let wt = out.column("wavetrend_data")?.struct_()?;
3102 assert!(wt.field_by_name("wt1".into())?.f64()?.get(0).is_some());
3103 assert!(wt.field_by_name("wt2".into())?.f64()?.get(0).is_some());
3104
3105 Ok(())
3106 }
3107
3108 #[test]
3109 fn test_polars_vortex() -> PolarsResult<()> {
3110 let df = df![
3111 "high" => [10.0, 12.0, 11.0],
3112 "low" => [8.0, 10.0, 9.0],
3113 "close" => [9.0, 11.0, 10.0]
3114 ]?;
3115
3116 let out = df
3117 .lazy()
3118 .ta()
3119 .vortex_indicator("high", "low", "close", 14)
3120 .collect()?;
3121
3122 let vortex = out.column("vortex_data")?.struct_()?;
3123 assert!(
3124 vortex
3125 .field_by_name("vi_plus".into())?
3126 .f64()?
3127 .get(0)
3128 .is_some()
3129 );
3130 assert!(
3131 vortex
3132 .field_by_name("vi_minus".into())?
3133 .f64()?
3134 .get(0)
3135 .is_some()
3136 );
3137
3138 Ok(())
3139 }
3140
3141 #[test]
3142 fn test_polars_ttm_squeeze() -> PolarsResult<()> {
3143 let df = df![
3144 "high" => [11.0, 12.0, 13.0, 14.0],
3145 "low" => [9.0, 10.0, 11.0, 12.0],
3146 "close" => [10.0, 11.0, 12.0, 13.0]
3147 ]?;
3148
3149 let out = df
3150 .lazy()
3151 .ta()
3152 .ttm_squeeze("high", "low", "close", 20, 2.0, 1.5)
3153 .collect()?;
3154
3155 let ttm = out.column("ttm_squeeze_data")?.struct_()?;
3156 assert!(
3157 ttm.field_by_name("histogram".into())?
3158 .f64()?
3159 .get(0)
3160 .is_some()
3161 );
3162 assert!(
3163 ttm.field_by_name("is_squeezed".into())?
3164 .bool()?
3165 .get(0)
3166 .is_some()
3167 );
3168
3169 Ok(())
3170 }
3171
3172 #[test]
3173 fn test_polars_donchian() -> PolarsResult<()> {
3174 let df = df![
3175 "high" => [10.0, 12.0, 11.0, 13.0, 15.0],
3176 "low" => [8.0, 7.0, 9.0, 10.0, 12.0]
3177 ]?;
3178
3179 let out = df
3180 .lazy()
3181 .ta()
3182 .donchian_channels("high", "low", 3)
3183 .collect()?;
3184
3185 let donchian = out.column("donchian_data")?.struct_()?;
3186 assert_eq!(
3188 donchian.field_by_name("upper".into())?.f64()?.get(3),
3189 Some(13.0)
3190 );
3191 assert_eq!(
3192 donchian.field_by_name("middle".into())?.f64()?.get(3),
3193 Some(10.0)
3194 );
3195 assert_eq!(
3196 donchian.field_by_name("lower".into())?.f64()?.get(3),
3197 Some(7.0)
3198 );
3199
3200 Ok(())
3201 }
3202
3203 #[test]
3204 fn test_polars_alma() -> PolarsResult<()> {
3205 let df = df![
3206 "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
3207 ]?;
3208
3209 let out = df.lazy().ta().alma("price", 9, 0.85, 6.0).collect()?;
3210
3211 let alma = out.column("alma")?.f64()?;
3212 assert!(alma.get(9).is_some());
3213
3214 Ok(())
3215 }
3216
3217 #[test]
3218 fn test_polars_keltner() -> PolarsResult<()> {
3219 let df = df![
3220 "high" => [12.0],
3221 "low" => [8.0],
3222 "close" => [10.0]
3223 ]?;
3224
3225 let out = df
3226 .lazy()
3227 .ta()
3228 .keltner_channels("high", "low", "close", 3, 3, 2.0)
3229 .collect()?;
3230
3231 let keltner = out.column("keltner_data")?.struct_()?;
3232 assert_eq!(
3233 keltner.field_by_name("middle".into())?.f64()?.get(0),
3234 Some(10.0)
3235 );
3236 assert_eq!(
3237 keltner.field_by_name("upper".into())?.f64()?.get(0),
3238 Some(18.0)
3239 );
3240 assert_eq!(
3241 keltner.field_by_name("lower".into())?.f64()?.get(0),
3242 Some(2.0)
3243 );
3244
3245 Ok(())
3246 }
3247
3248 #[test]
3249 fn test_polars_hma() -> PolarsResult<()> {
3250 let df = df![
3251 "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
3252 ]?;
3253
3254 let out = df.lazy().ta().hma("price", 4).collect()?;
3255
3256 let hma = out.column("hma")?.f64()?;
3257 assert!(hma.get(9).is_some());
3258
3259 Ok(())
3260 }
3261
3262 #[test]
3263 fn test_polars_anchored_vwap() -> PolarsResult<()> {
3264 let df = df![
3265 "price" => [10.0, 12.0, 15.0, 16.0],
3266 "volume" => [100.0, 200.0, 100.0, 100.0],
3267 "anchor" => [false, false, true, false]
3268 ]?;
3269
3270 let out = df
3271 .lazy()
3272 .ta()
3273 .anchored_vwap("price", "volume", "anchor")
3274 .collect()?;
3275
3276 let avwap = out.column("avwap")?.f64()?;
3277 assert_eq!(avwap.get(0), Some(10.0));
3278 assert_eq!(avwap.get(1), Some(11.333333333333334));
3279 assert_eq!(avwap.get(2), Some(15.0));
3280 assert_eq!(avwap.get(3), Some(15.5));
3281
3282 Ok(())
3283 }
3284
3285 #[test]
3286 fn test_polars_math_transforms() -> PolarsResult<()> {
3287 let df = df![
3288 "val" => [0.0, 1.5707963267948966] ]?;
3290
3291 let out = df.lazy().ta().sin("val").collect()?;
3292
3293 let sin = out.column("sin")?.f64()?;
3294 assert!((sin.get(0).unwrap() - 0.0).abs() < 1e-10);
3295 assert!((sin.get(1).unwrap() - 1.0).abs() < 1e-10);
3296
3297 Ok(())
3298 }
3299
3300 #[test]
3301 fn test_polars_math_operators() -> PolarsResult<()> {
3302 let df = df![
3303 "v1" => [10.0, 20.0],
3304 "v2" => [5.0, 30.0]
3305 ]?;
3306
3307 let out = df.lazy().ta().add("v1", "v2").ta().max("v1", 2).collect()?;
3308
3309 let add = out.column("add")?.f64()?;
3310 assert_eq!(add.get(0), Some(15.0));
3311 assert_eq!(add.get(1), Some(50.0));
3312
3313 let max = out.column("max")?.f64()?;
3314 assert_eq!(max.get(1), Some(20.0));
3315
3316 Ok(())
3317 }
3318
3319 #[test]
3320 fn test_polars_vpn() -> PolarsResult<()> {
3321 let df = df![
3322 "high" => [10.0, 11.0, 12.0],
3323 "low" => [9.0, 10.0, 11.0],
3324 "close" => [9.5, 10.5, 11.5],
3325 "volume" => [1000.0, 1100.0, 1200.0]
3326 ]?;
3327
3328 let out = df.lazy().ta().vpn("high", "low", "close", "volume", 30, 3).collect()?;
3329 let vpn = out.column("vpn")?.f64()?;
3330 assert!(vpn.get(2).is_some());
3331 Ok(())
3332 }
3333
3334 #[test]
3335 fn test_polars_gap_momentum() -> PolarsResult<()> {
3336 let df = df![
3337 "open" => [10.0, 11.0, 10.0],
3338 "close" => [10.5, 10.5, 9.5]
3339 ]?;
3340
3341 let out = df.lazy().ta().gap_momentum("open", "close", 10, 5).collect()?;
3342 let gm = out.column("gap_momentum")?.struct_()?;
3343 assert!(gm.field_by_name("gap_ratio".into())?.f64()?.get(2).is_some());
3344 assert!(gm.field_by_name("gap_signal".into())?.f64()?.get(2).is_some());
3345 Ok(())
3346 }
3347
3348 #[test]
3349 fn test_polars_autotune() -> PolarsResult<()> {
3350 let df = df![
3351 "price" => [100.0; 50]
3352 ]?;
3353
3354 let out = df.lazy().ta().autotune_filter("price", 20, 0.25).collect()?;
3355 let at = out.column("autotune")?.f64()?;
3356 assert!(at.get(49).is_some());
3357 Ok(())
3358 }
3359
3360 #[test]
3361 fn test_polars_adaptive_ema() -> PolarsResult<()> {
3362 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
3363 let out = df.lazy().ta().adaptive_ema("h", "l", "c", 10, 2).collect()?;
3364 assert!(out.column("adaptive_ema")?.f64()?.get(2).is_some());
3365 Ok(())
3366 }
3367
3368 #[test]
3369 fn test_polars_obvm() -> PolarsResult<()> {
3370 let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
3371 let out = df.lazy().ta().obvm("h", "l", "c", "v", 10, 3).collect()?;
3372 let data = out.column("obvm_data")?.struct_()?;
3373 assert!(data.field_by_name("obvm".into())?.f64()?.get(1).is_some());
3374 Ok(())
3375 }
3376
3377 #[test]
3378 fn test_polars_vfi() -> PolarsResult<()> {
3379 let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
3380 let out = df.lazy().ta().vfi("h", "l", "c", "v", 10, 0.2, 2.5, 3).collect()?;
3381 assert!(out.column("vfi")?.f64()?.get(1).is_some());
3382 Ok(())
3383 }
3384
3385 #[test]
3386 fn test_polars_sdo() -> PolarsResult<()> {
3387 let df = df!["p" => [10.0, 11.0, 12.0]]?;
3388 let out = df.lazy().ta().sdo("p", 2, 5, 3).collect()?;
3389 assert!(out.column("sdo")?.f64()?.get(2).is_some());
3390 Ok(())
3391 }
3392
3393 #[test]
3394 fn test_polars_rsmk() -> PolarsResult<()> {
3395 let df = df!["p" => [10.0, 11.0], "b" => [100.0, 101.0]]?;
3396 let out = df.lazy().ta().rsmk("p", "b", 90, 3).collect()?;
3397 assert!(out.column("rsmk")?.f64()?.get(1).is_some());
3398 Ok(())
3399 }
3400
3401 #[test]
3402 fn test_polars_rodc() -> PolarsResult<()> {
3403 let df = df!["p" => [10.0, 11.0, 10.0, 11.0, 12.0]]?;
3404 let out = df.lazy().ta().rodc("p", 10, 0.5, 3).collect()?;
3405 assert!(out.column("rodc")?.f64()?.get(4).is_some());
3406 Ok(())
3407 }
3408
3409 #[test]
3410 fn test_polars_reverse_ema() -> PolarsResult<()> {
3411 let df = df!["p" => [10.0, 11.0, 12.0]]?;
3412 let out = df.lazy().ta().reverse_ema("p", 0.1).collect()?;
3413 assert!(out.column("reverse_ema")?.f64()?.get(2).is_some());
3414 Ok(())
3415 }
3416
3417 #[test]
3418 fn test_polars_harrington_adx() -> PolarsResult<()> {
3419 let df = df!["h" => [10.0, 11.0, 12.0], "l" => [9.0, 10.0, 11.0], "c" => [9.5, 10.5, 11.5]]?;
3420 let out = df.lazy().ta().harrington_adx("h", "l", "c", 10, 1).collect()?;
3421 assert!(out.column("harrington_adx")?.f64()?.get(2).is_some());
3422 Ok(())
3423 }
3424
3425 #[test]
3426 fn test_polars_tradj_ema() -> PolarsResult<()> {
3427 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
3428 let out = df.lazy().ta().tradj_ema("h", "l", "c", 10, 2, 0.5).collect()?;
3429 assert!(out.column("tradj_ema")?.f64()?.get(2).is_some());
3430 Ok(())
3431 }
3432
3433 #[test]
3434 fn test_polars_sve_volatility_bands() -> PolarsResult<()> {
3435 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
3436 let out = df.lazy().ta().sve_volatility_bands("h", "l", "c", 10, 1.5, 1.0, 3).collect()?;
3437 let data = out.column("sve_bands_data")?.struct_()?;
3438 assert!(data.field_by_name("upper".into())?.f64()?.get(2).is_some());
3439 Ok(())
3440 }
3441
3442 #[test]
3443 fn test_polars_exp_dev_bands() -> PolarsResult<()> {
3444 let df = df!["p" => [10.0, 11.0, 12.0, 11.0, 10.0]]?;
3445 let out = df.lazy().ta().exp_dev_bands("p", 10, 2.0, true).collect()?;
3446 let data = out.column("exp_dev_bands_data")?.struct_()?;
3447 assert!(data.field_by_name("upper".into())?.f64()?.get(4).is_some());
3448 Ok(())
3449 }
3450
3451 #[test]
3452 fn test_regimes_conditioned_metrics() -> PolarsResult<()> {
3453 let df = df![
3454 "returns" => [0.01, 0.02, -0.01, -0.02, 0.01],
3455 "regime" => [0u32, 0, 1, 1, 0]
3456 ]?;
3457
3458 let out = df
3459 .lazy()
3460 .ta()
3461 .regimes_conditioned_metrics("returns", "regime", 252.0)
3462 .collect()?;
3463
3464 assert_eq!(out.height(), 2);
3465 assert!(out.column("sharpe_ratio").is_ok());
3466 assert!(out.column("skewness").is_ok());
3467 assert!(out.column("kurtosis").is_ok());
3468 assert!(out.column("sortino_ratio").is_ok());
3469
3470 Ok(())
3471 }
3472}
3473
3474impl QuantWaveExt for LazyFrame {
3475 fn ta(&self) -> QuantWaveNamespace<'_> {
3476 QuantWaveNamespace(self)
3477 }
3478}