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