Skip to main content

quantwave_polars/
lib.rs

1use polars::prelude::*;
2use quantwave_core::traits::Next;
3use quantwave_core::*;
4
5pub mod prelude {
6    pub use crate::{QuantWaveExt, QuantWaveNamespace};
7}
8
9pub trait QuantWaveExt {
10    fn ta(&self) -> QuantWaveNamespace<'_>;
11}
12
13pub struct QuantWaveNamespace<'a>(&'a LazyFrame);
14
15impl<'a> QuantWaveNamespace<'a> {
16    pub fn acos(self, name: &str) -> LazyFrame {
17        self.math_transform_1_in_1_out::<ACOS>(name, "acos")
18    }
19    pub fn asin(self, name: &str) -> LazyFrame {
20        self.math_transform_1_in_1_out::<ASIN>(name, "asin")
21    }
22    pub fn atan(self, name: &str) -> LazyFrame {
23        self.math_transform_1_in_1_out::<ATAN>(name, "atan")
24    }
25    pub fn ceil(self, name: &str) -> LazyFrame {
26        self.math_transform_1_in_1_out::<CEIL>(name, "ceil")
27    }
28    pub fn cos(self, name: &str) -> LazyFrame {
29        self.math_transform_1_in_1_out::<COS>(name, "cos")
30    }
31    pub fn cosh(self, name: &str) -> LazyFrame {
32        self.math_transform_1_in_1_out::<COSH>(name, "cosh")
33    }
34    pub fn exp(self, name: &str) -> LazyFrame {
35        self.math_transform_1_in_1_out::<EXP>(name, "exp")
36    }
37    pub fn floor(self, name: &str) -> LazyFrame {
38        self.math_transform_1_in_1_out::<FLOOR>(name, "floor")
39    }
40    pub fn ln(self, name: &str) -> LazyFrame {
41        self.math_transform_1_in_1_out::<LN>(name, "ln")
42    }
43    pub fn log10(self, name: &str) -> LazyFrame {
44        self.math_transform_1_in_1_out::<LOG10>(name, "log10")
45    }
46    pub fn sin(self, name: &str) -> LazyFrame {
47        self.math_transform_1_in_1_out::<SIN>(name, "sin")
48    }
49    pub fn sinh(self, name: &str) -> LazyFrame {
50        self.math_transform_1_in_1_out::<SINH>(name, "sinh")
51    }
52    pub fn sqrt(self, name: &str) -> LazyFrame {
53        self.math_transform_1_in_1_out::<SQRT>(name, "sqrt")
54    }
55    pub fn tan(self, name: &str) -> LazyFrame {
56        self.math_transform_1_in_1_out::<TAN>(name, "tan")
57    }
58    pub fn tanh(self, name: &str) -> LazyFrame {
59        self.math_transform_1_in_1_out::<TANH>(name, "tanh")
60    }
61
62    pub fn add(self, in1: &str, in2: &str) -> LazyFrame {
63        self.math_operator_2_in_1_out::<ADD>(in1, in2, "add")
64    }
65    pub fn sub(self, in1: &str, in2: &str) -> LazyFrame {
66        self.math_operator_2_in_1_out::<SUB>(in1, in2, "sub")
67    }
68    pub fn mult(self, in1: &str, in2: &str) -> LazyFrame {
69        self.math_operator_2_in_1_out::<MULT>(in1, in2, "mult")
70    }
71    pub fn div(self, in1: &str, in2: &str) -> LazyFrame {
72        self.math_operator_2_in_1_out::<DIV>(in1, in2, "div")
73    }
74
75    pub fn max(self, name: &str, period: usize) -> LazyFrame {
76        self.math_operator_1_in_1_out_period::<MAX>(name, period, "max")
77    }
78    pub fn maxindex(self, name: &str, period: usize) -> LazyFrame {
79        self.math_operator_1_in_1_out_period::<MAXINDEX>(name, period, "maxindex")
80    }
81    pub fn min(self, name: &str, period: usize) -> LazyFrame {
82        self.math_operator_1_in_1_out_period::<MIN>(name, period, "min")
83    }
84    pub fn minindex(self, name: &str, period: usize) -> LazyFrame {
85        self.math_operator_1_in_1_out_period::<MININDEX>(name, period, "minindex")
86    }
87    pub fn sum(self, name: &str, period: usize) -> LazyFrame {
88        self.math_operator_1_in_1_out_period::<SUM>(name, period, "sum")
89    }
90
91    pub fn sma(self, name: &str, period: usize) -> LazyFrame {
92        self.math_operator_1_in_1_out_period::<SMA>(name, period, "sma")
93    }
94    pub fn ema(self, name: &str, period: usize) -> LazyFrame {
95        self.math_operator_1_in_1_out_period::<EMA>(name, period, "ema")
96    }
97    pub fn wma(self, name: &str, period: usize) -> LazyFrame {
98        self.math_operator_1_in_1_out_period::<WMA>(name, period, "wma")
99    }
100    pub fn dema(self, name: &str, period: usize) -> LazyFrame {
101        self.math_operator_1_in_1_out_period::<DEMA>(name, period, "dema")
102    }
103    pub fn trima(self, name: &str, period: usize) -> LazyFrame {
104        self.math_operator_1_in_1_out_period::<TRIMA>(name, period, "trima")
105    }
106    pub fn kama(self, name: &str, period: usize) -> LazyFrame {
107        self.math_operator_1_in_1_out_period::<KAMA>(name, period, "kama")
108    }
109    pub fn midpoint(self, name: &str, period: usize) -> LazyFrame {
110        self.math_operator_1_in_1_out_period::<MIDPOINT>(name, period, "midpoint")
111    }
112    pub fn ht_trendline(self, name: &str) -> LazyFrame {
113        self.math_transform_1_in_1_out::<HT_TRENDLINE>(name, "ht_trendline")
114    }
115    pub fn midprice(self, high: &str, low: &str, period: usize) -> LazyFrame {
116        self.math_operator_2_in_1_out_period::<MIDPRICE>(high, low, period, "midprice")
117    }
118
119    pub fn rsi(self, name: &str, period: usize) -> LazyFrame {
120        self.math_operator_1_in_1_out_period::<RSI>(name, period, "rsi")
121    }
122    pub fn mom(self, name: &str, period: usize) -> LazyFrame {
123        self.math_operator_1_in_1_out_period::<MOM>(name, period, "mom")
124    }
125    pub fn roc(self, name: &str, period: usize) -> LazyFrame {
126        self.math_operator_1_in_1_out_period::<ROC>(name, period, "roc")
127    }
128    pub fn rocp(self, name: &str, period: usize) -> LazyFrame {
129        self.math_operator_1_in_1_out_period::<ROCP>(name, period, "rocp")
130    }
131    pub fn rocr(self, name: &str, period: usize) -> LazyFrame {
132        self.math_operator_1_in_1_out_period::<ROCR>(name, period, "rocr")
133    }
134    pub fn rocr100(self, name: &str, period: usize) -> LazyFrame {
135        self.math_operator_1_in_1_out_period::<ROCR100>(name, period, "rocr100")
136    }
137    pub fn trix(self, name: &str, period: usize) -> LazyFrame {
138        self.math_operator_1_in_1_out_period::<TRIX>(name, period, "trix")
139    }
140    pub fn cmo(self, name: &str, period: usize) -> LazyFrame {
141        self.math_operator_1_in_1_out_period::<CMO>(name, period, "cmo")
142    }
143
144    pub fn adx(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
145        self.ta_3_in_1_out_period::<ADX>(high, low, close, period, "adx")
146    }
147    pub fn adxr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
148        self.ta_3_in_1_out_period::<ADXR>(high, low, close, period, "adxr")
149    }
150    pub fn cci(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
151        self.ta_3_in_1_out_period::<CCI>(high, low, close, period, "cci")
152    }
153    pub fn willr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
154        self.ta_3_in_1_out_period::<WILLR>(high, low, close, period, "willr")
155    }
156    pub fn dx(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
157        self.ta_3_in_1_out_period::<DX>(high, low, close, period, "dx")
158    }
159    pub fn plus_di(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
160        self.ta_3_in_1_out_period::<PLUS_DI>(high, low, close, period, "plus_di")
161    }
162    pub fn minus_di(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
163        self.ta_3_in_1_out_period::<MINUS_DI>(high, low, close, period, "minus_di")
164    }
165
166    pub fn ta_atr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
167        self.ta_3_in_1_out_period::<TaATR>(high, low, close, period, "ta_atr")
168    }
169    pub fn ta_natr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
170        self.ta_3_in_1_out_period::<TaNATR>(high, low, close, period, "ta_natr")
171    }
172    pub fn ta_trange(self, high: &str, low: &str, close: &str) -> LazyFrame {
173        self.ta_3_in_1_out_default::<TaTRANGE>(high, low, close, "ta_trange")
174    }
175
176    pub fn obv(self, close: &str, volume: &str) -> LazyFrame {
177        self.math_operator_2_in_1_out::<OBV>(close, volume, "obv")
178    }
179    pub fn ad(self, high: &str, low: &str, close: &str, volume: &str) -> LazyFrame {
180        self.ta_4_in_1_out_default::<AD>(high, low, close, volume, "ad")
181    }
182    pub fn adosc(
183        self,
184        high: &str,
185        low: &str,
186        close: &str,
187        volume: &str,
188        fast: usize,
189        slow: usize,
190    ) -> LazyFrame {
191        let high_str = high.to_string();
192        let low_str = low.to_string();
193        let close_str = close.to_string();
194        let volume_str = volume.to_string();
195        self.0.clone().with_columns([as_struct(vec![
196            col(&high_str),
197            col(&low_str),
198            col(&close_str),
199            col(&volume_str),
200        ])
201        .map(
202            move |s| {
203                let ca = s.struct_()?;
204                let s_h = ca.field_by_name(&high_str)?;
205                let s_l = ca.field_by_name(&low_str)?;
206                let s_c = ca.field_by_name(&close_str)?;
207                let s_v = ca.field_by_name(&volume_str)?;
208
209                let high = s_h.f64()?;
210                let low = s_l.f64()?;
211                let close = s_c.f64()?;
212                let volume = s_v.f64()?;
213
214                let mut indicator = ADOSC::new(fast, slow);
215                let mut values = Vec::with_capacity(s.len());
216
217                for i in 0..s.len() {
218                    let h = high.get(i).unwrap_or(f64::NAN);
219                    let l = low.get(i).unwrap_or(f64::NAN);
220                    let c = close.get(i).unwrap_or(f64::NAN);
221                    let v = volume.get(i).unwrap_or(f64::NAN);
222                    values.push(indicator.next((h, l, c, v)));
223                }
224
225                Ok(Some(Column::from(Series::new("adosc".into(), values))))
226            },
227            GetOutput::from_type(DataType::Float64),
228        )
229        .alias("adosc")])
230    }
231
232    pub fn aroon(self, high: &str, low: &str, period: usize) -> LazyFrame {
233        let high_str = high.to_string();
234        let low_str = low.to_string();
235        self.0
236            .clone()
237            .with_columns([as_struct(vec![col(&high_str), col(&low_str)])
238                .map(
239                    move |s| {
240                        let ca = s.struct_()?;
241                        let s_h = ca.field_by_name(&high_str)?;
242                        let s_l = ca.field_by_name(&low_str)?;
243                        let high = s_h.f64()?;
244                        let low = s_l.f64()?;
245
246                        let mut indicator = AROON::new(period);
247                        let mut up_vals = Vec::with_capacity(s.len());
248                        let mut down_vals = Vec::with_capacity(s.len());
249
250                        for i in 0..s.len() {
251                            let h = high.get(i).unwrap_or(f64::NAN);
252                            let l = low.get(i).unwrap_or(f64::NAN);
253                            let (up, down) = indicator.next((h, l));
254                            up_vals.push(up);
255                            down_vals.push(down);
256                        }
257
258                        let s_up = Series::new("aroon_up".into(), up_vals);
259                        let s_down = Series::new("aroon_down".into(), down_vals);
260                        let struct_series = StructChunked::from_series(
261                            "aroon_result".into(),
262                            s.len(),
263                            [s_up, s_down].iter(),
264                        )?;
265                        Ok(Some(Column::from(struct_series.into_series())))
266                    },
267                    GetOutput::from_type(DataType::Struct(vec![
268                        Field::new("aroon_up".into(), DataType::Float64),
269                        Field::new("aroon_down".into(), DataType::Float64),
270                    ])),
271                )
272                .alias("aroon")])
273    }
274
275    pub fn stoch(
276        self,
277        high: &str,
278        low: &str,
279        close: &str,
280        fastk: usize,
281        slowk: usize,
282        slowk_matype: talib::MaType,
283        slowd: usize,
284        slowd_matype: talib::MaType,
285    ) -> LazyFrame {
286        let high_str = high.to_string();
287        let low_str = low.to_string();
288        let close_str = close.to_string();
289        self.0.clone().with_columns([as_struct(vec![
290            col(&high_str),
291            col(&low_str),
292            col(&close_str),
293        ])
294        .map(
295            move |s| {
296                let ca = s.struct_()?;
297                let s_h = ca.field_by_name(&high_str)?;
298                let s_l = ca.field_by_name(&low_str)?;
299                let s_c = ca.field_by_name(&close_str)?;
300                let high = s_h.f64()?;
301                let low = s_l.f64()?;
302                let close = s_c.f64()?;
303
304                let mut indicator = STOCH::new(fastk, slowk, slowk_matype, slowd, slowd_matype);
305                let mut k_vals = Vec::with_capacity(s.len());
306                let mut d_vals = Vec::with_capacity(s.len());
307
308                for i in 0..s.len() {
309                    let h = high.get(i).unwrap_or(f64::NAN);
310                    let l = low.get(i).unwrap_or(f64::NAN);
311                    let c = close.get(i).unwrap_or(f64::NAN);
312                    let (k, d) = indicator.next((h, l, c));
313                    k_vals.push(k);
314                    d_vals.push(d);
315                }
316
317                let s_k = Series::new("slowk".into(), k_vals);
318                let s_d = Series::new("slowd".into(), d_vals);
319                let struct_series =
320                    StructChunked::from_series("stoch_result".into(), s.len(), [s_k, s_d].iter())?;
321                Ok(Some(Column::from(struct_series.into_series())))
322            },
323            GetOutput::from_type(DataType::Struct(vec![
324                Field::new("slowk".into(), DataType::Float64),
325                Field::new("slowd".into(), DataType::Float64),
326            ])),
327        )
328        .alias("stoch")])
329    }
330
331    pub fn avgprice(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
332        self.ta_4_in_1_out_default::<AVGPRICE>(open, high, low, close, "avgprice")
333    }
334    pub fn medprice(self, high: &str, low: &str) -> LazyFrame {
335        self.math_operator_2_in_1_out::<MEDPRICE>(high, low, "medprice")
336    }
337    pub fn typprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
338        self.ta_3_in_1_out_default::<TYPPRICE>(high, low, close, "typprice")
339    }
340    pub fn wclprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
341        self.ta_3_in_1_out_default::<WCLPRICE>(high, low, close, "wclprice")
342    }
343
344    pub fn ht_dcperiod(self, name: &str) -> LazyFrame {
345        self.math_transform_1_in_1_out::<HT_DCPERIOD>(name, "ht_dcperiod")
346    }
347    pub fn ht_dcphase(self, name: &str) -> LazyFrame {
348        self.math_transform_1_in_1_out::<HT_DCPHASE>(name, "ht_dcphase")
349    }
350    pub fn ht_trendmode(self, name: &str) -> LazyFrame {
351        self.math_transform_1_in_1_out::<HT_TRENDMODE>(name, "ht_trendmode")
352    }
353
354    pub fn ta_stddev(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
355        let name_str = name.to_string();
356        self.0.clone().with_columns([col(&name_str)
357            .map(
358                move |s| {
359                    let ca = s.f64()?;
360                    let mut indicator = TaSTDDEV::new(period, nbdev);
361                    let mut values = Vec::with_capacity(s.len());
362                    for i in 0..s.len() {
363                        let val = ca.get(i).unwrap_or(f64::NAN);
364                        values.push(indicator.next(val));
365                    }
366                    Ok(Some(Column::from(Series::new("ta_stddev".into(), values))))
367                },
368                GetOutput::from_type(DataType::Float64),
369            )
370            .alias("ta_stddev")])
371    }
372    pub fn ta_var(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
373        let name_str = name.to_string();
374        self.0.clone().with_columns([col(&name_str)
375            .map(
376                move |s| {
377                    let ca = s.f64()?;
378                    let mut indicator = TaVAR::new(period, nbdev);
379                    let mut values = Vec::with_capacity(s.len());
380                    for i in 0..s.len() {
381                        let val = ca.get(i).unwrap_or(f64::NAN);
382                        values.push(indicator.next(val));
383                    }
384                    Ok(Some(Column::from(Series::new("ta_var".into(), values))))
385                },
386                GetOutput::from_type(DataType::Float64),
387            )
388            .alias("ta_var")])
389    }
390    pub fn ta_beta(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
391        self.math_operator_2_in_1_out_period::<TaBETA>(in1, in2, period, "ta_beta")
392    }
393    pub fn ta_correl(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
394        self.math_operator_2_in_1_out_period::<TaCORREL>(in1, in2, period, "ta_correl")
395    }
396    pub fn ta_linearreg(self, name: &str, period: usize) -> LazyFrame {
397        self.math_operator_1_in_1_out_period::<TaLINEARREG>(name, period, "ta_linearreg")
398    }
399    pub fn ta_linearreg_slope(self, name: &str, period: usize) -> LazyFrame {
400        self.math_operator_1_in_1_out_period::<TaLINEARREG_SLOPE>(
401            name,
402            period,
403            "ta_linearreg_slope",
404        )
405    }
406    pub fn ta_linearreg_intercept(self, name: &str, period: usize) -> LazyFrame {
407        self.math_operator_1_in_1_out_period::<TaLINEARREG_INTERCEPT>(
408            name,
409            period,
410            "ta_linearreg_intercept",
411        )
412    }
413    pub fn ta_linearreg_angle(self, name: &str, period: usize) -> LazyFrame {
414        self.math_operator_1_in_1_out_period::<TaLINEARREG_ANGLE>(
415            name,
416            period,
417            "ta_linearreg_angle",
418        )
419    }
420    pub fn ta_tsf(self, name: &str, period: usize) -> LazyFrame {
421        self.math_operator_1_in_1_out_period::<TaTSF>(name, period, "ta_tsf")
422    }
423
424    pub fn cdl_2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
425        self.ta_4_in_1_out_default::<CDL2CROWS>(open, high, low, close, "cdl_2crows")
426    }
427    pub fn cdl_3blackcrows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
428        self.ta_4_in_1_out_default::<CDL3BLACKCROWS>(open, high, low, close, "cdl_3blackcrows")
429    }
430    pub fn cdl_3inside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
431        self.ta_4_in_1_out_default::<CDL3INSIDE>(open, high, low, close, "cdl_3inside")
432    }
433    pub fn cdl_3linestrike(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
434        self.ta_4_in_1_out_default::<CDL3LINESTRIKE>(open, high, low, close, "cdl_3linestrike")
435    }
436    pub fn cdl_3outside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
437        self.ta_4_in_1_out_default::<CDL3OUTSIDE>(open, high, low, close, "cdl_3outside")
438    }
439    pub fn cdl_3starsinsouth(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
440        self.ta_4_in_1_out_default::<CDL3STARSINSOUTH>(open, high, low, close, "cdl_3starsinsouth")
441    }
442    pub fn cdl_3whitesoldiers(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
443        self.ta_4_in_1_out_default::<CDL3WHITESOLDIERS>(
444            open,
445            high,
446            low,
447            close,
448            "cdl_3whitesoldiers",
449        )
450    }
451    pub fn cdl_abandonedbaby(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
452        self.ta_4_in_1_out_default::<CDLABANDONEDBABY>(open, high, low, close, "cdl_abandonedbaby")
453    }
454    pub fn cdl_advanceblock(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
455        self.ta_4_in_1_out_default::<CDLADVANCEBLOCK>(open, high, low, close, "cdl_advanceblock")
456    }
457    pub fn cdl_belthold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
458        self.ta_4_in_1_out_default::<CDLBELTHOLD>(open, high, low, close, "cdl_belthold")
459    }
460    pub fn cdl_breakaway(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
461        self.ta_4_in_1_out_default::<CDLBREAKAWAY>(open, high, low, close, "cdl_breakaway")
462    }
463    pub fn cdl_closingmarubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
464        self.ta_4_in_1_out_default::<CDLCLOSINGMARUBOZU>(
465            open,
466            high,
467            low,
468            close,
469            "cdl_closingmarubozu",
470        )
471    }
472    pub fn cdl_concealbabyswall(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
473        self.ta_4_in_1_out_default::<CDLCONCEALBABYSWALL>(
474            open,
475            high,
476            low,
477            close,
478            "cdl_concealbabyswall",
479        )
480    }
481    pub fn cdl_counterattack(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
482        self.ta_4_in_1_out_default::<CDLCOUNTERATTACK>(open, high, low, close, "cdl_counterattack")
483    }
484    pub fn cdl_darkcloudcover(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
485        self.ta_4_in_1_out_default::<CDLDARKCLOUDCOVER>(
486            open,
487            high,
488            low,
489            close,
490            "cdl_darkcloudcover",
491        )
492    }
493    pub fn cdl_doji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
494        self.ta_4_in_1_out_default::<CDLDOJI>(open, high, low, close, "cdl_doji")
495    }
496    pub fn cdl_dojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
497        self.ta_4_in_1_out_default::<CDLDOJISTAR>(open, high, low, close, "cdl_dojistar")
498    }
499    pub fn cdl_dragonflydoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
500        self.ta_4_in_1_out_default::<CDLDRAGONFLYDOJI>(open, high, low, close, "cdl_dragonflydoji")
501    }
502    pub fn cdl_engulfing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
503        self.ta_4_in_1_out_default::<CDLENGULFING>(open, high, low, close, "cdl_engulfing")
504    }
505    pub fn cdl_eveningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
506        self.ta_4_in_1_out_default::<CDLEVENINGDOJISTAR>(
507            open,
508            high,
509            low,
510            close,
511            "cdl_eveningdojistar",
512        )
513    }
514    pub fn cdl_eveningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
515        self.ta_4_in_1_out_default::<CDLEVENINGSTAR>(open, high, low, close, "cdl_eveningstar")
516    }
517    pub fn cdl_gapsidesidewhite(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
518        self.ta_4_in_1_out_default::<CDLGAPSIDESIDEWHITE>(
519            open,
520            high,
521            low,
522            close,
523            "cdl_gapsidesidewhite",
524        )
525    }
526    pub fn cdl_gravestonedoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
527        self.ta_4_in_1_out_default::<CDLGRAVESTONEDOJI>(
528            open,
529            high,
530            low,
531            close,
532            "cdl_gravestonedoji",
533        )
534    }
535    pub fn cdl_hammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
536        self.ta_4_in_1_out_default::<CDLHAMMER>(open, high, low, close, "cdl_hammer")
537    }
538    pub fn cdl_hangingman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
539        self.ta_4_in_1_out_default::<CDLHANGINGMAN>(open, high, low, close, "cdl_hangingman")
540    }
541    pub fn cdl_harami(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
542        self.ta_4_in_1_out_default::<CDLHARAMI>(open, high, low, close, "cdl_harami")
543    }
544    pub fn cdl_haramicross(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
545        self.ta_4_in_1_out_default::<CDLHARAMICROSS>(open, high, low, close, "cdl_haramicross")
546    }
547    pub fn cdl_highwave(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
548        self.ta_4_in_1_out_default::<CDLHIGHWAVE>(open, high, low, close, "cdl_highwave")
549    }
550    pub fn cdl_hikkake(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
551        self.ta_4_in_1_out_default::<CDLHIKKAKE>(open, high, low, close, "cdl_hikkake")
552    }
553    pub fn cdl_hikkakemod(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
554        self.ta_4_in_1_out_default::<CDLHIKKAKEMOD>(open, high, low, close, "cdl_hikkakemod")
555    }
556    pub fn cdl_homingpigeon(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
557        self.ta_4_in_1_out_default::<CDLHOMINGPIGEON>(open, high, low, close, "cdl_homingpigeon")
558    }
559    pub fn cdl_identical3crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
560        self.ta_4_in_1_out_default::<CDLIDENTICAL3CROWS>(
561            open,
562            high,
563            low,
564            close,
565            "cdl_identical3crows",
566        )
567    }
568    pub fn cdl_inneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
569        self.ta_4_in_1_out_default::<CDLINNECK>(open, high, low, close, "cdl_inneck")
570    }
571    pub fn cdl_invertedhammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
572        self.ta_4_in_1_out_default::<CDLINVERTEDHAMMER>(
573            open,
574            high,
575            low,
576            close,
577            "cdl_invertedhammer",
578        )
579    }
580    pub fn cdl_kicking(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
581        self.ta_4_in_1_out_default::<CDLKICKING>(open, high, low, close, "cdl_kicking")
582    }
583    pub fn cdl_kickingbylength(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
584        self.ta_4_in_1_out_default::<CDLKICKINGBYLENGTH>(
585            open,
586            high,
587            low,
588            close,
589            "cdl_kickingbylength",
590        )
591    }
592    pub fn cdl_ladderbottom(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
593        self.ta_4_in_1_out_default::<CDLLADDERBOTTOM>(open, high, low, close, "cdl_ladderbottom")
594    }
595    pub fn cdl_longleggeddoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
596        self.ta_4_in_1_out_default::<CDLLONGLEGGEDDOJI>(
597            open,
598            high,
599            low,
600            close,
601            "cdl_longleggeddoji",
602        )
603    }
604    pub fn cdl_longline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
605        self.ta_4_in_1_out_default::<CDLLONGLINE>(open, high, low, close, "cdl_longline")
606    }
607    pub fn cdl_marubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
608        self.ta_4_in_1_out_default::<CDLMARUBOZU>(open, high, low, close, "cdl_marubozu")
609    }
610    pub fn cdl_matchinglow(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
611        self.ta_4_in_1_out_default::<CDLMATCHINGLOW>(open, high, low, close, "cdl_matchinglow")
612    }
613    pub fn cdl_mathold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
614        self.ta_4_in_1_out_default::<CDLMATHOLD>(open, high, low, close, "cdl_mathold")
615    }
616    pub fn cdl_morningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
617        self.ta_4_in_1_out_default::<CDLMORNINGDOJISTAR>(
618            open,
619            high,
620            low,
621            close,
622            "cdl_morningdojistar",
623        )
624    }
625    pub fn cdl_morningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
626        self.ta_4_in_1_out_default::<CDLMORNINGSTAR>(open, high, low, close, "cdl_morningstar")
627    }
628    pub fn cdl_onneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
629        self.ta_4_in_1_out_default::<CDLONNECK>(open, high, low, close, "cdl_onneck")
630    }
631    pub fn cdl_piercing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
632        self.ta_4_in_1_out_default::<CDLPIERCING>(open, high, low, close, "cdl_piercing")
633    }
634    pub fn cdl_rickshawman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
635        self.ta_4_in_1_out_default::<CDLRICKSHAWMAN>(open, high, low, close, "cdl_rickshawman")
636    }
637    pub fn cdl_risefall3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
638        self.ta_4_in_1_out_default::<CDLRISEFALL3METHODS>(
639            open,
640            high,
641            low,
642            close,
643            "cdl_risefall3methods",
644        )
645    }
646    pub fn cdl_separatinglines(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
647        self.ta_4_in_1_out_default::<CDLSEPARATINGLINES>(
648            open,
649            high,
650            low,
651            close,
652            "cdl_separatinglines",
653        )
654    }
655    pub fn cdl_shootingstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
656        self.ta_4_in_1_out_default::<CDLSHOOTINGSTAR>(open, high, low, close, "cdl_shootingstar")
657    }
658    pub fn cdl_shortline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
659        self.ta_4_in_1_out_default::<CDLSHORTLINE>(open, high, low, close, "cdl_shortline")
660    }
661    pub fn cdl_spinningtop(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
662        self.ta_4_in_1_out_default::<CDLSPINNINGTOP>(open, high, low, close, "cdl_spinningtop")
663    }
664    pub fn cdl_stalledpattern(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
665        self.ta_4_in_1_out_default::<CDLSTALLEDPATTERN>(
666            open,
667            high,
668            low,
669            close,
670            "cdl_stalledpattern",
671        )
672    }
673    pub fn cdl_sticksandwich(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
674        self.ta_4_in_1_out_default::<CDLSTICKSANDWICH>(open, high, low, close, "cdl_sticksandwich")
675    }
676    pub fn cdl_takuri(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
677        self.ta_4_in_1_out_default::<CDLTAKURI>(open, high, low, close, "cdl_takuri")
678    }
679    pub fn cdl_tasukigap(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
680        self.ta_4_in_1_out_default::<CDLTASUKIGAP>(open, high, low, close, "cdl_tasukigap")
681    }
682    pub fn cdl_thrusting(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
683        self.ta_4_in_1_out_default::<CDLTHRUSTING>(open, high, low, close, "cdl_thrusting")
684    }
685    pub fn cdl_tristar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
686        self.ta_4_in_1_out_default::<CDLTRISTAR>(open, high, low, close, "cdl_tristar")
687    }
688    pub fn cdl_unique3river(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
689        self.ta_4_in_1_out_default::<CDLUNIQUE3RIVER>(open, high, low, close, "cdl_unique3river")
690    }
691    pub fn cdl_upsidegap2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
692        self.ta_4_in_1_out_default::<CDLUPSIDEGAP2CROWS>(
693            open,
694            high,
695            low,
696            close,
697            "cdl_upsidegap2crows",
698        )
699    }
700    pub fn cdl_xsidegap3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
701        self.ta_4_in_1_out_default::<CDLXSIDEGAP3METHODS>(
702            open,
703            high,
704            low,
705            close,
706            "cdl_xsidegap3methods",
707        )
708    }
709
710    pub fn macd(self, name: &str, fast: usize, slow: usize, signal: usize) -> LazyFrame {
711        let name_str = name.to_string();
712        self.0.clone().with_columns([col(&name_str)
713            .map(
714                move |s| {
715                    let ca = s.f64()?;
716                    let mut indicator = MACD::new(fast, slow, signal);
717                    let mut macd_vals = Vec::with_capacity(s.len());
718                    let mut signal_vals = Vec::with_capacity(s.len());
719                    let mut hist_vals = Vec::with_capacity(s.len());
720
721                    for i in 0..s.len() {
722                        let val = ca.get(i).unwrap_or(f64::NAN);
723                        let (m, s_val, h) = indicator.next(val);
724                        macd_vals.push(m);
725                        signal_vals.push(s_val);
726                        hist_vals.push(h);
727                    }
728
729                    let s_macd = Series::new("macd".into(), macd_vals);
730                    let s_signal = Series::new("macd_signal".into(), signal_vals);
731                    let s_hist = Series::new("macd_hist".into(), hist_vals);
732
733                    let struct_series = StructChunked::from_series(
734                        "macd_result".into(),
735                        s.len(),
736                        [s_macd, s_signal, s_hist].iter(),
737                    )?;
738                    Ok(Some(Column::from(struct_series.into_series())))
739                },
740                GetOutput::from_type(DataType::Struct(vec![
741                    Field::new("macd".into(), DataType::Float64),
742                    Field::new("macd_signal".into(), DataType::Float64),
743                    Field::new("macd_hist".into(), DataType::Float64),
744                ])),
745            )
746            .alias("macd")])
747    }
748
749    pub fn bbands(
750        self,
751        name: &str,
752        period: usize,
753        nbdevup: f64,
754        nbdevdn: f64,
755        matype: talib::MaType,
756    ) -> LazyFrame {
757        let name_str = name.to_string();
758        self.0.clone().with_columns([col(&name_str)
759            .map(
760                move |s| {
761                    let ca = s.f64()?;
762                    let mut indicator = BBANDS::new(period, nbdevup, nbdevdn, matype);
763                    let mut upper_vals = Vec::with_capacity(s.len());
764                    let mut middle_vals = Vec::with_capacity(s.len());
765                    let mut lower_vals = Vec::with_capacity(s.len());
766
767                    for i in 0..s.len() {
768                        let val = ca.get(i).unwrap_or(f64::NAN);
769                        let (u, m, l) = indicator.next(val);
770                        upper_vals.push(u);
771                        middle_vals.push(m);
772                        lower_vals.push(l);
773                    }
774
775                    let s_upper = Series::new("upper".into(), upper_vals);
776                    let s_middle = Series::new("middle".into(), middle_vals);
777                    let s_lower = Series::new("lower".into(), lower_vals);
778
779                    let struct_series = StructChunked::from_series(
780                        "bbands_result".into(),
781                        s.len(),
782                        [s_upper, s_middle, s_lower].iter(),
783                    )?;
784                    Ok(Some(Column::from(struct_series.into_series())))
785                },
786                GetOutput::from_type(DataType::Struct(vec![
787                    Field::new("upper".into(), DataType::Float64),
788                    Field::new("middle".into(), DataType::Float64),
789                    Field::new("lower".into(), DataType::Float64),
790                ])),
791            )
792            .alias("bbands")])
793    }
794
795    fn ta_3_in_1_out_period<I>(
796        self,
797        in1: &str,
798        in2: &str,
799        in3: &str,
800        period: usize,
801        output_name: &str,
802    ) -> LazyFrame
803    where
804        I: Next<(f64, f64, f64), Output = f64> + Send + Sync + 'static,
805        I: From<usize>,
806    {
807        let in1_str = in1.to_string();
808        let in2_str = in2.to_string();
809        let in3_str = in3.to_string();
810        let output_name_str = output_name.to_string();
811        let output_name_for_closure = output_name_str.clone();
812        self.0.clone().with_columns(
813            [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
814                .map(
815                    move |s| {
816                        let ca = s.struct_()?;
817                        let s1 = ca.field_by_name(&in1_str)?;
818                        let s2 = ca.field_by_name(&in2_str)?;
819                        let s3 = ca.field_by_name(&in3_str)?;
820
821                        let ca1 = s1.f64()?;
822                        let ca2 = s2.f64()?;
823                        let ca3 = s3.f64()?;
824
825                        let mut indicator = I::from(period);
826                        let mut values = Vec::with_capacity(s.len());
827
828                        for i in 0..s.len() {
829                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
830                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
831                            let v3 = ca3.get(i).unwrap_or(f64::NAN);
832                            values.push(indicator.next((v1, v2, v3)));
833                        }
834
835                        Ok(Some(Column::from(Series::new(
836                            output_name_for_closure.clone().into(),
837                            values,
838                        ))))
839                    },
840                    GetOutput::from_type(DataType::Float64),
841                )
842                .alias(&output_name_str)],
843        )
844    }
845
846    fn ta_3_in_1_out_default<I>(
847        self,
848        in1: &str,
849        in2: &str,
850        in3: &str,
851        output_name: &str,
852    ) -> LazyFrame
853    where
854        I: Next<(f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
855    {
856        let in1_str = in1.to_string();
857        let in2_str = in2.to_string();
858        let in3_str = in3.to_string();
859        let output_name_str = output_name.to_string();
860        let output_name_for_closure = output_name_str.clone();
861        self.0.clone().with_columns(
862            [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
863                .map(
864                    move |s| {
865                        let ca = s.struct_()?;
866                        let s1 = ca.field_by_name(&in1_str)?;
867                        let s2 = ca.field_by_name(&in2_str)?;
868                        let s3 = ca.field_by_name(&in3_str)?;
869
870                        let ca1 = s1.f64()?;
871                        let ca2 = s2.f64()?;
872                        let ca3 = s3.f64()?;
873
874                        let mut indicator = I::default();
875                        let mut values = Vec::with_capacity(s.len());
876
877                        for i in 0..s.len() {
878                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
879                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
880                            let v3 = ca3.get(i).unwrap_or(f64::NAN);
881                            values.push(indicator.next((v1, v2, v3)));
882                        }
883
884                        Ok(Some(Column::from(Series::new(
885                            output_name_for_closure.clone().into(),
886                            values,
887                        ))))
888                    },
889                    GetOutput::from_type(DataType::Float64),
890                )
891                .alias(&output_name_str)],
892        )
893    }
894
895    fn ta_4_in_1_out_default<I>(
896        self,
897        in1: &str,
898        in2: &str,
899        in3: &str,
900        in4: &str,
901        output_name: &str,
902    ) -> LazyFrame
903    where
904        I: Next<(f64, f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
905    {
906        let in1_str = in1.to_string();
907        let in2_str = in2.to_string();
908        let in3_str = in3.to_string();
909        let in4_str = in4.to_string();
910        let output_name_str = output_name.to_string();
911        let output_name_for_closure = output_name_str.clone();
912        self.0.clone().with_columns([as_struct(vec![
913            col(&in1_str),
914            col(&in2_str),
915            col(&in3_str),
916            col(&in4_str),
917        ])
918        .map(
919            move |s| {
920                let ca = s.struct_()?;
921                let s1 = ca.field_by_name(&in1_str)?;
922                let s2 = ca.field_by_name(&in2_str)?;
923                let s3 = ca.field_by_name(&in3_str)?;
924                let s4 = ca.field_by_name(&in4_str)?;
925
926                let ca1 = s1.f64()?;
927                let ca2 = s2.f64()?;
928                let ca3 = s3.f64()?;
929                let ca4 = s4.f64()?;
930
931                let mut indicator = I::default();
932                let mut values = Vec::with_capacity(s.len());
933
934                for i in 0..s.len() {
935                    let v1 = ca1.get(i).unwrap_or(f64::NAN);
936                    let v2 = ca2.get(i).unwrap_or(f64::NAN);
937                    let v3 = ca3.get(i).unwrap_or(f64::NAN);
938                    let v4 = ca4.get(i).unwrap_or(f64::NAN);
939                    values.push(indicator.next((v1, v2, v3, v4)));
940                }
941
942                Ok(Some(Column::from(Series::new(
943                    output_name_for_closure.clone().into(),
944                    values,
945                ))))
946            },
947            GetOutput::from_type(DataType::Float64),
948        )
949        .alias(&output_name_str)])
950    }
951
952    fn math_transform_1_in_1_out<I>(self, name: &str, output_name: &str) -> LazyFrame
953    where
954        I: Next<f64, Output = f64> + Default + Send + Sync + 'static,
955    {
956        let name = name.to_string();
957        let output_name_str = output_name.to_string();
958        let output_name_for_closure = output_name_str.clone();
959        self.0.clone().with_columns([col(&name)
960            .map(
961                move |s| {
962                    let ca = s.f64()?;
963                    let mut indicator = I::default();
964                    let mut values = Vec::with_capacity(s.len());
965
966                    for i in 0..s.len() {
967                        let val = ca.get(i).unwrap_or(f64::NAN);
968                        values.push(indicator.next(val));
969                    }
970
971                    Ok(Some(Column::from(Series::new(
972                        output_name_for_closure.clone().into(),
973                        values,
974                    ))))
975                },
976                GetOutput::from_type(DataType::Float64),
977            )
978            .alias(&output_name_str)])
979    }
980
981    fn math_operator_2_in_1_out<I>(self, in1: &str, in2: &str, output_name: &str) -> LazyFrame
982    where
983        I: Next<(f64, f64), Output = f64> + Default + Send + Sync + 'static,
984    {
985        let in1_str = in1.to_string();
986        let in2_str = in2.to_string();
987        let output_name_str = output_name.to_string();
988        let output_name_for_closure = output_name_str.clone();
989        self.0
990            .clone()
991            .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
992                .map(
993                    move |s| {
994                        let ca = s.struct_()?;
995                        let s1 = ca.field_by_name(&in1_str)?;
996                        let s2 = ca.field_by_name(&in2_str)?;
997
998                        let ca1 = s1.f64()?;
999                        let ca2 = s2.f64()?;
1000
1001                        let mut indicator = I::default();
1002                        let mut values = Vec::with_capacity(s.len());
1003
1004                        for i in 0..s.len() {
1005                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
1006                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
1007                            values.push(indicator.next((v1, v2)));
1008                        }
1009
1010                        Ok(Some(Column::from(Series::new(
1011                            output_name_for_closure.clone().into(),
1012                            values,
1013                        ))))
1014                    },
1015                    GetOutput::from_type(DataType::Float64),
1016                )
1017                .alias(&output_name_str)])
1018    }
1019
1020    fn math_operator_1_in_1_out_period<I>(
1021        self,
1022        name: &str,
1023        period: usize,
1024        output_name: &str,
1025    ) -> LazyFrame
1026    where
1027        I: Next<f64, Output = f64> + Send + Sync + 'static,
1028        I: From<usize>,
1029    {
1030        let name = name.to_string();
1031        let output_name_str = output_name.to_string();
1032        let output_name_for_closure = output_name_str.clone();
1033        self.0.clone().with_columns([col(&name)
1034            .map(
1035                move |s| {
1036                    let ca = s.f64()?;
1037                    let mut indicator = I::from(period);
1038                    let mut values = Vec::with_capacity(s.len());
1039
1040                    for i in 0..s.len() {
1041                        let val = ca.get(i).unwrap_or(f64::NAN);
1042                        values.push(indicator.next(val));
1043                    }
1044
1045                    Ok(Some(Column::from(Series::new(
1046                        output_name_for_closure.clone().into(),
1047                        values,
1048                    ))))
1049                },
1050                GetOutput::from_type(DataType::Float64),
1051            )
1052            .alias(&output_name_str)])
1053    }
1054
1055    fn math_operator_2_in_1_out_period<I>(
1056        self,
1057        in1: &str,
1058        in2: &str,
1059        period: usize,
1060        output_name: &str,
1061    ) -> LazyFrame
1062    where
1063        I: Next<(f64, f64), Output = f64> + Send + Sync + 'static,
1064        I: From<usize>,
1065    {
1066        let in1_str = in1.to_string();
1067        let in2_str = in2.to_string();
1068        let output_name_str = output_name.to_string();
1069        let output_name_for_closure = output_name_str.clone();
1070        self.0
1071            .clone()
1072            .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1073                .map(
1074                    move |s| {
1075                        let ca = s.struct_()?;
1076                        let s1 = ca.field_by_name(&in1_str)?;
1077                        let s2 = ca.field_by_name(&in2_str)?;
1078
1079                        let ca1 = s1.f64()?;
1080                        let ca2 = s2.f64()?;
1081
1082                        let mut indicator = I::from(period);
1083                        let mut values = Vec::with_capacity(s.len());
1084
1085                        for i in 0..s.len() {
1086                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
1087                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
1088                            values.push(indicator.next((v1, v2)));
1089                        }
1090
1091                        Ok(Some(Column::from(Series::new(
1092                            output_name_for_closure.clone().into(),
1093                            values,
1094                        ))))
1095                    },
1096                    GetOutput::from_type(DataType::Float64),
1097                )
1098                .alias(&output_name_str)])
1099    }
1100
1101    pub fn supertrend(self, period: usize, multiplier: f64) -> LazyFrame {
1102        self.0
1103            .clone()
1104            .with_columns([as_struct(vec![col("high"), col("low"), col("close")])
1105                .map(
1106                    move |s| {
1107                        let ca = s.struct_()?;
1108                        let s_high = ca.field_by_name("high")?;
1109                        let s_low = ca.field_by_name("low")?;
1110                        let s_close = ca.field_by_name("close")?;
1111
1112                        let high = s_high.f64()?;
1113                        let low = s_low.f64()?;
1114                        let close = s_close.f64()?;
1115
1116                        let mut st = SuperTrend::new(period, multiplier);
1117                        let mut values = Vec::with_capacity(s.len());
1118                        let mut directions = Vec::with_capacity(s.len());
1119
1120                        for i in 0..s.len() {
1121                            let h = high.get(i).unwrap_or(0.0);
1122                            let l = low.get(i).unwrap_or(0.0);
1123                            let c = close.get(i).unwrap_or(0.0);
1124                            let (val, dir) = st.next((h, l, c));
1125                            values.push(val);
1126                            directions.push(dir as f64);
1127                        }
1128
1129                        let st_series = Series::new("supertrend".into(), values);
1130                        let dir_series = Series::new("supertrend_direction".into(), directions);
1131
1132                        let out = StructChunked::from_series(
1133                            "supertrend_output".into(),
1134                            s.len(),
1135                            [st_series, dir_series].iter(),
1136                        )?;
1137                        Ok(Some(Column::from(out.into_series())))
1138                    },
1139                    GetOutput::from_type(DataType::Struct(vec![
1140                        Field::new("supertrend".into(), DataType::Float64),
1141                        Field::new("supertrend_direction".into(), DataType::Float64),
1142                    ])),
1143                )
1144                .alias("supertrend_data")])
1145    }
1146
1147    pub fn anchored_vwap(self, price: &str, volume: &str, anchor: &str) -> LazyFrame {
1148        let price = price.to_string();
1149        let volume = volume.to_string();
1150        let anchor = anchor.to_string();
1151
1152        self.0
1153            .clone()
1154            .with_columns([as_struct(vec![col(&price), col(&volume), col(&anchor)])
1155                .map(
1156                    move |s| {
1157                        let ca = s.struct_()?;
1158                        let s_price = ca.field_by_name(&price)?;
1159                        let s_volume = ca.field_by_name(&volume)?;
1160                        let s_anchor = ca.field_by_name(&anchor)?;
1161
1162                        let price = s_price.f64()?;
1163                        let volume = s_volume.f64()?;
1164                        let anchor = s_anchor.bool()?;
1165
1166                        let mut avwap = quantwave_core::AnchoredVWAP::new();
1167                        let mut values = Vec::with_capacity(s.len());
1168
1169                        for i in 0..s.len() {
1170                            let p = price.get(i).unwrap_or(0.0);
1171                            let v = volume.get(i).unwrap_or(0.0);
1172                            let a = anchor.get(i).unwrap_or(false);
1173                            values.push(avwap.next((p, v, a)));
1174                        }
1175
1176                        Ok(Some(Column::from(Series::new(
1177                            "anchored_vwap".into(),
1178                            values,
1179                        ))))
1180                    },
1181                    GetOutput::from_type(DataType::Float64),
1182                )
1183                .alias("avwap")])
1184    }
1185
1186    pub fn hma(self, name: &str, period: usize) -> LazyFrame {
1187        let name = name.to_string();
1188        self.0.clone().with_columns([col(&name)
1189            .map(
1190                move |s| {
1191                    let ca = s.f64()?;
1192                    let mut hma = quantwave_core::HMA::new(period);
1193                    let mut values = Vec::with_capacity(s.len());
1194
1195                    for i in 0..s.len() {
1196                        let val = ca.get(i).unwrap_or(0.0);
1197                        values.push(hma.next(val));
1198                    }
1199
1200                    Ok(Some(Column::from(Series::new("hma".into(), values))))
1201                },
1202                GetOutput::from_type(DataType::Float64),
1203            )
1204            .alias("hma")])
1205    }
1206
1207    pub fn keltner_channels(
1208        self,
1209        high: &str,
1210        low: &str,
1211        close: &str,
1212        ema_period: usize,
1213        atr_period: usize,
1214        multiplier: f64,
1215    ) -> LazyFrame {
1216        let high = high.to_string();
1217        let low = low.to_string();
1218        let close = close.to_string();
1219
1220        self.0
1221            .clone()
1222            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1223                .map(
1224                    move |s| {
1225                        let ca = s.struct_()?;
1226                        let s_high = ca.field_by_name(&high)?;
1227                        let s_low = ca.field_by_name(&low)?;
1228                        let s_close = ca.field_by_name(&close)?;
1229
1230                        let high = s_high.f64()?;
1231                        let low = s_low.f64()?;
1232                        let close = s_close.f64()?;
1233
1234                        let mut kc = quantwave_core::KeltnerChannels::new(
1235                            ema_period, atr_period, multiplier,
1236                        );
1237                        let mut uppers = Vec::with_capacity(s.len());
1238                        let mut middles = Vec::with_capacity(s.len());
1239                        let mut lowers = Vec::with_capacity(s.len());
1240
1241                        for i in 0..s.len() {
1242                            let h = high.get(i).unwrap_or(0.0);
1243                            let l = low.get(i).unwrap_or(0.0);
1244                            let c = close.get(i).unwrap_or(0.0);
1245                            let (upper, middle, lower) = kc.next((h, l, c));
1246                            uppers.push(upper);
1247                            middles.push(middle);
1248                            lowers.push(lower);
1249                        }
1250
1251                        let upper_series = Series::new("upper".into(), uppers);
1252                        let middle_series = Series::new("middle".into(), middles);
1253                        let lower_series = Series::new("lower".into(), lowers);
1254
1255                        let out = StructChunked::from_series(
1256                            "keltner_output".into(),
1257                            s.len(),
1258                            [upper_series, middle_series, lower_series].iter(),
1259                        )?;
1260                        Ok(Some(Column::from(out.into_series())))
1261                    },
1262                    GetOutput::from_type(DataType::Struct(vec![
1263                        Field::new("upper".into(), DataType::Float64),
1264                        Field::new("middle".into(), DataType::Float64),
1265                        Field::new("lower".into(), DataType::Float64),
1266                    ])),
1267                )
1268                .alias("keltner_data")])
1269    }
1270
1271    pub fn alma(self, name: &str, period: usize, offset: f64, sigma: f64) -> LazyFrame {
1272        let name = name.to_string();
1273        self.0.clone().with_columns([col(&name)
1274            .map(
1275                move |s| {
1276                    let ca = s.f64()?;
1277                    let mut alma = quantwave_core::ALMA::new(period, offset, sigma);
1278                    let mut values = Vec::with_capacity(s.len());
1279
1280                    for i in 0..s.len() {
1281                        let val = ca.get(i).unwrap_or(0.0);
1282                        values.push(alma.next(val));
1283                    }
1284
1285                    Ok(Some(Column::from(Series::new("alma".into(), values))))
1286                },
1287                GetOutput::from_type(DataType::Float64),
1288            )
1289            .alias("alma")])
1290    }
1291
1292    pub fn donchian_channels(self, high: &str, low: &str, period: usize) -> LazyFrame {
1293        let high = high.to_string();
1294        let low = low.to_string();
1295
1296        self.0
1297            .clone()
1298            .with_columns([as_struct(vec![col(&high), col(&low)])
1299                .map(
1300                    move |s| {
1301                        let ca = s.struct_()?;
1302                        let s_high = ca.field_by_name(&high)?;
1303                        let s_low = ca.field_by_name(&low)?;
1304
1305                        let high = s_high.f64()?;
1306                        let low = s_low.f64()?;
1307
1308                        let mut dc = quantwave_core::DonchianChannels::new(period);
1309                        let mut uppers = Vec::with_capacity(s.len());
1310                        let mut middles = Vec::with_capacity(s.len());
1311                        let mut lowers = Vec::with_capacity(s.len());
1312
1313                        for i in 0..s.len() {
1314                            let h = high.get(i).unwrap_or(0.0);
1315                            let l = low.get(i).unwrap_or(0.0);
1316                            let (upper, middle, lower) = dc.next((h, l));
1317                            uppers.push(upper);
1318                            middles.push(middle);
1319                            lowers.push(lower);
1320                        }
1321
1322                        let upper_series = Series::new("upper".into(), uppers);
1323                        let middle_series = Series::new("middle".into(), middles);
1324                        let lower_series = Series::new("lower".into(), lowers);
1325
1326                        let out = StructChunked::from_series(
1327                            "donchian_output".into(),
1328                            s.len(),
1329                            [upper_series, middle_series, lower_series].iter(),
1330                        )?;
1331                        Ok(Some(Column::from(out.into_series())))
1332                    },
1333                    GetOutput::from_type(DataType::Struct(vec![
1334                        Field::new("upper".into(), DataType::Float64),
1335                        Field::new("middle".into(), DataType::Float64),
1336                        Field::new("lower".into(), DataType::Float64),
1337                    ])),
1338                )
1339                .alias("donchian_data")])
1340    }
1341
1342    pub fn ttm_squeeze(
1343        self,
1344        high: &str,
1345        low: &str,
1346        close: &str,
1347        period: usize,
1348        multiplier_bb: f64,
1349        multiplier_kc: f64,
1350    ) -> LazyFrame {
1351        let high = high.to_string();
1352        let low = low.to_string();
1353        let close = close.to_string();
1354
1355        self.0
1356            .clone()
1357            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1358                .map(
1359                    move |s| {
1360                        let ca = s.struct_()?;
1361                        let s_high = ca.field_by_name(&high)?;
1362                        let s_low = ca.field_by_name(&low)?;
1363                        let s_close = ca.field_by_name(&close)?;
1364
1365                        let high = s_high.f64()?;
1366                        let low = s_low.f64()?;
1367                        let close = s_close.f64()?;
1368
1369                        let mut ttm =
1370                            quantwave_core::TTMSqueeze::new(period, multiplier_bb, multiplier_kc);
1371                        let mut histograms = Vec::with_capacity(s.len());
1372                        let mut squeezed = Vec::with_capacity(s.len());
1373
1374                        for i in 0..s.len() {
1375                            let h = high.get(i).unwrap_or(0.0);
1376                            let l = low.get(i).unwrap_or(0.0);
1377                            let c = close.get(i).unwrap_or(0.0);
1378                            let (hist, is_sq) = ttm.next((h, l, c));
1379                            histograms.push(hist);
1380                            squeezed.push(is_sq);
1381                        }
1382
1383                        let hist_series = Series::new("histogram".into(), histograms);
1384                        let squeezed_series = Series::new("is_squeezed".into(), squeezed);
1385
1386                        let out = StructChunked::from_series(
1387                            "ttm_squeeze_output".into(),
1388                            s.len(),
1389                            [hist_series, squeezed_series].iter(),
1390                        )?;
1391                        Ok(Some(Column::from(out.into_series())))
1392                    },
1393                    GetOutput::from_type(DataType::Struct(vec![
1394                        Field::new("histogram".into(), DataType::Float64),
1395                        Field::new("is_squeezed".into(), DataType::Boolean),
1396                    ])),
1397                )
1398                .alias("ttm_squeeze_data")])
1399    }
1400
1401    pub fn vortex_indicator(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
1402        let high = high.to_string();
1403        let low = low.to_string();
1404        let close = close.to_string();
1405
1406        self.0
1407            .clone()
1408            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1409                .map(
1410                    move |s| {
1411                        let ca = s.struct_()?;
1412                        let s_high = ca.field_by_name(&high)?;
1413                        let s_low = ca.field_by_name(&low)?;
1414                        let s_close = ca.field_by_name(&close)?;
1415
1416                        let high = s_high.f64()?;
1417                        let low = s_low.f64()?;
1418                        let close = s_close.f64()?;
1419
1420                        let mut vi = quantwave_core::VortexIndicator::new(period);
1421                        let mut plus_vals = Vec::with_capacity(s.len());
1422                        let mut minus_vals = Vec::with_capacity(s.len());
1423
1424                        for i in 0..s.len() {
1425                            let h = high.get(i).unwrap_or(0.0);
1426                            let l = low.get(i).unwrap_or(0.0);
1427                            let c = close.get(i).unwrap_or(0.0);
1428                            let (plus, minus) = vi.next((h, l, c));
1429                            plus_vals.push(plus);
1430                            minus_vals.push(minus);
1431                        }
1432
1433                        let plus_series = Series::new("vi_plus".into(), plus_vals);
1434                        let minus_series = Series::new("vi_minus".into(), minus_vals);
1435
1436                        let out = StructChunked::from_series(
1437                            "vortex_output".into(),
1438                            s.len(),
1439                            [plus_series, minus_series].iter(),
1440                        )?;
1441                        Ok(Some(Column::from(out.into_series())))
1442                    },
1443                    GetOutput::from_type(DataType::Struct(vec![
1444                        Field::new("vi_plus".into(), DataType::Float64),
1445                        Field::new("vi_minus".into(), DataType::Float64),
1446                    ])),
1447                )
1448                .alias("vortex_data")])
1449    }
1450
1451    pub fn heikin_ashi(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
1452        let open = open.to_string();
1453        let high = high.to_string();
1454        let low = low.to_string();
1455        let close = close.to_string();
1456
1457        self.0.clone().with_columns([as_struct(vec![
1458            col(&open),
1459            col(&high),
1460            col(&low),
1461            col(&close),
1462        ])
1463        .map(
1464            move |s| {
1465                let ca = s.struct_()?;
1466                let s_open = ca.field_by_name(&open)?;
1467                let s_high = ca.field_by_name(&high)?;
1468                let s_low = ca.field_by_name(&low)?;
1469                let s_close = ca.field_by_name(&close)?;
1470
1471                let open = s_open.f64()?;
1472                let high = s_high.f64()?;
1473                let low = s_low.f64()?;
1474                let close = s_close.f64()?;
1475
1476                let mut ha = quantwave_core::HeikinAshi::new();
1477                let mut ha_opens = Vec::with_capacity(s.len());
1478                let mut ha_highs = Vec::with_capacity(s.len());
1479                let mut ha_lows = Vec::with_capacity(s.len());
1480                let mut ha_closes = Vec::with_capacity(s.len());
1481
1482                for i in 0..s.len() {
1483                    let o = open.get(i).unwrap_or(0.0);
1484                    let h = high.get(i).unwrap_or(0.0);
1485                    let l = low.get(i).unwrap_or(0.0);
1486                    let c = close.get(i).unwrap_or(0.0);
1487                    let (ha_o, ha_h, ha_l, ha_c) = ha.next((o, h, l, c));
1488                    ha_opens.push(ha_o);
1489                    ha_highs.push(ha_h);
1490                    ha_lows.push(ha_l);
1491                    ha_closes.push(ha_c);
1492                }
1493
1494                let o_series = Series::new("ha_open".into(), ha_opens);
1495                let h_series = Series::new("ha_high".into(), ha_highs);
1496                let l_series = Series::new("ha_low".into(), ha_lows);
1497                let c_series = Series::new("ha_close".into(), ha_closes);
1498
1499                let out = StructChunked::from_series(
1500                    "heikin_ashi_output".into(),
1501                    s.len(),
1502                    [o_series, h_series, l_series, c_series].iter(),
1503                )?;
1504                Ok(Some(Column::from(out.into_series())))
1505            },
1506            GetOutput::from_type(DataType::Struct(vec![
1507                Field::new("ha_open".into(), DataType::Float64),
1508                Field::new("ha_high".into(), DataType::Float64),
1509                Field::new("ha_low".into(), DataType::Float64),
1510                Field::new("ha_close".into(), DataType::Float64),
1511            ])),
1512        )
1513        .alias("heikin_ashi_data")])
1514    }
1515
1516    pub fn wavetrend(
1517        self,
1518        high: &str,
1519        low: &str,
1520        close: &str,
1521        n1: usize,
1522        n2: usize,
1523        n3: usize,
1524    ) -> LazyFrame {
1525        let high = high.to_string();
1526        let low = low.to_string();
1527        let close = close.to_string();
1528
1529        self.0
1530            .clone()
1531            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1532                .map(
1533                    move |s| {
1534                        let ca = s.struct_()?;
1535                        let s_high = ca.field_by_name(&high)?;
1536                        let s_low = ca.field_by_name(&low)?;
1537                        let s_close = ca.field_by_name(&close)?;
1538
1539                        let high = s_high.f64()?;
1540                        let low = s_low.f64()?;
1541                        let close = s_close.f64()?;
1542
1543                        let mut wt = quantwave_core::WaveTrend::new(n1, n2, n3);
1544                        let mut wt1_vals = Vec::with_capacity(s.len());
1545                        let mut wt2_vals = Vec::with_capacity(s.len());
1546
1547                        for i in 0..s.len() {
1548                            let h = high.get(i).unwrap_or(0.0);
1549                            let l = low.get(i).unwrap_or(0.0);
1550                            let c = close.get(i).unwrap_or(0.0);
1551                            let (wt1, wt2) = wt.next((h, l, c));
1552                            wt1_vals.push(wt1);
1553                            wt2_vals.push(wt2);
1554                        }
1555
1556                        let wt1_series = Series::new("wt1".into(), wt1_vals);
1557                        let wt2_series = Series::new("wt2".into(), wt2_vals);
1558
1559                        let out = StructChunked::from_series(
1560                            "wavetrend_output".into(),
1561                            s.len(),
1562                            [wt1_series, wt2_series].iter(),
1563                        )?;
1564                        Ok(Some(Column::from(out.into_series())))
1565                    },
1566                    GetOutput::from_type(DataType::Struct(vec![
1567                        Field::new("wt1".into(), DataType::Float64),
1568                        Field::new("wt2".into(), DataType::Float64),
1569                    ])),
1570                )
1571                .alias("wavetrend_data")])
1572    }
1573
1574    pub fn tema(self, name: &str, period: usize) -> LazyFrame {
1575        let name = name.to_string();
1576        self.0.clone().with_columns([col(&name)
1577            .map(
1578                move |s| {
1579                    let ca = s.f64()?;
1580                    let mut tema = quantwave_core::TEMA::new(period);
1581                    let mut values = Vec::with_capacity(s.len());
1582
1583                    for i in 0..s.len() {
1584                        let val = ca.get(i).unwrap_or(0.0);
1585                        values.push(tema.next(val));
1586                    }
1587
1588                    Ok(Some(Column::from(Series::new("tema".into(), values))))
1589                },
1590                GetOutput::from_type(DataType::Float64),
1591            )
1592            .alias("tema")])
1593    }
1594
1595    pub fn zlema(self, name: &str, period: usize) -> LazyFrame {
1596        let name = name.to_string();
1597        self.0.clone().with_columns([col(&name)
1598            .map(
1599                move |s| {
1600                    let ca = s.f64()?;
1601                    let mut zlema = quantwave_core::ZLEMA::new(period);
1602                    let mut values = Vec::with_capacity(s.len());
1603
1604                    for i in 0..s.len() {
1605                        let val = ca.get(i).unwrap_or(0.0);
1606                        values.push(zlema.next(val));
1607                    }
1608
1609                    Ok(Some(Column::from(Series::new("zlema".into(), values))))
1610                },
1611                GetOutput::from_type(DataType::Float64),
1612            )
1613            .alias("zlema")])
1614    }
1615
1616    pub fn atr_trailing_stop(
1617        self,
1618        high: &str,
1619        low: &str,
1620        close: &str,
1621        period: usize,
1622        multiplier: f64,
1623    ) -> LazyFrame {
1624        let high = high.to_string();
1625        let low = low.to_string();
1626        let close = close.to_string();
1627
1628        self.0
1629            .clone()
1630            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1631                .map(
1632                    move |s| {
1633                        let ca = s.struct_()?;
1634                        let s_high = ca.field_by_name(&high)?;
1635                        let s_low = ca.field_by_name(&low)?;
1636                        let s_close = ca.field_by_name(&close)?;
1637
1638                        let high = s_high.f64()?;
1639                        let low = s_low.f64()?;
1640                        let close = s_close.f64()?;
1641
1642                        let mut atr_ts = quantwave_core::ATRTrailingStop::new(period, multiplier);
1643                        let mut stops = Vec::with_capacity(s.len());
1644                        let mut directions = Vec::with_capacity(s.len());
1645
1646                        for i in 0..s.len() {
1647                            let h = high.get(i).unwrap_or(0.0);
1648                            let l = low.get(i).unwrap_or(0.0);
1649                            let c = close.get(i).unwrap_or(0.0);
1650                            let (stop, dir) = atr_ts.next((h, l, c));
1651                            stops.push(stop);
1652                            directions.push(dir as f64);
1653                        }
1654
1655                        let stop_series = Series::new("stop".into(), stops);
1656                        let dir_series = Series::new("direction".into(), directions);
1657
1658                        let out = StructChunked::from_series(
1659                            "atr_ts_output".into(),
1660                            s.len(),
1661                            [stop_series, dir_series].iter(),
1662                        )?;
1663                        Ok(Some(Column::from(out.into_series())))
1664                    },
1665                    GetOutput::from_type(DataType::Struct(vec![
1666                        Field::new("stop".into(), DataType::Float64),
1667                        Field::new("direction".into(), DataType::Float64),
1668                    ])),
1669                )
1670                .alias("atr_ts_data")])
1671    }
1672
1673    pub fn pivot_points(self, high: &str, low: &str, close: &str) -> LazyFrame {
1674        let high = high.to_string();
1675        let low = low.to_string();
1676        let close = close.to_string();
1677
1678        self.0
1679            .clone()
1680            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1681                .map(
1682                    move |s| {
1683                        let ca = s.struct_()?;
1684                        let s_high = ca.field_by_name(&high)?;
1685                        let s_low = ca.field_by_name(&low)?;
1686                        let s_close = ca.field_by_name(&close)?;
1687
1688                        let high = s_high.f64()?;
1689                        let low = s_low.f64()?;
1690                        let close = s_close.f64()?;
1691
1692                        let mut pivot = quantwave_core::PivotPoints::new();
1693                        let mut p_vals = Vec::with_capacity(s.len());
1694                        let mut r1_vals = Vec::with_capacity(s.len());
1695                        let mut s1_vals = Vec::with_capacity(s.len());
1696                        let mut r2_vals = Vec::with_capacity(s.len());
1697                        let mut s2_vals = Vec::with_capacity(s.len());
1698
1699                        for i in 0..s.len() {
1700                            let h = high.get(i).unwrap_or(0.0);
1701                            let l = low.get(i).unwrap_or(0.0);
1702                            let c = close.get(i).unwrap_or(0.0);
1703                            let (p, r1, s1, r2, s2) = pivot.next((h, l, c));
1704                            p_vals.push(p);
1705                            r1_vals.push(r1);
1706                            s1_vals.push(s1);
1707                            r2_vals.push(r2);
1708                            s2_vals.push(s2);
1709                        }
1710
1711                        let p_series = Series::new("p".into(), p_vals);
1712                        let r1_series = Series::new("r1".into(), r1_vals);
1713                        let s1_series = Series::new("s1".into(), s1_vals);
1714                        let r2_series = Series::new("r2".into(), r2_vals);
1715                        let s2_series = Series::new("s2".into(), s2_vals);
1716
1717                        let out = StructChunked::from_series(
1718                            "pivot_output".into(),
1719                            s.len(),
1720                            [p_series, r1_series, s1_series, r2_series, s2_series].iter(),
1721                        )?;
1722                        Ok(Some(Column::from(out.into_series())))
1723                    },
1724                    GetOutput::from_type(DataType::Struct(vec![
1725                        Field::new("p".into(), DataType::Float64),
1726                        Field::new("r1".into(), DataType::Float64),
1727                        Field::new("s1".into(), DataType::Float64),
1728                        Field::new("r2".into(), DataType::Float64),
1729                        Field::new("s2".into(), DataType::Float64),
1730                    ])),
1731                )
1732                .alias("pivot_points_data")])
1733    }
1734
1735    pub fn bill_williams_fractals(self, high: &str, low: &str) -> LazyFrame {
1736        let high = high.to_string();
1737        let low = low.to_string();
1738
1739        self.0
1740            .clone()
1741            .with_columns([as_struct(vec![col(&high), col(&low)])
1742                .map(
1743                    move |s| {
1744                        let ca = s.struct_()?;
1745                        let s_high = ca.field_by_name(&high)?;
1746                        let s_low = ca.field_by_name(&low)?;
1747
1748                        let high = s_high.f64()?;
1749                        let low = s_low.f64()?;
1750
1751                        let mut fractals = quantwave_core::BillWilliamsFractals::new();
1752                        let mut bearish_vals = Vec::with_capacity(s.len());
1753                        let mut bullish_vals = Vec::with_capacity(s.len());
1754
1755                        for i in 0..s.len() {
1756                            let h = high.get(i).unwrap_or(0.0);
1757                            let l = low.get(i).unwrap_or(0.0);
1758                            let (bear, bull) = fractals.next((h, l));
1759                            bearish_vals.push(bear);
1760                            bullish_vals.push(bull);
1761                        }
1762
1763                        let bearish_series = Series::new("bearish".into(), bearish_vals);
1764                        let bullish_series = Series::new("bullish".into(), bullish_vals);
1765
1766                        let out = StructChunked::from_series(
1767                            "fractals_output".into(),
1768                            s.len(),
1769                            [bearish_series, bullish_series].iter(),
1770                        )?;
1771                        Ok(Some(Column::from(out.into_series())))
1772                    },
1773                    GetOutput::from_type(DataType::Struct(vec![
1774                        Field::new("bearish".into(), DataType::Boolean),
1775                        Field::new("bullish".into(), DataType::Boolean),
1776                    ])),
1777                )
1778                .alias("fractals_data")])
1779    }
1780
1781    pub fn ichimoku_cloud(
1782        self,
1783        high: &str,
1784        low: &str,
1785        p1: usize,
1786        p2: usize,
1787        p3: usize,
1788    ) -> LazyFrame {
1789        let high = high.to_string();
1790        let low = low.to_string();
1791
1792        self.0
1793            .clone()
1794            .with_columns([as_struct(vec![col(&high), col(&low)])
1795                .map(
1796                    move |s| {
1797                        let ca = s.struct_()?;
1798                        let s_high = ca.field_by_name(&high)?;
1799                        let s_low = ca.field_by_name(&low)?;
1800
1801                        let high = s_high.f64()?;
1802                        let low = s_low.f64()?;
1803
1804                        let mut ic = quantwave_core::IchimokuCloud::new(p1, p2, p3);
1805                        let mut t_vals = Vec::with_capacity(s.len());
1806                        let mut k_vals = Vec::with_capacity(s.len());
1807                        let mut sa_vals = Vec::with_capacity(s.len());
1808                        let mut sb_vals = Vec::with_capacity(s.len());
1809
1810                        for i in 0..s.len() {
1811                            let h = high.get(i).unwrap_or(0.0);
1812                            let l = low.get(i).unwrap_or(0.0);
1813                            let (t, k, sa, sb) = ic.next((h, l));
1814                            t_vals.push(t);
1815                            k_vals.push(k);
1816                            sa_vals.push(sa);
1817                            sb_vals.push(sb);
1818                        }
1819
1820                        let t_series = Series::new("tenkan".into(), t_vals);
1821                        let k_series = Series::new("kijun".into(), k_vals);
1822                        let sa_series = Series::new("senkou_a".into(), sa_vals);
1823                        let sb_series = Series::new("senkou_b".into(), sb_vals);
1824
1825                        let out = StructChunked::from_series(
1826                            "ichimoku_output".into(),
1827                            s.len(),
1828                            [t_series, k_series, sa_series, sb_series].iter(),
1829                        )?;
1830                        Ok(Some(Column::from(out.into_series())))
1831                    },
1832                    GetOutput::from_type(DataType::Struct(vec![
1833                        Field::new("tenkan".into(), DataType::Float64),
1834                        Field::new("kijun".into(), DataType::Float64),
1835                        Field::new("senkou_a".into(), DataType::Float64),
1836                        Field::new("senkou_b".into(), DataType::Float64),
1837                    ])),
1838                )
1839                .alias("ichimoku_data")])
1840    }
1841}
1842
1843#[cfg(test)]
1844mod tests {
1845    use super::*;
1846
1847    #[test]
1848    fn test_polars_heikin_ashi() -> PolarsResult<()> {
1849        let df = df![
1850            "open" => [10.0, 11.0],
1851            "high" => [12.0, 13.0],
1852            "low" => [8.0, 10.0],
1853            "close" => [11.0, 12.0]
1854        ]?;
1855
1856        let out = df
1857            .lazy()
1858            .ta()
1859            .heikin_ashi("open", "high", "low", "close")
1860            .collect()?;
1861
1862        let ha = out.column("heikin_ashi_data")?.struct_()?;
1863        assert_eq!(
1864            ha.field_by_name("ha_open".into())?.f64()?.get(0),
1865            Some(10.5)
1866        );
1867        assert_eq!(
1868            ha.field_by_name("ha_close".into())?.f64()?.get(0),
1869            Some(10.25)
1870        );
1871
1872        Ok(())
1873    }
1874
1875    #[test]
1876    fn test_polars_tema_zlema() -> PolarsResult<()> {
1877        let df = df![
1878            "price" => [1.0, 2.0, 3.0, 4.0, 5.0]
1879        ]?;
1880
1881        let out = df.clone().lazy().ta().tema("price", 3).collect()?;
1882
1883        let tema = out.column("tema")?.f64()?;
1884        assert!(tema.get(4).is_some());
1885
1886        let out2 = df.lazy().ta().zlema("price", 3).collect()?;
1887
1888        let zlema = out2.column("zlema")?.f64()?;
1889        assert!(zlema.get(4).is_some());
1890
1891        Ok(())
1892    }
1893
1894    #[test]
1895    fn test_polars_atr_ts() -> PolarsResult<()> {
1896        let df = df![
1897            "high" => [10.0, 12.0, 11.0],
1898            "low" => [8.0, 10.0, 9.0],
1899            "close" => [9.0, 11.0, 10.0]
1900        ]?;
1901
1902        let out = df
1903            .lazy()
1904            .ta()
1905            .atr_trailing_stop("high", "low", "close", 14, 2.5)
1906            .collect()?;
1907
1908        let atr_ts = out.column("atr_ts_data")?.struct_()?;
1909        assert!(atr_ts.field_by_name("stop".into())?.f64()?.get(0).is_some());
1910        assert!(
1911            atr_ts
1912                .field_by_name("direction".into())?
1913                .f64()?
1914                .get(0)
1915                .is_some()
1916        );
1917
1918        Ok(())
1919    }
1920
1921    #[test]
1922    fn test_polars_pivot_points() -> PolarsResult<()> {
1923        let df = df![
1924            "high" => [10.0, 12.0, 11.0],
1925            "low" => [8.0, 10.0, 9.0],
1926            "close" => [9.0, 11.0, 10.0]
1927        ]?;
1928
1929        let out = df
1930            .lazy()
1931            .ta()
1932            .pivot_points("high", "low", "close")
1933            .collect()?;
1934
1935        let pivot = out.column("pivot_points_data")?.struct_()?;
1936        assert!(pivot.field_by_name("p".into())?.f64()?.get(0).is_some());
1937        assert!(pivot.field_by_name("r1".into())?.f64()?.get(0).is_some());
1938
1939        Ok(())
1940    }
1941
1942    #[test]
1943    fn test_polars_fractals() -> PolarsResult<()> {
1944        let df = df![
1945            "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
1946            "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
1947        ]?;
1948
1949        let out = df
1950            .lazy()
1951            .ta()
1952            .bill_williams_fractals("high", "low")
1953            .collect()?;
1954
1955        let fractals = out.column("fractals_data")?.struct_()?;
1956        assert!(
1957            fractals
1958                .field_by_name("bearish".into())?
1959                .bool()?
1960                .get(4)
1961                .unwrap()
1962        );
1963        assert!(
1964            fractals
1965                .field_by_name("bullish".into())?
1966                .bool()?
1967                .get(4)
1968                .unwrap()
1969        );
1970
1971        Ok(())
1972    }
1973
1974    #[test]
1975    fn test_polars_ichimoku() -> PolarsResult<()> {
1976        let df = df![
1977            "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
1978            "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
1979        ]?;
1980
1981        let out = df
1982            .lazy()
1983            .ta()
1984            .ichimoku_cloud("high", "low", 9, 26, 52)
1985            .collect()?;
1986
1987        let ichimoku = out.column("ichimoku_data")?.struct_()?;
1988        assert!(
1989            ichimoku
1990                .field_by_name("tenkan".into())?
1991                .f64()?
1992                .get(4)
1993                .is_some()
1994        );
1995        assert!(
1996            ichimoku
1997                .field_by_name("kijun".into())?
1998                .f64()?
1999                .get(4)
2000                .is_some()
2001        );
2002
2003        Ok(())
2004    }
2005
2006    #[test]
2007    fn test_polars_wavetrend() -> PolarsResult<()> {
2008        let df = df![
2009            "high" => [10.0, 12.0, 11.0],
2010            "low" => [8.0, 10.0, 9.0],
2011            "close" => [9.0, 11.0, 10.0]
2012        ]?;
2013
2014        let out = df
2015            .lazy()
2016            .ta()
2017            .wavetrend("high", "low", "close", 10, 21, 4)
2018            .collect()?;
2019
2020        let wt = out.column("wavetrend_data")?.struct_()?;
2021        assert!(wt.field_by_name("wt1".into())?.f64()?.get(0).is_some());
2022        assert!(wt.field_by_name("wt2".into())?.f64()?.get(0).is_some());
2023
2024        Ok(())
2025    }
2026
2027    #[test]
2028    fn test_polars_vortex() -> PolarsResult<()> {
2029        let df = df![
2030            "high" => [10.0, 12.0, 11.0],
2031            "low" => [8.0, 10.0, 9.0],
2032            "close" => [9.0, 11.0, 10.0]
2033        ]?;
2034
2035        let out = df
2036            .lazy()
2037            .ta()
2038            .vortex_indicator("high", "low", "close", 14)
2039            .collect()?;
2040
2041        let vortex = out.column("vortex_data")?.struct_()?;
2042        assert!(
2043            vortex
2044                .field_by_name("vi_plus".into())?
2045                .f64()?
2046                .get(0)
2047                .is_some()
2048        );
2049        assert!(
2050            vortex
2051                .field_by_name("vi_minus".into())?
2052                .f64()?
2053                .get(0)
2054                .is_some()
2055        );
2056
2057        Ok(())
2058    }
2059
2060    #[test]
2061    fn test_polars_ttm_squeeze() -> PolarsResult<()> {
2062        let df = df![
2063            "high" => [11.0, 12.0, 13.0, 14.0],
2064            "low" => [9.0, 10.0, 11.0, 12.0],
2065            "close" => [10.0, 11.0, 12.0, 13.0]
2066        ]?;
2067
2068        let out = df
2069            .lazy()
2070            .ta()
2071            .ttm_squeeze("high", "low", "close", 20, 2.0, 1.5)
2072            .collect()?;
2073
2074        let ttm = out.column("ttm_squeeze_data")?.struct_()?;
2075        assert!(
2076            ttm.field_by_name("histogram".into())?
2077                .f64()?
2078                .get(0)
2079                .is_some()
2080        );
2081        assert!(
2082            ttm.field_by_name("is_squeezed".into())?
2083                .bool()?
2084                .get(0)
2085                .is_some()
2086        );
2087
2088        Ok(())
2089    }
2090
2091    #[test]
2092    fn test_polars_donchian() -> PolarsResult<()> {
2093        let df = df![
2094            "high" => [10.0, 12.0, 11.0, 13.0, 15.0],
2095            "low" => [8.0, 7.0, 9.0, 10.0, 12.0]
2096        ]?;
2097
2098        let out = df
2099            .lazy()
2100            .ta()
2101            .donchian_channels("high", "low", 3)
2102            .collect()?;
2103
2104        let donchian = out.column("donchian_data")?.struct_()?;
2105        // bar 4: H=13, L=10. Window (12,7), (11,9), (13,10). Upper=13, Lower=7, Middle=10
2106        assert_eq!(
2107            donchian.field_by_name("upper".into())?.f64()?.get(3),
2108            Some(13.0)
2109        );
2110        assert_eq!(
2111            donchian.field_by_name("middle".into())?.f64()?.get(3),
2112            Some(10.0)
2113        );
2114        assert_eq!(
2115            donchian.field_by_name("lower".into())?.f64()?.get(3),
2116            Some(7.0)
2117        );
2118
2119        Ok(())
2120    }
2121
2122    #[test]
2123    fn test_polars_alma() -> PolarsResult<()> {
2124        let df = df![
2125            "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
2126        ]?;
2127
2128        let out = df.lazy().ta().alma("price", 9, 0.85, 6.0).collect()?;
2129
2130        let alma = out.column("alma")?.f64()?;
2131        assert!(alma.get(9).is_some());
2132
2133        Ok(())
2134    }
2135
2136    #[test]
2137    fn test_polars_keltner() -> PolarsResult<()> {
2138        let df = df![
2139            "high" => [12.0],
2140            "low" => [8.0],
2141            "close" => [10.0]
2142        ]?;
2143
2144        let out = df
2145            .lazy()
2146            .ta()
2147            .keltner_channels("high", "low", "close", 3, 3, 2.0)
2148            .collect()?;
2149
2150        let keltner = out.column("keltner_data")?.struct_()?;
2151        assert_eq!(
2152            keltner.field_by_name("middle".into())?.f64()?.get(0),
2153            Some(10.0)
2154        );
2155        assert_eq!(
2156            keltner.field_by_name("upper".into())?.f64()?.get(0),
2157            Some(18.0)
2158        );
2159        assert_eq!(
2160            keltner.field_by_name("lower".into())?.f64()?.get(0),
2161            Some(2.0)
2162        );
2163
2164        Ok(())
2165    }
2166
2167    #[test]
2168    fn test_polars_hma() -> PolarsResult<()> {
2169        let df = df![
2170            "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
2171        ]?;
2172
2173        let out = df.lazy().ta().hma("price", 4).collect()?;
2174
2175        let hma = out.column("hma")?.f64()?;
2176        assert!(hma.get(9).is_some());
2177
2178        Ok(())
2179    }
2180
2181    #[test]
2182    fn test_polars_anchored_vwap() -> PolarsResult<()> {
2183        let df = df![
2184            "price" => [10.0, 12.0, 15.0, 16.0],
2185            "volume" => [100.0, 200.0, 100.0, 100.0],
2186            "anchor" => [false, false, true, false]
2187        ]?;
2188
2189        let out = df
2190            .lazy()
2191            .ta()
2192            .anchored_vwap("price", "volume", "anchor")
2193            .collect()?;
2194
2195        let avwap = out.column("avwap")?.f64()?;
2196        assert_eq!(avwap.get(0), Some(10.0));
2197        assert_eq!(avwap.get(1), Some(11.333333333333334));
2198        assert_eq!(avwap.get(2), Some(15.0));
2199        assert_eq!(avwap.get(3), Some(15.5));
2200
2201        Ok(())
2202    }
2203
2204    #[test]
2205    fn test_polars_math_transforms() -> PolarsResult<()> {
2206        let df = df![
2207            "val" => [0.0, 1.5707963267948966] // 0, PI/2
2208        ]?;
2209
2210        let out = df.lazy().ta().sin("val").collect()?;
2211
2212        let sin = out.column("sin")?.f64()?;
2213        assert!((sin.get(0).unwrap() - 0.0).abs() < 1e-10);
2214        assert!((sin.get(1).unwrap() - 1.0).abs() < 1e-10);
2215
2216        Ok(())
2217    }
2218
2219    #[test]
2220    fn test_polars_math_operators() -> PolarsResult<()> {
2221        let df = df![
2222            "v1" => [10.0, 20.0],
2223            "v2" => [5.0, 30.0]
2224        ]?;
2225
2226        let out = df.lazy().ta().add("v1", "v2").ta().max("v1", 2).collect()?;
2227
2228        let add = out.column("add")?.f64()?;
2229        assert_eq!(add.get(0), Some(15.0));
2230        assert_eq!(add.get(1), Some(50.0));
2231
2232        let max = out.column("max")?.f64()?;
2233        assert_eq!(max.get(1), Some(20.0));
2234
2235        Ok(())
2236    }
2237}
2238
2239impl QuantWaveExt for LazyFrame {
2240    fn ta(&self) -> QuantWaveNamespace<'_> {
2241        QuantWaveNamespace(self)
2242    }
2243}