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    #[allow(clippy::too_many_arguments)]
276    pub fn stoch(
277        self,
278        high: &str,
279        low: &str,
280        close: &str,
281        fastk: usize,
282        slowk: usize,
283        slowk_matype: talib::MaType,
284        slowd: usize,
285        slowd_matype: talib::MaType,
286    ) -> LazyFrame {
287        let high_str = high.to_string();
288        let low_str = low.to_string();
289        let close_str = close.to_string();
290        self.0.clone().with_columns([as_struct(vec![
291            col(&high_str),
292            col(&low_str),
293            col(&close_str),
294        ])
295        .map(
296            move |s| {
297                let ca = s.struct_()?;
298                let s_h = ca.field_by_name(&high_str)?;
299                let s_l = ca.field_by_name(&low_str)?;
300                let s_c = ca.field_by_name(&close_str)?;
301                let high = s_h.f64()?;
302                let low = s_l.f64()?;
303                let close = s_c.f64()?;
304
305                let mut indicator = STOCH::new(fastk, slowk, slowk_matype, slowd, slowd_matype);
306                let mut k_vals = Vec::with_capacity(s.len());
307                let mut d_vals = Vec::with_capacity(s.len());
308
309                for i in 0..s.len() {
310                    let h = high.get(i).unwrap_or(f64::NAN);
311                    let l = low.get(i).unwrap_or(f64::NAN);
312                    let c = close.get(i).unwrap_or(f64::NAN);
313                    let (k, d) = indicator.next((h, l, c));
314                    k_vals.push(k);
315                    d_vals.push(d);
316                }
317
318                let s_k = Series::new("slowk".into(), k_vals);
319                let s_d = Series::new("slowd".into(), d_vals);
320                let struct_series =
321                    StructChunked::from_series("stoch_result".into(), s.len(), [s_k, s_d].iter())?;
322                Ok(Some(Column::from(struct_series.into_series())))
323            },
324            GetOutput::from_type(DataType::Struct(vec![
325                Field::new("slowk".into(), DataType::Float64),
326                Field::new("slowd".into(), DataType::Float64),
327            ])),
328        )
329        .alias("stoch")])
330    }
331
332    pub fn avgprice(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
333        self.ta_4_in_1_out_default::<AVGPRICE>(open, high, low, close, "avgprice")
334    }
335    pub fn medprice(self, high: &str, low: &str) -> LazyFrame {
336        self.math_operator_2_in_1_out::<MEDPRICE>(high, low, "medprice")
337    }
338    pub fn typprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
339        self.ta_3_in_1_out_default::<TYPPRICE>(high, low, close, "typprice")
340    }
341    pub fn wclprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
342        self.ta_3_in_1_out_default::<WCLPRICE>(high, low, close, "wclprice")
343    }
344
345    pub fn ht_dcperiod(self, name: &str) -> LazyFrame {
346        self.math_transform_1_in_1_out::<HT_DCPERIOD>(name, "ht_dcperiod")
347    }
348    pub fn ht_dcphase(self, name: &str) -> LazyFrame {
349        self.math_transform_1_in_1_out::<HT_DCPHASE>(name, "ht_dcphase")
350    }
351    pub fn ht_trendmode(self, name: &str) -> LazyFrame {
352        self.math_transform_1_in_1_out::<HT_TRENDMODE>(name, "ht_trendmode")
353    }
354
355    pub fn ta_stddev(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
356        let name_str = name.to_string();
357        self.0.clone().with_columns([col(&name_str)
358            .map(
359                move |s| {
360                    let ca = s.f64()?;
361                    let mut indicator = TaSTDDEV::new(period, nbdev);
362                    let mut values = Vec::with_capacity(s.len());
363                    for i in 0..s.len() {
364                        let val = ca.get(i).unwrap_or(f64::NAN);
365                        values.push(indicator.next(val));
366                    }
367                    Ok(Some(Column::from(Series::new("ta_stddev".into(), values))))
368                },
369                GetOutput::from_type(DataType::Float64),
370            )
371            .alias("ta_stddev")])
372    }
373    pub fn ta_var(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
374        let name_str = name.to_string();
375        self.0.clone().with_columns([col(&name_str)
376            .map(
377                move |s| {
378                    let ca = s.f64()?;
379                    let mut indicator = TaVAR::new(period, nbdev);
380                    let mut values = Vec::with_capacity(s.len());
381                    for i in 0..s.len() {
382                        let val = ca.get(i).unwrap_or(f64::NAN);
383                        values.push(indicator.next(val));
384                    }
385                    Ok(Some(Column::from(Series::new("ta_var".into(), values))))
386                },
387                GetOutput::from_type(DataType::Float64),
388            )
389            .alias("ta_var")])
390    }
391    pub fn ta_beta(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
392        self.math_operator_2_in_1_out_period::<TaBETA>(in1, in2, period, "ta_beta")
393    }
394    pub fn ta_correl(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
395        self.math_operator_2_in_1_out_period::<TaCORREL>(in1, in2, period, "ta_correl")
396    }
397    pub fn ta_linearreg(self, name: &str, period: usize) -> LazyFrame {
398        self.math_operator_1_in_1_out_period::<TaLINEARREG>(name, period, "ta_linearreg")
399    }
400    pub fn ta_linearreg_slope(self, name: &str, period: usize) -> LazyFrame {
401        self.math_operator_1_in_1_out_period::<TaLINEARREG_SLOPE>(
402            name,
403            period,
404            "ta_linearreg_slope",
405        )
406    }
407    pub fn ta_linearreg_intercept(self, name: &str, period: usize) -> LazyFrame {
408        self.math_operator_1_in_1_out_period::<TaLINEARREG_INTERCEPT>(
409            name,
410            period,
411            "ta_linearreg_intercept",
412        )
413    }
414    pub fn ta_linearreg_angle(self, name: &str, period: usize) -> LazyFrame {
415        self.math_operator_1_in_1_out_period::<TaLINEARREG_ANGLE>(
416            name,
417            period,
418            "ta_linearreg_angle",
419        )
420    }
421    pub fn ta_tsf(self, name: &str, period: usize) -> LazyFrame {
422        self.math_operator_1_in_1_out_period::<TaTSF>(name, period, "ta_tsf")
423    }
424
425    pub fn cdl_2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
426        self.ta_4_in_1_out_default::<CDL2CROWS>(open, high, low, close, "cdl_2crows")
427    }
428    pub fn cdl_3blackcrows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
429        self.ta_4_in_1_out_default::<CDL3BLACKCROWS>(open, high, low, close, "cdl_3blackcrows")
430    }
431    pub fn cdl_3inside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
432        self.ta_4_in_1_out_default::<CDL3INSIDE>(open, high, low, close, "cdl_3inside")
433    }
434    pub fn cdl_3linestrike(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
435        self.ta_4_in_1_out_default::<CDL3LINESTRIKE>(open, high, low, close, "cdl_3linestrike")
436    }
437    pub fn cdl_3outside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
438        self.ta_4_in_1_out_default::<CDL3OUTSIDE>(open, high, low, close, "cdl_3outside")
439    }
440    pub fn cdl_3starsinsouth(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
441        self.ta_4_in_1_out_default::<CDL3STARSINSOUTH>(open, high, low, close, "cdl_3starsinsouth")
442    }
443    pub fn cdl_3whitesoldiers(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
444        self.ta_4_in_1_out_default::<CDL3WHITESOLDIERS>(
445            open,
446            high,
447            low,
448            close,
449            "cdl_3whitesoldiers",
450        )
451    }
452    pub fn cdl_abandonedbaby(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
453        self.ta_4_in_1_out_default::<CDLABANDONEDBABY>(open, high, low, close, "cdl_abandonedbaby")
454    }
455    pub fn cdl_advanceblock(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
456        self.ta_4_in_1_out_default::<CDLADVANCEBLOCK>(open, high, low, close, "cdl_advanceblock")
457    }
458    pub fn cdl_belthold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
459        self.ta_4_in_1_out_default::<CDLBELTHOLD>(open, high, low, close, "cdl_belthold")
460    }
461    pub fn cdl_breakaway(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
462        self.ta_4_in_1_out_default::<CDLBREAKAWAY>(open, high, low, close, "cdl_breakaway")
463    }
464    pub fn cdl_closingmarubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
465        self.ta_4_in_1_out_default::<CDLCLOSINGMARUBOZU>(
466            open,
467            high,
468            low,
469            close,
470            "cdl_closingmarubozu",
471        )
472    }
473    pub fn cdl_concealbabyswall(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
474        self.ta_4_in_1_out_default::<CDLCONCEALBABYSWALL>(
475            open,
476            high,
477            low,
478            close,
479            "cdl_concealbabyswall",
480        )
481    }
482    pub fn cdl_counterattack(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
483        self.ta_4_in_1_out_default::<CDLCOUNTERATTACK>(open, high, low, close, "cdl_counterattack")
484    }
485    pub fn cdl_darkcloudcover(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
486        self.ta_4_in_1_out_default::<CDLDARKCLOUDCOVER>(
487            open,
488            high,
489            low,
490            close,
491            "cdl_darkcloudcover",
492        )
493    }
494    pub fn cdl_doji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
495        self.ta_4_in_1_out_default::<CDLDOJI>(open, high, low, close, "cdl_doji")
496    }
497    pub fn cdl_dojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
498        self.ta_4_in_1_out_default::<CDLDOJISTAR>(open, high, low, close, "cdl_dojistar")
499    }
500    pub fn cdl_dragonflydoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
501        self.ta_4_in_1_out_default::<CDLDRAGONFLYDOJI>(open, high, low, close, "cdl_dragonflydoji")
502    }
503    pub fn cdl_engulfing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
504        self.ta_4_in_1_out_default::<CDLENGULFING>(open, high, low, close, "cdl_engulfing")
505    }
506    pub fn cdl_eveningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
507        self.ta_4_in_1_out_default::<CDLEVENINGDOJISTAR>(
508            open,
509            high,
510            low,
511            close,
512            "cdl_eveningdojistar",
513        )
514    }
515    pub fn cdl_eveningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
516        self.ta_4_in_1_out_default::<CDLEVENINGSTAR>(open, high, low, close, "cdl_eveningstar")
517    }
518    pub fn cdl_gapsidesidewhite(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
519        self.ta_4_in_1_out_default::<CDLGAPSIDESIDEWHITE>(
520            open,
521            high,
522            low,
523            close,
524            "cdl_gapsidesidewhite",
525        )
526    }
527    pub fn cdl_gravestonedoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
528        self.ta_4_in_1_out_default::<CDLGRAVESTONEDOJI>(
529            open,
530            high,
531            low,
532            close,
533            "cdl_gravestonedoji",
534        )
535    }
536    pub fn cdl_hammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
537        self.ta_4_in_1_out_default::<CDLHAMMER>(open, high, low, close, "cdl_hammer")
538    }
539    pub fn cdl_hangingman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
540        self.ta_4_in_1_out_default::<CDLHANGINGMAN>(open, high, low, close, "cdl_hangingman")
541    }
542    pub fn cdl_harami(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
543        self.ta_4_in_1_out_default::<CDLHARAMI>(open, high, low, close, "cdl_harami")
544    }
545    pub fn cdl_haramicross(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
546        self.ta_4_in_1_out_default::<CDLHARAMICROSS>(open, high, low, close, "cdl_haramicross")
547    }
548    pub fn cdl_highwave(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
549        self.ta_4_in_1_out_default::<CDLHIGHWAVE>(open, high, low, close, "cdl_highwave")
550    }
551    pub fn cdl_hikkake(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
552        self.ta_4_in_1_out_default::<CDLHIKKAKE>(open, high, low, close, "cdl_hikkake")
553    }
554    pub fn cdl_hikkakemod(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
555        self.ta_4_in_1_out_default::<CDLHIKKAKEMOD>(open, high, low, close, "cdl_hikkakemod")
556    }
557    pub fn cdl_homingpigeon(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
558        self.ta_4_in_1_out_default::<CDLHOMINGPIGEON>(open, high, low, close, "cdl_homingpigeon")
559    }
560    pub fn cdl_identical3crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
561        self.ta_4_in_1_out_default::<CDLIDENTICAL3CROWS>(
562            open,
563            high,
564            low,
565            close,
566            "cdl_identical3crows",
567        )
568    }
569    pub fn cdl_inneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
570        self.ta_4_in_1_out_default::<CDLINNECK>(open, high, low, close, "cdl_inneck")
571    }
572    pub fn cdl_invertedhammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
573        self.ta_4_in_1_out_default::<CDLINVERTEDHAMMER>(
574            open,
575            high,
576            low,
577            close,
578            "cdl_invertedhammer",
579        )
580    }
581    pub fn cdl_kicking(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
582        self.ta_4_in_1_out_default::<CDLKICKING>(open, high, low, close, "cdl_kicking")
583    }
584    pub fn cdl_kickingbylength(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
585        self.ta_4_in_1_out_default::<CDLKICKINGBYLENGTH>(
586            open,
587            high,
588            low,
589            close,
590            "cdl_kickingbylength",
591        )
592    }
593    pub fn cdl_ladderbottom(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
594        self.ta_4_in_1_out_default::<CDLLADDERBOTTOM>(open, high, low, close, "cdl_ladderbottom")
595    }
596    pub fn cdl_longleggeddoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
597        self.ta_4_in_1_out_default::<CDLLONGLEGGEDDOJI>(
598            open,
599            high,
600            low,
601            close,
602            "cdl_longleggeddoji",
603        )
604    }
605    pub fn cdl_longline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
606        self.ta_4_in_1_out_default::<CDLLONGLINE>(open, high, low, close, "cdl_longline")
607    }
608    pub fn cdl_marubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
609        self.ta_4_in_1_out_default::<CDLMARUBOZU>(open, high, low, close, "cdl_marubozu")
610    }
611    pub fn cdl_matchinglow(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
612        self.ta_4_in_1_out_default::<CDLMATCHINGLOW>(open, high, low, close, "cdl_matchinglow")
613    }
614    pub fn cdl_mathold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
615        self.ta_4_in_1_out_default::<CDLMATHOLD>(open, high, low, close, "cdl_mathold")
616    }
617    pub fn cdl_morningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
618        self.ta_4_in_1_out_default::<CDLMORNINGDOJISTAR>(
619            open,
620            high,
621            low,
622            close,
623            "cdl_morningdojistar",
624        )
625    }
626    pub fn cdl_morningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
627        self.ta_4_in_1_out_default::<CDLMORNINGSTAR>(open, high, low, close, "cdl_morningstar")
628    }
629    pub fn cdl_onneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
630        self.ta_4_in_1_out_default::<CDLONNECK>(open, high, low, close, "cdl_onneck")
631    }
632    pub fn cdl_piercing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
633        self.ta_4_in_1_out_default::<CDLPIERCING>(open, high, low, close, "cdl_piercing")
634    }
635    pub fn cdl_rickshawman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
636        self.ta_4_in_1_out_default::<CDLRICKSHAWMAN>(open, high, low, close, "cdl_rickshawman")
637    }
638    pub fn cdl_risefall3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
639        self.ta_4_in_1_out_default::<CDLRISEFALL3METHODS>(
640            open,
641            high,
642            low,
643            close,
644            "cdl_risefall3methods",
645        )
646    }
647    pub fn cdl_separatinglines(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
648        self.ta_4_in_1_out_default::<CDLSEPARATINGLINES>(
649            open,
650            high,
651            low,
652            close,
653            "cdl_separatinglines",
654        )
655    }
656    pub fn cdl_shootingstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
657        self.ta_4_in_1_out_default::<CDLSHOOTINGSTAR>(open, high, low, close, "cdl_shootingstar")
658    }
659    pub fn cdl_shortline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
660        self.ta_4_in_1_out_default::<CDLSHORTLINE>(open, high, low, close, "cdl_shortline")
661    }
662    pub fn cdl_spinningtop(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
663        self.ta_4_in_1_out_default::<CDLSPINNINGTOP>(open, high, low, close, "cdl_spinningtop")
664    }
665    pub fn cdl_stalledpattern(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
666        self.ta_4_in_1_out_default::<CDLSTALLEDPATTERN>(
667            open,
668            high,
669            low,
670            close,
671            "cdl_stalledpattern",
672        )
673    }
674    pub fn cdl_sticksandwich(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
675        self.ta_4_in_1_out_default::<CDLSTICKSANDWICH>(open, high, low, close, "cdl_sticksandwich")
676    }
677    pub fn cdl_takuri(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
678        self.ta_4_in_1_out_default::<CDLTAKURI>(open, high, low, close, "cdl_takuri")
679    }
680    pub fn cdl_tasukigap(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
681        self.ta_4_in_1_out_default::<CDLTASUKIGAP>(open, high, low, close, "cdl_tasukigap")
682    }
683    pub fn cdl_thrusting(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
684        self.ta_4_in_1_out_default::<CDLTHRUSTING>(open, high, low, close, "cdl_thrusting")
685    }
686    pub fn cdl_tristar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
687        self.ta_4_in_1_out_default::<CDLTRISTAR>(open, high, low, close, "cdl_tristar")
688    }
689    pub fn cdl_unique3river(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
690        self.ta_4_in_1_out_default::<CDLUNIQUE3RIVER>(open, high, low, close, "cdl_unique3river")
691    }
692    pub fn cdl_upsidegap2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
693        self.ta_4_in_1_out_default::<CDLUPSIDEGAP2CROWS>(
694            open,
695            high,
696            low,
697            close,
698            "cdl_upsidegap2crows",
699        )
700    }
701    pub fn cdl_xsidegap3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
702        self.ta_4_in_1_out_default::<CDLXSIDEGAP3METHODS>(
703            open,
704            high,
705            low,
706            close,
707            "cdl_xsidegap3methods",
708        )
709    }
710
711    pub fn macd(self, name: &str, fast: usize, slow: usize, signal: usize) -> LazyFrame {
712        let name_str = name.to_string();
713        self.0.clone().with_columns([col(&name_str)
714            .map(
715                move |s| {
716                    let ca = s.f64()?;
717                    let mut indicator = MACD::new(fast, slow, signal);
718                    let mut macd_vals = Vec::with_capacity(s.len());
719                    let mut signal_vals = Vec::with_capacity(s.len());
720                    let mut hist_vals = Vec::with_capacity(s.len());
721
722                    for i in 0..s.len() {
723                        let val = ca.get(i).unwrap_or(f64::NAN);
724                        let (m, s_val, h) = indicator.next(val);
725                        macd_vals.push(m);
726                        signal_vals.push(s_val);
727                        hist_vals.push(h);
728                    }
729
730                    let s_macd = Series::new("macd".into(), macd_vals);
731                    let s_signal = Series::new("macd_signal".into(), signal_vals);
732                    let s_hist = Series::new("macd_hist".into(), hist_vals);
733
734                    let struct_series = StructChunked::from_series(
735                        "macd_result".into(),
736                        s.len(),
737                        [s_macd, s_signal, s_hist].iter(),
738                    )?;
739                    Ok(Some(Column::from(struct_series.into_series())))
740                },
741                GetOutput::from_type(DataType::Struct(vec![
742                    Field::new("macd".into(), DataType::Float64),
743                    Field::new("macd_signal".into(), DataType::Float64),
744                    Field::new("macd_hist".into(), DataType::Float64),
745                ])),
746            )
747            .alias("macd")])
748    }
749
750    pub fn bbands(
751        self,
752        name: &str,
753        period: usize,
754        nbdevup: f64,
755        nbdevdn: f64,
756        matype: talib::MaType,
757    ) -> LazyFrame {
758        let name_str = name.to_string();
759        self.0.clone().with_columns([col(&name_str)
760            .map(
761                move |s| {
762                    let ca = s.f64()?;
763                    let mut indicator = BBANDS::new(period, nbdevup, nbdevdn, matype);
764                    let mut upper_vals = Vec::with_capacity(s.len());
765                    let mut middle_vals = Vec::with_capacity(s.len());
766                    let mut lower_vals = Vec::with_capacity(s.len());
767
768                    for i in 0..s.len() {
769                        let val = ca.get(i).unwrap_or(f64::NAN);
770                        let (u, m, l) = indicator.next(val);
771                        upper_vals.push(u);
772                        middle_vals.push(m);
773                        lower_vals.push(l);
774                    }
775
776                    let s_upper = Series::new("upper".into(), upper_vals);
777                    let s_middle = Series::new("middle".into(), middle_vals);
778                    let s_lower = Series::new("lower".into(), lower_vals);
779
780                    let struct_series = StructChunked::from_series(
781                        "bbands_result".into(),
782                        s.len(),
783                        [s_upper, s_middle, s_lower].iter(),
784                    )?;
785                    Ok(Some(Column::from(struct_series.into_series())))
786                },
787                GetOutput::from_type(DataType::Struct(vec![
788                    Field::new("upper".into(), DataType::Float64),
789                    Field::new("middle".into(), DataType::Float64),
790                    Field::new("lower".into(), DataType::Float64),
791                ])),
792            )
793            .alias("bbands")])
794    }
795
796    fn ta_3_in_1_out_period<I>(
797        self,
798        in1: &str,
799        in2: &str,
800        in3: &str,
801        period: usize,
802        output_name: &str,
803    ) -> LazyFrame
804    where
805        I: Next<(f64, f64, f64), Output = f64> + Send + Sync + 'static,
806        I: From<usize>,
807    {
808        let in1_str = in1.to_string();
809        let in2_str = in2.to_string();
810        let in3_str = in3.to_string();
811        let output_name_str = output_name.to_string();
812        let output_name_for_closure = output_name_str.clone();
813        self.0.clone().with_columns(
814            [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
815                .map(
816                    move |s| {
817                        let ca = s.struct_()?;
818                        let s1 = ca.field_by_name(&in1_str)?;
819                        let s2 = ca.field_by_name(&in2_str)?;
820                        let s3 = ca.field_by_name(&in3_str)?;
821
822                        let ca1 = s1.f64()?;
823                        let ca2 = s2.f64()?;
824                        let ca3 = s3.f64()?;
825
826                        let mut indicator = I::from(period);
827                        let mut values = Vec::with_capacity(s.len());
828
829                        for i in 0..s.len() {
830                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
831                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
832                            let v3 = ca3.get(i).unwrap_or(f64::NAN);
833                            values.push(indicator.next((v1, v2, v3)));
834                        }
835
836                        Ok(Some(Column::from(Series::new(
837                            output_name_for_closure.clone().into(),
838                            values,
839                        ))))
840                    },
841                    GetOutput::from_type(DataType::Float64),
842                )
843                .alias(&output_name_str)],
844        )
845    }
846
847    fn ta_3_in_1_out_default<I>(
848        self,
849        in1: &str,
850        in2: &str,
851        in3: &str,
852        output_name: &str,
853    ) -> LazyFrame
854    where
855        I: Next<(f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
856    {
857        let in1_str = in1.to_string();
858        let in2_str = in2.to_string();
859        let in3_str = in3.to_string();
860        let output_name_str = output_name.to_string();
861        let output_name_for_closure = output_name_str.clone();
862        self.0.clone().with_columns(
863            [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
864                .map(
865                    move |s| {
866                        let ca = s.struct_()?;
867                        let s1 = ca.field_by_name(&in1_str)?;
868                        let s2 = ca.field_by_name(&in2_str)?;
869                        let s3 = ca.field_by_name(&in3_str)?;
870
871                        let ca1 = s1.f64()?;
872                        let ca2 = s2.f64()?;
873                        let ca3 = s3.f64()?;
874
875                        let mut indicator = I::default();
876                        let mut values = Vec::with_capacity(s.len());
877
878                        for i in 0..s.len() {
879                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
880                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
881                            let v3 = ca3.get(i).unwrap_or(f64::NAN);
882                            values.push(indicator.next((v1, v2, v3)));
883                        }
884
885                        Ok(Some(Column::from(Series::new(
886                            output_name_for_closure.clone().into(),
887                            values,
888                        ))))
889                    },
890                    GetOutput::from_type(DataType::Float64),
891                )
892                .alias(&output_name_str)],
893        )
894    }
895
896    fn ta_4_in_1_out_default<I>(
897        self,
898        in1: &str,
899        in2: &str,
900        in3: &str,
901        in4: &str,
902        output_name: &str,
903    ) -> LazyFrame
904    where
905        I: Next<(f64, f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
906    {
907        let in1_str = in1.to_string();
908        let in2_str = in2.to_string();
909        let in3_str = in3.to_string();
910        let in4_str = in4.to_string();
911        let output_name_str = output_name.to_string();
912        let output_name_for_closure = output_name_str.clone();
913        self.0.clone().with_columns([as_struct(vec![
914            col(&in1_str),
915            col(&in2_str),
916            col(&in3_str),
917            col(&in4_str),
918        ])
919        .map(
920            move |s| {
921                let ca = s.struct_()?;
922                let s1 = ca.field_by_name(&in1_str)?;
923                let s2 = ca.field_by_name(&in2_str)?;
924                let s3 = ca.field_by_name(&in3_str)?;
925                let s4 = ca.field_by_name(&in4_str)?;
926
927                let ca1 = s1.f64()?;
928                let ca2 = s2.f64()?;
929                let ca3 = s3.f64()?;
930                let ca4 = s4.f64()?;
931
932                let mut indicator = I::default();
933                let mut values = Vec::with_capacity(s.len());
934
935                for i in 0..s.len() {
936                    let v1 = ca1.get(i).unwrap_or(f64::NAN);
937                    let v2 = ca2.get(i).unwrap_or(f64::NAN);
938                    let v3 = ca3.get(i).unwrap_or(f64::NAN);
939                    let v4 = ca4.get(i).unwrap_or(f64::NAN);
940                    values.push(indicator.next((v1, v2, v3, v4)));
941                }
942
943                Ok(Some(Column::from(Series::new(
944                    output_name_for_closure.clone().into(),
945                    values,
946                ))))
947            },
948            GetOutput::from_type(DataType::Float64),
949        )
950        .alias(&output_name_str)])
951    }
952
953    fn math_transform_1_in_1_out<I>(self, name: &str, output_name: &str) -> LazyFrame
954    where
955        I: Next<f64, Output = f64> + Default + Send + Sync + 'static,
956    {
957        let name = name.to_string();
958        let output_name_str = output_name.to_string();
959        let output_name_for_closure = output_name_str.clone();
960        self.0.clone().with_columns([col(&name)
961            .map(
962                move |s| {
963                    let ca = s.f64()?;
964                    let mut indicator = I::default();
965                    let mut values = Vec::with_capacity(s.len());
966
967                    for i in 0..s.len() {
968                        let val = ca.get(i).unwrap_or(f64::NAN);
969                        values.push(indicator.next(val));
970                    }
971
972                    Ok(Some(Column::from(Series::new(
973                        output_name_for_closure.clone().into(),
974                        values,
975                    ))))
976                },
977                GetOutput::from_type(DataType::Float64),
978            )
979            .alias(&output_name_str)])
980    }
981
982    fn math_operator_2_in_1_out<I>(self, in1: &str, in2: &str, output_name: &str) -> LazyFrame
983    where
984        I: Next<(f64, f64), Output = f64> + Default + Send + Sync + 'static,
985    {
986        let in1_str = in1.to_string();
987        let in2_str = in2.to_string();
988        let output_name_str = output_name.to_string();
989        let output_name_for_closure = output_name_str.clone();
990        self.0
991            .clone()
992            .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
993                .map(
994                    move |s| {
995                        let ca = s.struct_()?;
996                        let s1 = ca.field_by_name(&in1_str)?;
997                        let s2 = ca.field_by_name(&in2_str)?;
998
999                        let ca1 = s1.f64()?;
1000                        let ca2 = s2.f64()?;
1001
1002                        let mut indicator = I::default();
1003                        let mut values = Vec::with_capacity(s.len());
1004
1005                        for i in 0..s.len() {
1006                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
1007                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
1008                            values.push(indicator.next((v1, v2)));
1009                        }
1010
1011                        Ok(Some(Column::from(Series::new(
1012                            output_name_for_closure.clone().into(),
1013                            values,
1014                        ))))
1015                    },
1016                    GetOutput::from_type(DataType::Float64),
1017                )
1018                .alias(&output_name_str)])
1019    }
1020
1021    fn math_operator_1_in_1_out_period<I>(
1022        self,
1023        name: &str,
1024        period: usize,
1025        output_name: &str,
1026    ) -> LazyFrame
1027    where
1028        I: Next<f64, Output = f64> + Send + Sync + 'static,
1029        I: From<usize>,
1030    {
1031        let name = name.to_string();
1032        let output_name_str = output_name.to_string();
1033        let output_name_for_closure = output_name_str.clone();
1034        self.0.clone().with_columns([col(&name)
1035            .map(
1036                move |s| {
1037                    let ca = s.f64()?;
1038                    let mut indicator = I::from(period);
1039                    let mut values = Vec::with_capacity(s.len());
1040
1041                    for i in 0..s.len() {
1042                        let val = ca.get(i).unwrap_or(f64::NAN);
1043                        values.push(indicator.next(val));
1044                    }
1045
1046                    Ok(Some(Column::from(Series::new(
1047                        output_name_for_closure.clone().into(),
1048                        values,
1049                    ))))
1050                },
1051                GetOutput::from_type(DataType::Float64),
1052            )
1053            .alias(&output_name_str)])
1054    }
1055
1056    fn math_operator_2_in_1_out_period<I>(
1057        self,
1058        in1: &str,
1059        in2: &str,
1060        period: usize,
1061        output_name: &str,
1062    ) -> LazyFrame
1063    where
1064        I: Next<(f64, f64), Output = f64> + Send + Sync + 'static,
1065        I: From<usize>,
1066    {
1067        let in1_str = in1.to_string();
1068        let in2_str = in2.to_string();
1069        let output_name_str = output_name.to_string();
1070        let output_name_for_closure = output_name_str.clone();
1071        self.0
1072            .clone()
1073            .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1074                .map(
1075                    move |s| {
1076                        let ca = s.struct_()?;
1077                        let s1 = ca.field_by_name(&in1_str)?;
1078                        let s2 = ca.field_by_name(&in2_str)?;
1079
1080                        let ca1 = s1.f64()?;
1081                        let ca2 = s2.f64()?;
1082
1083                        let mut indicator = I::from(period);
1084                        let mut values = Vec::with_capacity(s.len());
1085
1086                        for i in 0..s.len() {
1087                            let v1 = ca1.get(i).unwrap_or(f64::NAN);
1088                            let v2 = ca2.get(i).unwrap_or(f64::NAN);
1089                            values.push(indicator.next((v1, v2)));
1090                        }
1091
1092                        Ok(Some(Column::from(Series::new(
1093                            output_name_for_closure.clone().into(),
1094                            values,
1095                        ))))
1096                    },
1097                    GetOutput::from_type(DataType::Float64),
1098                )
1099                .alias(&output_name_str)])
1100    }
1101
1102    pub fn supertrend(self, period: usize, multiplier: f64) -> LazyFrame {
1103        self.0
1104            .clone()
1105            .with_columns([as_struct(vec![col("high"), col("low"), col("close")])
1106                .map(
1107                    move |s| {
1108                        let ca = s.struct_()?;
1109                        let s_high = ca.field_by_name("high")?;
1110                        let s_low = ca.field_by_name("low")?;
1111                        let s_close = ca.field_by_name("close")?;
1112
1113                        let high = s_high.f64()?;
1114                        let low = s_low.f64()?;
1115                        let close = s_close.f64()?;
1116
1117                        let mut st = SuperTrend::new(period, multiplier);
1118                        let mut values = Vec::with_capacity(s.len());
1119                        let mut directions = Vec::with_capacity(s.len());
1120
1121                        for i in 0..s.len() {
1122                            let h = high.get(i).unwrap_or(0.0);
1123                            let l = low.get(i).unwrap_or(0.0);
1124                            let c = close.get(i).unwrap_or(0.0);
1125                            let (val, dir) = st.next((h, l, c));
1126                            values.push(val);
1127                            directions.push(dir as f64);
1128                        }
1129
1130                        let st_series = Series::new("supertrend".into(), values);
1131                        let dir_series = Series::new("supertrend_direction".into(), directions);
1132
1133                        let out = StructChunked::from_series(
1134                            "supertrend_output".into(),
1135                            s.len(),
1136                            [st_series, dir_series].iter(),
1137                        )?;
1138                        Ok(Some(Column::from(out.into_series())))
1139                    },
1140                    GetOutput::from_type(DataType::Struct(vec![
1141                        Field::new("supertrend".into(), DataType::Float64),
1142                        Field::new("supertrend_direction".into(), DataType::Float64),
1143                    ])),
1144                )
1145                .alias("supertrend_data")])
1146    }
1147
1148    pub fn anchored_vwap(self, price: &str, volume: &str, anchor: &str) -> LazyFrame {
1149        let price = price.to_string();
1150        let volume = volume.to_string();
1151        let anchor = anchor.to_string();
1152
1153        self.0
1154            .clone()
1155            .with_columns([as_struct(vec![col(&price), col(&volume), col(&anchor)])
1156                .map(
1157                    move |s| {
1158                        let ca = s.struct_()?;
1159                        let s_price = ca.field_by_name(&price)?;
1160                        let s_volume = ca.field_by_name(&volume)?;
1161                        let s_anchor = ca.field_by_name(&anchor)?;
1162
1163                        let price = s_price.f64()?;
1164                        let volume = s_volume.f64()?;
1165                        let anchor = s_anchor.bool()?;
1166
1167                        let mut avwap = quantwave_core::AnchoredVWAP::new();
1168                        let mut values = Vec::with_capacity(s.len());
1169
1170                        for i in 0..s.len() {
1171                            let p = price.get(i).unwrap_or(0.0);
1172                            let v = volume.get(i).unwrap_or(0.0);
1173                            let a = anchor.get(i).unwrap_or(false);
1174                            values.push(avwap.next((p, v, a)));
1175                        }
1176
1177                        Ok(Some(Column::from(Series::new(
1178                            "anchored_vwap".into(),
1179                            values,
1180                        ))))
1181                    },
1182                    GetOutput::from_type(DataType::Float64),
1183                )
1184                .alias("avwap")])
1185    }
1186
1187    pub fn hma(self, name: &str, period: usize) -> LazyFrame {
1188        let name = name.to_string();
1189        self.0.clone().with_columns([col(&name)
1190            .map(
1191                move |s| {
1192                    let ca = s.f64()?;
1193                    let mut hma = quantwave_core::HMA::new(period);
1194                    let mut values = Vec::with_capacity(s.len());
1195
1196                    for i in 0..s.len() {
1197                        let val = ca.get(i).unwrap_or(0.0);
1198                        values.push(hma.next(val));
1199                    }
1200
1201                    Ok(Some(Column::from(Series::new("hma".into(), values))))
1202                },
1203                GetOutput::from_type(DataType::Float64),
1204            )
1205            .alias("hma")])
1206    }
1207
1208    pub fn vpn(
1209        self,
1210        high: &str,
1211        low: &str,
1212        close: &str,
1213        volume: &str,
1214        period: usize,
1215        smooth_period: usize,
1216    ) -> LazyFrame {
1217        let high_str = high.to_string();
1218        let low_str = low.to_string();
1219        let close_str = close.to_string();
1220        let volume_str = volume.to_string();
1221
1222        self.0.clone().with_columns([as_struct(vec![
1223            col(&high_str),
1224            col(&low_str),
1225            col(&close_str),
1226            col(&volume_str),
1227        ])
1228        .map(
1229            move |s| {
1230                let ca = s.struct_()?;
1231                let s_h = ca.field_by_name(&high_str)?;
1232                let s_l = ca.field_by_name(&low_str)?;
1233                let s_c = ca.field_by_name(&close_str)?;
1234                let s_v = ca.field_by_name(&volume_str)?;
1235
1236                let high = s_h.f64()?;
1237                let low = s_l.f64()?;
1238                let close = s_c.f64()?;
1239                let volume = s_v.f64()?;
1240
1241                let mut indicator = quantwave_core::VPNIndicator::new(period, smooth_period);
1242                let mut values = Vec::with_capacity(s.len());
1243
1244                for i in 0..s.len() {
1245                    let h = high.get(i).unwrap_or(f64::NAN);
1246                    let l = low.get(i).unwrap_or(f64::NAN);
1247                    let c = close.get(i).unwrap_or(f64::NAN);
1248                    let v = volume.get(i).unwrap_or(f64::NAN);
1249                    values.push(indicator.next((h, l, c, v)));
1250                }
1251
1252                Ok(Some(Column::from(Series::new("vpn".into(), values))))
1253            },
1254            GetOutput::from_type(DataType::Float64),
1255        )
1256        .alias("vpn")])
1257    }
1258
1259    pub fn gap_momentum(
1260        self,
1261        open: &str,
1262        close: &str,
1263        period: usize,
1264        signal_period: usize,
1265    ) -> LazyFrame {
1266        let open_str = open.to_string();
1267        let close_str = close.to_string();
1268
1269        self.0.clone().with_columns([as_struct(vec![
1270            col(&open_str),
1271            col(&close_str),
1272        ])
1273        .map(
1274            move |s| {
1275                let ca = s.struct_()?;
1276                let s_o = ca.field_by_name(&open_str)?;
1277                let s_c = ca.field_by_name(&close_str)?;
1278
1279                let open = s_o.f64()?;
1280                let close = s_c.f64()?;
1281
1282                let mut indicator = quantwave_core::GapMomentum::new(period, signal_period);
1283                let mut ratio_vals = Vec::with_capacity(s.len());
1284                let mut signal_vals = Vec::with_capacity(s.len());
1285
1286                for i in 0..s.len() {
1287                    let o = open.get(i).unwrap_or(f64::NAN);
1288                    let c = close.get(i).unwrap_or(f64::NAN);
1289                    let (ratio, signal) = indicator.next((o, c));
1290                    ratio_vals.push(ratio);
1291                    signal_vals.push(signal);
1292                }
1293
1294                let s_ratio = Series::new("gap_ratio".into(), ratio_vals);
1295                let s_signal = Series::new("gap_signal".into(), signal_vals);
1296                let struct_series = StructChunked::from_series(
1297                    "gap_momentum_result".into(),
1298                    s.len(),
1299                    [s_ratio, s_signal].iter(),
1300                )?;
1301                Ok(Some(Column::from(struct_series.into_series())))
1302            },
1303            GetOutput::from_type(DataType::Struct(vec![
1304                Field::new("gap_ratio".into(), DataType::Float64),
1305                Field::new("gap_signal".into(), DataType::Float64),
1306            ])),
1307        )
1308        .alias("gap_momentum")])
1309    }
1310
1311    pub fn autotune_filter(self, name: &str, window: usize, bandwidth: f64) -> LazyFrame {
1312        let name_str = name.to_string();
1313        self.0.clone().with_columns([col(&name_str)
1314            .map(
1315                move |s| {
1316                    let ca = s.f64()?;
1317                    let mut indicator = quantwave_core::AutoTuneFilter::new(window, bandwidth);
1318                    let mut values = Vec::with_capacity(s.len());
1319
1320                    for i in 0..s.len() {
1321                        let val = ca.get(i).unwrap_or(f64::NAN);
1322                        values.push(indicator.next(val));
1323                    }
1324
1325                    Ok(Some(Column::from(Series::new("autotune".into(), values))))
1326                },
1327                GetOutput::from_type(DataType::Float64),
1328            )
1329            .alias("autotune")])
1330    }
1331
1332    pub fn adaptive_ema(
1333        self,
1334        high: &str,
1335        low: &str,
1336        close: &str,
1337        period: usize,
1338        pds: usize,
1339    ) -> LazyFrame {
1340        let h_str = high.to_string();
1341        let l_str = low.to_string();
1342        let c_str = close.to_string();
1343
1344        self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1345            .map(
1346                move |s| {
1347                    let ca = s.struct_()?;
1348                    let f_h = ca.field_by_name(&h_str)?;
1349                    let high = f_h.f64()?;
1350                    let f_l = ca.field_by_name(&l_str)?;
1351                    let low = f_l.f64()?;
1352                    let f_c = ca.field_by_name(&c_str)?;
1353                    let close = f_c.f64()?;
1354
1355                    let mut indicator = quantwave_core::AdaptiveEMA::new(period, pds);
1356                    let mut values = Vec::with_capacity(s.len());
1357
1358                    for i in 0..s.len() {
1359                        let h = high.get(i).unwrap_or(f64::NAN);
1360                        let l = low.get(i).unwrap_or(f64::NAN);
1361                        let c = close.get(i).unwrap_or(f64::NAN);
1362                        values.push(indicator.next((h, l, c)));
1363                    }
1364
1365                    Ok(Some(Column::from(Series::new("adaptive_ema".into(), values))))
1366                },
1367                GetOutput::from_type(DataType::Float64),
1368            )
1369            .alias("adaptive_ema")])
1370    }
1371
1372    pub fn tradj_ema(
1373        self,
1374        high: &str,
1375        low: &str,
1376        close: &str,
1377        period: usize,
1378        pds: usize,
1379        mltp: f64,
1380    ) -> LazyFrame {
1381        let h_str = high.to_string();
1382        let l_str = low.to_string();
1383        let c_str = close.to_string();
1384
1385        self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1386            .map(
1387                move |s| {
1388                    let ca = s.struct_()?;
1389                    let f_h = ca.field_by_name(&h_str)?;
1390                    let high = f_h.f64()?;
1391                    let f_l = ca.field_by_name(&l_str)?;
1392                    let low = f_l.f64()?;
1393                    let f_c = ca.field_by_name(&c_str)?;
1394                    let close = f_c.f64()?;
1395
1396                    let mut indicator = quantwave_core::TRAdjEMA::new(period, pds, mltp);
1397                    let mut values = Vec::with_capacity(s.len());
1398
1399                    for i in 0..s.len() {
1400                        let h = high.get(i).unwrap_or(f64::NAN);
1401                        let l = low.get(i).unwrap_or(f64::NAN);
1402                        let c = close.get(i).unwrap_or(f64::NAN);
1403                        values.push(indicator.next((h, l, c)));
1404                    }
1405
1406                    Ok(Some(Column::from(Series::new("tradj_ema".into(), values))))
1407                },
1408                GetOutput::from_type(DataType::Float64),
1409            )
1410            .alias("tradj_ema")])
1411    }
1412
1413    pub fn obvm(
1414        self,
1415        high: &str,
1416        low: &str,
1417        close: &str,
1418        volume: &str,
1419        obvm_period: usize,
1420        signal_period: usize,
1421    ) -> LazyFrame {
1422        let h_str = high.to_string();
1423        let l_str = low.to_string();
1424        let c_str = close.to_string();
1425        let v_str = volume.to_string();
1426
1427        self.0.clone().with_columns([as_struct(vec![
1428            col(&h_str),
1429            col(&l_str),
1430            col(&c_str),
1431            col(&v_str),
1432        ])
1433        .map(
1434            move |s| {
1435                let ca = s.struct_()?;
1436                let f_h = ca.field_by_name(&h_str)?;
1437                let high = f_h.f64()?;
1438                let f_l = ca.field_by_name(&l_str)?;
1439                let low = f_l.f64()?;
1440                let f_c = ca.field_by_name(&c_str)?;
1441                let close = f_c.f64()?;
1442                let f_v = ca.field_by_name(&v_str)?;
1443                let volume = f_v.f64()?;
1444
1445                let mut indicator = quantwave_core::Obvm::new(obvm_period, signal_period);
1446                let mut obvm_vals = Vec::with_capacity(s.len());
1447                let mut signal_vals = Vec::with_capacity(s.len());
1448
1449                for i in 0..s.len() {
1450                    let h = high.get(i).unwrap_or(f64::NAN);
1451                    let l = low.get(i).unwrap_or(f64::NAN);
1452                    let c = close.get(i).unwrap_or(f64::NAN);
1453                    let v = volume.get(i).unwrap_or(f64::NAN);
1454                    let (o, sig) = indicator.next((h, l, c, v));
1455                    obvm_vals.push(o);
1456                    signal_vals.push(sig);
1457                }
1458
1459                let s_obvm = Series::new("obvm".into(), obvm_vals);
1460                let s_signal = Series::new("signal".into(), signal_vals);
1461                let struct_series = StructChunked::from_series(
1462                    "obvm_data".into(),
1463                    s.len(),
1464                    [s_obvm, s_signal].iter(),
1465                )?;
1466                Ok(Some(Column::from(struct_series.into_series())))
1467            },
1468            GetOutput::from_type(DataType::Struct(vec![
1469                Field::new("obvm".into(), DataType::Float64),
1470                Field::new("signal".into(), DataType::Float64),
1471            ])),
1472        )
1473        .alias("obvm_data")])
1474    }
1475
1476    pub fn vfi(
1477        self,
1478        high: &str,
1479        low: &str,
1480        close: &str,
1481        volume: &str,
1482        period: usize,
1483        coef: f64,
1484        vcoef: f64,
1485        smoothing_period: usize,
1486    ) -> LazyFrame {
1487        let h_str = high.to_string();
1488        let l_str = low.to_string();
1489        let c_str = close.to_string();
1490        let v_str = volume.to_string();
1491
1492        self.0.clone().with_columns([as_struct(vec![
1493            col(&h_str),
1494            col(&l_str),
1495            col(&c_str),
1496            col(&v_str),
1497        ])
1498        .map(
1499            move |s| {
1500                let ca = s.struct_()?;
1501                let f_h = ca.field_by_name(&h_str)?;
1502                let high = f_h.f64()?;
1503                let f_l = ca.field_by_name(&l_str)?;
1504                let low = f_l.f64()?;
1505                let f_c = ca.field_by_name(&c_str)?;
1506                let close = f_c.f64()?;
1507                let f_v = ca.field_by_name(&v_str)?;
1508                let volume = f_v.f64()?;
1509
1510                let mut indicator = quantwave_core::Vfi::new(period, coef, vcoef, smoothing_period);
1511                let mut values = Vec::with_capacity(s.len());
1512
1513                for i in 0..s.len() {
1514                    let h = high.get(i).unwrap_or(f64::NAN);
1515                    let l = low.get(i).unwrap_or(f64::NAN);
1516                    let c = close.get(i).unwrap_or(f64::NAN);
1517                    let v = volume.get(i).unwrap_or(f64::NAN);
1518                    values.push(indicator.next((h, l, c, v)));
1519                }
1520
1521                Ok(Some(Column::from(Series::new("vfi".into(), values))))
1522            },
1523            GetOutput::from_type(DataType::Float64),
1524        )
1525        .alias("vfi")])
1526    }
1527
1528    pub fn sve_volatility_bands(
1529        self,
1530        high: &str,
1531        low: &str,
1532        close: &str,
1533        bands_period: usize,
1534        bands_deviation: f64,
1535        low_band_adjust: f64,
1536        mid_line_length: usize,
1537    ) -> LazyFrame {
1538        let h_str = high.to_string();
1539        let l_str = low.to_string();
1540        let c_str = close.to_string();
1541
1542        self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1543            .map(
1544                move |s| {
1545                    let ca = s.struct_()?;
1546                    let f_h = ca.field_by_name(&h_str)?;
1547                    let high = f_h.f64()?;
1548                    let f_l = ca.field_by_name(&l_str)?;
1549                    let low = f_l.f64()?;
1550                    let f_c = ca.field_by_name(&c_str)?;
1551                    let close = f_c.f64()?;
1552
1553                    let mut indicator = quantwave_core::SVEVolatilityBands::new(
1554                        bands_period,
1555                        bands_deviation,
1556                        low_band_adjust,
1557                        mid_line_length,
1558                    );
1559                    let mut upper_vals = Vec::with_capacity(s.len());
1560                    let mut mid_vals = Vec::with_capacity(s.len());
1561                    let mut lower_vals = Vec::with_capacity(s.len());
1562
1563                    for i in 0..s.len() {
1564                        let h = high.get(i).unwrap_or(f64::NAN);
1565                        let l = low.get(i).unwrap_or(f64::NAN);
1566                        let c = close.get(i).unwrap_or(f64::NAN);
1567                        let (upper, mid, lower) = indicator.next((h, l, c));
1568                        upper_vals.push(upper);
1569                        mid_vals.push(mid);
1570                        lower_vals.push(lower);
1571                    }
1572
1573                    let s_upper = Series::new("upper".into(), upper_vals);
1574                    let s_mid = Series::new("middle".into(), mid_vals);
1575                    let s_lower = Series::new("lower".into(), lower_vals);
1576                    let struct_series = StructChunked::from_series(
1577                        "sve_bands_data".into(),
1578                        s.len(),
1579                        [s_upper, s_mid, s_lower].iter(),
1580                    )?;
1581                    Ok(Some(Column::from(struct_series.into_series())))
1582                },
1583                GetOutput::from_type(DataType::Struct(vec![
1584                    Field::new("upper".into(), DataType::Float64),
1585                    Field::new("middle".into(), DataType::Float64),
1586                    Field::new("lower".into(), DataType::Float64),
1587                ])),
1588            )
1589            .alias("sve_bands_data")])
1590    }
1591
1592    pub fn exp_dev_bands(
1593        self,
1594        name: &str,
1595        period: usize,
1596        multiplier: f64,
1597        use_sma: bool,
1598    ) -> LazyFrame {
1599        let name_str = name.to_string();
1600        self.0.clone().with_columns([col(&name_str)
1601            .map(
1602                move |s| {
1603                    let ca = s.f64()?;
1604                    let mut indicator = quantwave_core::ExpDevBands::new(period, multiplier, use_sma);
1605                    let mut upper_vals = Vec::with_capacity(s.len());
1606                    let mut mid_vals = Vec::with_capacity(s.len());
1607                    let mut lower_vals = Vec::with_capacity(s.len());
1608
1609                    for i in 0..s.len() {
1610                        let val = ca.get(i).unwrap_or(f64::NAN);
1611                        let (upper, mid, lower) = indicator.next(val);
1612                        upper_vals.push(upper);
1613                        mid_vals.push(mid);
1614                        lower_vals.push(lower);
1615                    }
1616
1617                    let s_upper = Series::new("upper".into(), upper_vals);
1618                    let s_mid = Series::new("middle".into(), mid_vals);
1619                    let s_lower = Series::new("lower".into(), lower_vals);
1620                    let struct_series = StructChunked::from_series(
1621                        "exp_dev_bands_data".into(),
1622                        s.len(),
1623                        [s_upper, s_mid, s_lower].iter(),
1624                    )?;
1625                    Ok(Some(Column::from(struct_series.into_series())))
1626                },
1627                GetOutput::from_type(DataType::Struct(vec![
1628                    Field::new("upper".into(), DataType::Float64),
1629                    Field::new("middle".into(), DataType::Float64),
1630                    Field::new("lower".into(), DataType::Float64),
1631                ])),
1632            )
1633            .alias("exp_dev_bands_data")])
1634    }
1635
1636    pub fn sdo(
1637        self,
1638        name: &str,
1639        lookback_period: usize,
1640        period: usize,
1641        ema_pds: usize,
1642    ) -> LazyFrame {
1643        let name_str = name.to_string();
1644        self.0.clone().with_columns([col(&name_str)
1645            .map(
1646                move |s| {
1647                    let ca = s.f64()?;
1648                    let mut indicator = quantwave_core::SDO::new(lookback_period, period, ema_pds);
1649                    let mut values = Vec::with_capacity(s.len());
1650
1651                    for i in 0..s.len() {
1652                        let val = ca.get(i).unwrap_or(f64::NAN);
1653                        values.push(indicator.next(val));
1654                    }
1655
1656                    Ok(Some(Column::from(Series::new("sdo".into(), values))))
1657                },
1658                GetOutput::from_type(DataType::Float64),
1659            )
1660            .alias("sdo")])
1661    }
1662
1663    pub fn rsmk(self, price: &str, benchmark: &str, length: usize, ema_length: usize) -> LazyFrame {
1664        let p_str = price.to_string();
1665        let b_str = benchmark.to_string();
1666
1667        self.0.clone().with_columns([as_struct(vec![col(&p_str), col(&b_str)])
1668            .map(
1669                move |s| {
1670                    let ca = s.struct_()?;
1671                    let f_p = ca.field_by_name(&p_str)?;
1672                    let price = f_p.f64()?;
1673                    let f_b = ca.field_by_name(&b_str)?;
1674                    let benchmark = f_b.f64()?;
1675
1676                    let mut indicator = quantwave_core::RSMK::new(length, ema_length);
1677                    let mut values = Vec::with_capacity(s.len());
1678
1679                    for i in 0..s.len() {
1680                        let p = price.get(i).unwrap_or(f64::NAN);
1681                        let b = benchmark.get(i).unwrap_or(f64::NAN);
1682                        values.push(indicator.next((p, b)));
1683                    }
1684
1685                    Ok(Some(Column::from(Series::new("rsmk".into(), values))))
1686                },
1687                GetOutput::from_type(DataType::Float64),
1688            )
1689            .alias("rsmk")])
1690    }
1691
1692    pub fn rodc(
1693        self,
1694        name: &str,
1695        window_size: usize,
1696        threshold: f64,
1697        smooth_period: usize,
1698    ) -> LazyFrame {
1699        let name_str = name.to_string();
1700        self.0.clone().with_columns([col(&name_str)
1701            .map(
1702                move |s| {
1703                    let ca = s.f64()?;
1704                    let mut indicator = quantwave_core::RODC::new(window_size, threshold, smooth_period);
1705                    let mut values = Vec::with_capacity(s.len());
1706
1707                    for i in 0..s.len() {
1708                        let val = ca.get(i).unwrap_or(f64::NAN);
1709                        values.push(indicator.next(val));
1710                    }
1711
1712                    Ok(Some(Column::from(Series::new("rodc".into(), values))))
1713                },
1714                GetOutput::from_type(DataType::Float64),
1715            )
1716            .alias("rodc")])
1717    }
1718
1719    pub fn reverse_ema(self, name: &str, alpha: f64) -> LazyFrame {
1720        let name_str = name.to_string();
1721        self.0.clone().with_columns([col(&name_str)
1722            .map(
1723                move |s| {
1724                    let ca = s.f64()?;
1725                    let mut indicator = quantwave_core::ReverseEMA::new(alpha);
1726                    let mut values = Vec::with_capacity(s.len());
1727
1728                    for i in 0..s.len() {
1729                        let val = ca.get(i).unwrap_or(f64::NAN);
1730                        values.push(indicator.next(val));
1731                    }
1732
1733                    Ok(Some(Column::from(Series::new("reverse_ema".into(), values))))
1734                },
1735                GetOutput::from_type(DataType::Float64),
1736            )
1737            .alias("reverse_ema")])
1738    }
1739
1740    pub fn harrington_adx(
1741        self,
1742        high: &str,
1743        low: &str,
1744        close: &str,
1745        adx_length: usize,
1746        adx_smooth_length: usize,
1747    ) -> LazyFrame {
1748        let h_str = high.to_string();
1749        let l_str = low.to_string();
1750        let c_str = close.to_string();
1751
1752        self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
1753            .map(
1754                move |s| {
1755                    let ca = s.struct_()?;
1756                    let f_h = ca.field_by_name(&h_str)?;
1757                    let high = f_h.f64()?;
1758                    let f_l = ca.field_by_name(&l_str)?;
1759                    let low = f_l.f64()?;
1760                    let f_c = ca.field_by_name(&c_str)?;
1761                    let close = f_c.f64()?;
1762
1763                    let mut indicator = quantwave_core::HarringtonADXOscillator::new(adx_length, adx_smooth_length);
1764                    let mut values = Vec::with_capacity(s.len());
1765
1766                    for i in 0..s.len() {
1767                        let h = high.get(i).unwrap_or(f64::NAN);
1768                        let l = low.get(i).unwrap_or(f64::NAN);
1769                        let c = close.get(i).unwrap_or(f64::NAN);
1770                        values.push(indicator.next((h, l, c)));
1771                    }
1772
1773                    Ok(Some(Column::from(Series::new("harrington_adx".into(), values))))
1774                },
1775                GetOutput::from_type(DataType::Float64),
1776            )
1777            .alias("harrington_adx")])
1778    }
1779
1780    pub fn keltner_channels(
1781        self,
1782        high: &str,
1783        low: &str,
1784        close: &str,
1785        ema_period: usize,
1786        atr_period: usize,
1787        multiplier: f64,
1788    ) -> LazyFrame {
1789        let high = high.to_string();
1790        let low = low.to_string();
1791        let close = close.to_string();
1792
1793        self.0
1794            .clone()
1795            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1796                .map(
1797                    move |s| {
1798                        let ca = s.struct_()?;
1799                        let s_high = ca.field_by_name(&high)?;
1800                        let s_low = ca.field_by_name(&low)?;
1801                        let s_close = ca.field_by_name(&close)?;
1802
1803                        let high = s_high.f64()?;
1804                        let low = s_low.f64()?;
1805                        let close = s_close.f64()?;
1806
1807                        let mut kc = quantwave_core::KeltnerChannels::new(
1808                            ema_period, atr_period, multiplier,
1809                        );
1810                        let mut uppers = Vec::with_capacity(s.len());
1811                        let mut middles = Vec::with_capacity(s.len());
1812                        let mut lowers = Vec::with_capacity(s.len());
1813
1814                        for i in 0..s.len() {
1815                            let h = high.get(i).unwrap_or(0.0);
1816                            let l = low.get(i).unwrap_or(0.0);
1817                            let c = close.get(i).unwrap_or(0.0);
1818                            let (upper, middle, lower) = kc.next((h, l, c));
1819                            uppers.push(upper);
1820                            middles.push(middle);
1821                            lowers.push(lower);
1822                        }
1823
1824                        let upper_series = Series::new("upper".into(), uppers);
1825                        let middle_series = Series::new("middle".into(), middles);
1826                        let lower_series = Series::new("lower".into(), lowers);
1827
1828                        let out = StructChunked::from_series(
1829                            "keltner_output".into(),
1830                            s.len(),
1831                            [upper_series, middle_series, lower_series].iter(),
1832                        )?;
1833                        Ok(Some(Column::from(out.into_series())))
1834                    },
1835                    GetOutput::from_type(DataType::Struct(vec![
1836                        Field::new("upper".into(), DataType::Float64),
1837                        Field::new("middle".into(), DataType::Float64),
1838                        Field::new("lower".into(), DataType::Float64),
1839                    ])),
1840                )
1841                .alias("keltner_data")])
1842    }
1843
1844    pub fn alma(self, name: &str, period: usize, offset: f64, sigma: f64) -> LazyFrame {
1845        let name = name.to_string();
1846        self.0.clone().with_columns([col(&name)
1847            .map(
1848                move |s| {
1849                    let ca = s.f64()?;
1850                    let mut alma = quantwave_core::ALMA::new(period, offset, sigma);
1851                    let mut values = Vec::with_capacity(s.len());
1852
1853                    for i in 0..s.len() {
1854                        let val = ca.get(i).unwrap_or(0.0);
1855                        values.push(alma.next(val));
1856                    }
1857
1858                    Ok(Some(Column::from(Series::new("alma".into(), values))))
1859                },
1860                GetOutput::from_type(DataType::Float64),
1861            )
1862            .alias("alma")])
1863    }
1864
1865    pub fn donchian_channels(self, high: &str, low: &str, period: usize) -> LazyFrame {
1866        let high = high.to_string();
1867        let low = low.to_string();
1868
1869        self.0
1870            .clone()
1871            .with_columns([as_struct(vec![col(&high), col(&low)])
1872                .map(
1873                    move |s| {
1874                        let ca = s.struct_()?;
1875                        let s_high = ca.field_by_name(&high)?;
1876                        let s_low = ca.field_by_name(&low)?;
1877
1878                        let high = s_high.f64()?;
1879                        let low = s_low.f64()?;
1880
1881                        let mut dc = quantwave_core::DonchianChannels::new(period);
1882                        let mut uppers = Vec::with_capacity(s.len());
1883                        let mut middles = Vec::with_capacity(s.len());
1884                        let mut lowers = Vec::with_capacity(s.len());
1885
1886                        for i in 0..s.len() {
1887                            let h = high.get(i).unwrap_or(0.0);
1888                            let l = low.get(i).unwrap_or(0.0);
1889                            let (upper, middle, lower) = dc.next((h, l));
1890                            uppers.push(upper);
1891                            middles.push(middle);
1892                            lowers.push(lower);
1893                        }
1894
1895                        let upper_series = Series::new("upper".into(), uppers);
1896                        let middle_series = Series::new("middle".into(), middles);
1897                        let lower_series = Series::new("lower".into(), lowers);
1898
1899                        let out = StructChunked::from_series(
1900                            "donchian_output".into(),
1901                            s.len(),
1902                            [upper_series, middle_series, lower_series].iter(),
1903                        )?;
1904                        Ok(Some(Column::from(out.into_series())))
1905                    },
1906                    GetOutput::from_type(DataType::Struct(vec![
1907                        Field::new("upper".into(), DataType::Float64),
1908                        Field::new("middle".into(), DataType::Float64),
1909                        Field::new("lower".into(), DataType::Float64),
1910                    ])),
1911                )
1912                .alias("donchian_data")])
1913    }
1914
1915    pub fn ttm_squeeze(
1916        self,
1917        high: &str,
1918        low: &str,
1919        close: &str,
1920        period: usize,
1921        multiplier_bb: f64,
1922        multiplier_kc: f64,
1923    ) -> LazyFrame {
1924        let high = high.to_string();
1925        let low = low.to_string();
1926        let close = close.to_string();
1927
1928        self.0
1929            .clone()
1930            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1931                .map(
1932                    move |s| {
1933                        let ca = s.struct_()?;
1934                        let s_high = ca.field_by_name(&high)?;
1935                        let s_low = ca.field_by_name(&low)?;
1936                        let s_close = ca.field_by_name(&close)?;
1937
1938                        let high = s_high.f64()?;
1939                        let low = s_low.f64()?;
1940                        let close = s_close.f64()?;
1941
1942                        let mut ttm =
1943                            quantwave_core::TTMSqueeze::new(period, multiplier_bb, multiplier_kc);
1944                        let mut histograms = Vec::with_capacity(s.len());
1945                        let mut squeezed = Vec::with_capacity(s.len());
1946
1947                        for i in 0..s.len() {
1948                            let h = high.get(i).unwrap_or(0.0);
1949                            let l = low.get(i).unwrap_or(0.0);
1950                            let c = close.get(i).unwrap_or(0.0);
1951                            let (hist, is_sq) = ttm.next((h, l, c));
1952                            histograms.push(hist);
1953                            squeezed.push(is_sq);
1954                        }
1955
1956                        let hist_series = Series::new("histogram".into(), histograms);
1957                        let squeezed_series = Series::new("is_squeezed".into(), squeezed);
1958
1959                        let out = StructChunked::from_series(
1960                            "ttm_squeeze_output".into(),
1961                            s.len(),
1962                            [hist_series, squeezed_series].iter(),
1963                        )?;
1964                        Ok(Some(Column::from(out.into_series())))
1965                    },
1966                    GetOutput::from_type(DataType::Struct(vec![
1967                        Field::new("histogram".into(), DataType::Float64),
1968                        Field::new("is_squeezed".into(), DataType::Boolean),
1969                    ])),
1970                )
1971                .alias("ttm_squeeze_data")])
1972    }
1973
1974    pub fn vortex_indicator(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
1975        let high = high.to_string();
1976        let low = low.to_string();
1977        let close = close.to_string();
1978
1979        self.0
1980            .clone()
1981            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1982                .map(
1983                    move |s| {
1984                        let ca = s.struct_()?;
1985                        let s_high = ca.field_by_name(&high)?;
1986                        let s_low = ca.field_by_name(&low)?;
1987                        let s_close = ca.field_by_name(&close)?;
1988
1989                        let high = s_high.f64()?;
1990                        let low = s_low.f64()?;
1991                        let close = s_close.f64()?;
1992
1993                        let mut vi = quantwave_core::VortexIndicator::new(period);
1994                        let mut plus_vals = Vec::with_capacity(s.len());
1995                        let mut minus_vals = Vec::with_capacity(s.len());
1996
1997                        for i in 0..s.len() {
1998                            let h = high.get(i).unwrap_or(0.0);
1999                            let l = low.get(i).unwrap_or(0.0);
2000                            let c = close.get(i).unwrap_or(0.0);
2001                            let (plus, minus) = vi.next((h, l, c));
2002                            plus_vals.push(plus);
2003                            minus_vals.push(minus);
2004                        }
2005
2006                        let plus_series = Series::new("vi_plus".into(), plus_vals);
2007                        let minus_series = Series::new("vi_minus".into(), minus_vals);
2008
2009                        let out = StructChunked::from_series(
2010                            "vortex_output".into(),
2011                            s.len(),
2012                            [plus_series, minus_series].iter(),
2013                        )?;
2014                        Ok(Some(Column::from(out.into_series())))
2015                    },
2016                    GetOutput::from_type(DataType::Struct(vec![
2017                        Field::new("vi_plus".into(), DataType::Float64),
2018                        Field::new("vi_minus".into(), DataType::Float64),
2019                    ])),
2020                )
2021                .alias("vortex_data")])
2022    }
2023
2024    pub fn heikin_ashi(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
2025        let open = open.to_string();
2026        let high = high.to_string();
2027        let low = low.to_string();
2028        let close = close.to_string();
2029
2030        self.0.clone().with_columns([as_struct(vec![
2031            col(&open),
2032            col(&high),
2033            col(&low),
2034            col(&close),
2035        ])
2036        .map(
2037            move |s| {
2038                let ca = s.struct_()?;
2039                let s_open = ca.field_by_name(&open)?;
2040                let s_high = ca.field_by_name(&high)?;
2041                let s_low = ca.field_by_name(&low)?;
2042                let s_close = ca.field_by_name(&close)?;
2043
2044                let open = s_open.f64()?;
2045                let high = s_high.f64()?;
2046                let low = s_low.f64()?;
2047                let close = s_close.f64()?;
2048
2049                let mut ha = quantwave_core::HeikinAshi::new();
2050                let mut ha_opens = Vec::with_capacity(s.len());
2051                let mut ha_highs = Vec::with_capacity(s.len());
2052                let mut ha_lows = Vec::with_capacity(s.len());
2053                let mut ha_closes = Vec::with_capacity(s.len());
2054
2055                for i in 0..s.len() {
2056                    let o = open.get(i).unwrap_or(0.0);
2057                    let h = high.get(i).unwrap_or(0.0);
2058                    let l = low.get(i).unwrap_or(0.0);
2059                    let c = close.get(i).unwrap_or(0.0);
2060                    let (ha_o, ha_h, ha_l, ha_c) = ha.next((o, h, l, c));
2061                    ha_opens.push(ha_o);
2062                    ha_highs.push(ha_h);
2063                    ha_lows.push(ha_l);
2064                    ha_closes.push(ha_c);
2065                }
2066
2067                let o_series = Series::new("ha_open".into(), ha_opens);
2068                let h_series = Series::new("ha_high".into(), ha_highs);
2069                let l_series = Series::new("ha_low".into(), ha_lows);
2070                let c_series = Series::new("ha_close".into(), ha_closes);
2071
2072                let out = StructChunked::from_series(
2073                    "heikin_ashi_output".into(),
2074                    s.len(),
2075                    [o_series, h_series, l_series, c_series].iter(),
2076                )?;
2077                Ok(Some(Column::from(out.into_series())))
2078            },
2079            GetOutput::from_type(DataType::Struct(vec![
2080                Field::new("ha_open".into(), DataType::Float64),
2081                Field::new("ha_high".into(), DataType::Float64),
2082                Field::new("ha_low".into(), DataType::Float64),
2083                Field::new("ha_close".into(), DataType::Float64),
2084            ])),
2085        )
2086        .alias("heikin_ashi_data")])
2087    }
2088
2089    pub fn wavetrend(
2090        self,
2091        high: &str,
2092        low: &str,
2093        close: &str,
2094        n1: usize,
2095        n2: usize,
2096        n3: usize,
2097    ) -> LazyFrame {
2098        let high = high.to_string();
2099        let low = low.to_string();
2100        let close = close.to_string();
2101
2102        self.0
2103            .clone()
2104            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2105                .map(
2106                    move |s| {
2107                        let ca = s.struct_()?;
2108                        let s_high = ca.field_by_name(&high)?;
2109                        let s_low = ca.field_by_name(&low)?;
2110                        let s_close = ca.field_by_name(&close)?;
2111
2112                        let high = s_high.f64()?;
2113                        let low = s_low.f64()?;
2114                        let close = s_close.f64()?;
2115
2116                        let mut wt = quantwave_core::WaveTrend::new(n1, n2, n3);
2117                        let mut wt1_vals = Vec::with_capacity(s.len());
2118                        let mut wt2_vals = Vec::with_capacity(s.len());
2119
2120                        for i in 0..s.len() {
2121                            let h = high.get(i).unwrap_or(0.0);
2122                            let l = low.get(i).unwrap_or(0.0);
2123                            let c = close.get(i).unwrap_or(0.0);
2124                            let (wt1, wt2) = wt.next((h, l, c));
2125                            wt1_vals.push(wt1);
2126                            wt2_vals.push(wt2);
2127                        }
2128
2129                        let wt1_series = Series::new("wt1".into(), wt1_vals);
2130                        let wt2_series = Series::new("wt2".into(), wt2_vals);
2131
2132                        let out = StructChunked::from_series(
2133                            "wavetrend_output".into(),
2134                            s.len(),
2135                            [wt1_series, wt2_series].iter(),
2136                        )?;
2137                        Ok(Some(Column::from(out.into_series())))
2138                    },
2139                    GetOutput::from_type(DataType::Struct(vec![
2140                        Field::new("wt1".into(), DataType::Float64),
2141                        Field::new("wt2".into(), DataType::Float64),
2142                    ])),
2143                )
2144                .alias("wavetrend_data")])
2145    }
2146
2147    pub fn tema(self, name: &str, period: usize) -> LazyFrame {
2148        let name = name.to_string();
2149        self.0.clone().with_columns([col(&name)
2150            .map(
2151                move |s| {
2152                    let ca = s.f64()?;
2153                    let mut tema = quantwave_core::TEMA::new(period);
2154                    let mut values = Vec::with_capacity(s.len());
2155
2156                    for i in 0..s.len() {
2157                        let val = ca.get(i).unwrap_or(0.0);
2158                        values.push(tema.next(val));
2159                    }
2160
2161                    Ok(Some(Column::from(Series::new("tema".into(), values))))
2162                },
2163                GetOutput::from_type(DataType::Float64),
2164            )
2165            .alias("tema")])
2166    }
2167
2168    pub fn zlema(self, name: &str, period: usize) -> LazyFrame {
2169        let name = name.to_string();
2170        self.0.clone().with_columns([col(&name)
2171            .map(
2172                move |s| {
2173                    let ca = s.f64()?;
2174                    let mut zlema = quantwave_core::ZLEMA::new(period);
2175                    let mut values = Vec::with_capacity(s.len());
2176
2177                    for i in 0..s.len() {
2178                        let val = ca.get(i).unwrap_or(0.0);
2179                        values.push(zlema.next(val));
2180                    }
2181
2182                    Ok(Some(Column::from(Series::new("zlema".into(), values))))
2183                },
2184                GetOutput::from_type(DataType::Float64),
2185            )
2186            .alias("zlema")])
2187    }
2188
2189    pub fn atr_trailing_stop(
2190        self,
2191        high: &str,
2192        low: &str,
2193        close: &str,
2194        period: usize,
2195        multiplier: f64,
2196    ) -> LazyFrame {
2197        let high = high.to_string();
2198        let low = low.to_string();
2199        let close = close.to_string();
2200
2201        self.0
2202            .clone()
2203            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2204                .map(
2205                    move |s| {
2206                        let ca = s.struct_()?;
2207                        let s_high = ca.field_by_name(&high)?;
2208                        let s_low = ca.field_by_name(&low)?;
2209                        let s_close = ca.field_by_name(&close)?;
2210
2211                        let high = s_high.f64()?;
2212                        let low = s_low.f64()?;
2213                        let close = s_close.f64()?;
2214
2215                        let mut atr_ts = quantwave_core::ATRTrailingStop::new(period, multiplier);
2216                        let mut stops = Vec::with_capacity(s.len());
2217                        let mut directions = Vec::with_capacity(s.len());
2218
2219                        for i in 0..s.len() {
2220                            let h = high.get(i).unwrap_or(0.0);
2221                            let l = low.get(i).unwrap_or(0.0);
2222                            let c = close.get(i).unwrap_or(0.0);
2223                            let (stop, dir) = atr_ts.next((h, l, c));
2224                            stops.push(stop);
2225                            directions.push(dir as f64);
2226                        }
2227
2228                        let stop_series = Series::new("stop".into(), stops);
2229                        let dir_series = Series::new("direction".into(), directions);
2230
2231                        let out = StructChunked::from_series(
2232                            "atr_ts_output".into(),
2233                            s.len(),
2234                            [stop_series, dir_series].iter(),
2235                        )?;
2236                        Ok(Some(Column::from(out.into_series())))
2237                    },
2238                    GetOutput::from_type(DataType::Struct(vec![
2239                        Field::new("stop".into(), DataType::Float64),
2240                        Field::new("direction".into(), DataType::Float64),
2241                    ])),
2242                )
2243                .alias("atr_ts_data")])
2244    }
2245
2246    pub fn pivot_points(self, high: &str, low: &str, close: &str) -> LazyFrame {
2247        let high = high.to_string();
2248        let low = low.to_string();
2249        let close = close.to_string();
2250
2251        self.0
2252            .clone()
2253            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2254                .map(
2255                    move |s| {
2256                        let ca = s.struct_()?;
2257                        let s_high = ca.field_by_name(&high)?;
2258                        let s_low = ca.field_by_name(&low)?;
2259                        let s_close = ca.field_by_name(&close)?;
2260
2261                        let high = s_high.f64()?;
2262                        let low = s_low.f64()?;
2263                        let close = s_close.f64()?;
2264
2265                        let mut pivot = quantwave_core::PivotPoints::new();
2266                        let mut p_vals = Vec::with_capacity(s.len());
2267                        let mut r1_vals = Vec::with_capacity(s.len());
2268                        let mut s1_vals = Vec::with_capacity(s.len());
2269                        let mut r2_vals = Vec::with_capacity(s.len());
2270                        let mut s2_vals = Vec::with_capacity(s.len());
2271
2272                        for i in 0..s.len() {
2273                            let h = high.get(i).unwrap_or(0.0);
2274                            let l = low.get(i).unwrap_or(0.0);
2275                            let c = close.get(i).unwrap_or(0.0);
2276                            let (p, r1, s1, r2, s2) = pivot.next((h, l, c));
2277                            p_vals.push(p);
2278                            r1_vals.push(r1);
2279                            s1_vals.push(s1);
2280                            r2_vals.push(r2);
2281                            s2_vals.push(s2);
2282                        }
2283
2284                        let p_series = Series::new("p".into(), p_vals);
2285                        let r1_series = Series::new("r1".into(), r1_vals);
2286                        let s1_series = Series::new("s1".into(), s1_vals);
2287                        let r2_series = Series::new("r2".into(), r2_vals);
2288                        let s2_series = Series::new("s2".into(), s2_vals);
2289
2290                        let out = StructChunked::from_series(
2291                            "pivot_output".into(),
2292                            s.len(),
2293                            [p_series, r1_series, s1_series, r2_series, s2_series].iter(),
2294                        )?;
2295                        Ok(Some(Column::from(out.into_series())))
2296                    },
2297                    GetOutput::from_type(DataType::Struct(vec![
2298                        Field::new("p".into(), DataType::Float64),
2299                        Field::new("r1".into(), DataType::Float64),
2300                        Field::new("s1".into(), DataType::Float64),
2301                        Field::new("r2".into(), DataType::Float64),
2302                        Field::new("s2".into(), DataType::Float64),
2303                    ])),
2304                )
2305                .alias("pivot_points_data")])
2306    }
2307
2308    pub fn bill_williams_fractals(self, high: &str, low: &str) -> LazyFrame {
2309        let high = high.to_string();
2310        let low = low.to_string();
2311
2312        self.0
2313            .clone()
2314            .with_columns([as_struct(vec![col(&high), col(&low)])
2315                .map(
2316                    move |s| {
2317                        let ca = s.struct_()?;
2318                        let s_high = ca.field_by_name(&high)?;
2319                        let s_low = ca.field_by_name(&low)?;
2320
2321                        let high = s_high.f64()?;
2322                        let low = s_low.f64()?;
2323
2324                        let mut fractals = quantwave_core::BillWilliamsFractals::new();
2325                        let mut bearish_vals = Vec::with_capacity(s.len());
2326                        let mut bullish_vals = Vec::with_capacity(s.len());
2327
2328                        for i in 0..s.len() {
2329                            let h = high.get(i).unwrap_or(0.0);
2330                            let l = low.get(i).unwrap_or(0.0);
2331                            let (bear, bull) = fractals.next((h, l));
2332                            bearish_vals.push(bear);
2333                            bullish_vals.push(bull);
2334                        }
2335
2336                        let bearish_series = Series::new("bearish".into(), bearish_vals);
2337                        let bullish_series = Series::new("bullish".into(), bullish_vals);
2338
2339                        let out = StructChunked::from_series(
2340                            "fractals_output".into(),
2341                            s.len(),
2342                            [bearish_series, bullish_series].iter(),
2343                        )?;
2344                        Ok(Some(Column::from(out.into_series())))
2345                    },
2346                    GetOutput::from_type(DataType::Struct(vec![
2347                        Field::new("bearish".into(), DataType::Boolean),
2348                        Field::new("bullish".into(), DataType::Boolean),
2349                    ])),
2350                )
2351                .alias("fractals_data")])
2352    }
2353
2354    pub fn ichimoku_cloud(
2355        self,
2356        high: &str,
2357        low: &str,
2358        p1: usize,
2359        p2: usize,
2360        p3: usize,
2361    ) -> LazyFrame {
2362        let high = high.to_string();
2363        let low = low.to_string();
2364
2365        self.0
2366            .clone()
2367            .with_columns([as_struct(vec![col(&high), col(&low)])
2368                .map(
2369                    move |s| {
2370                        let ca = s.struct_()?;
2371                        let s_high = ca.field_by_name(&high)?;
2372                        let s_low = ca.field_by_name(&low)?;
2373
2374                        let high = s_high.f64()?;
2375                        let low = s_low.f64()?;
2376
2377                        let mut ic = quantwave_core::IchimokuCloud::new(p1, p2, p3);
2378                        let mut t_vals = Vec::with_capacity(s.len());
2379                        let mut k_vals = Vec::with_capacity(s.len());
2380                        let mut sa_vals = Vec::with_capacity(s.len());
2381                        let mut sb_vals = Vec::with_capacity(s.len());
2382
2383                        for i in 0..s.len() {
2384                            let h = high.get(i).unwrap_or(0.0);
2385                            let l = low.get(i).unwrap_or(0.0);
2386                            let (t, k, sa, sb) = ic.next((h, l));
2387                            t_vals.push(t);
2388                            k_vals.push(k);
2389                            sa_vals.push(sa);
2390                            sb_vals.push(sb);
2391                        }
2392
2393                        let t_series = Series::new("tenkan".into(), t_vals);
2394                        let k_series = Series::new("kijun".into(), k_vals);
2395                        let sa_series = Series::new("senkou_a".into(), sa_vals);
2396                        let sb_series = Series::new("senkou_b".into(), sb_vals);
2397
2398                        let out = StructChunked::from_series(
2399                            "ichimoku_output".into(),
2400                            s.len(),
2401                            [t_series, k_series, sa_series, sb_series].iter(),
2402                        )?;
2403                        Ok(Some(Column::from(out.into_series())))
2404                    },
2405                    GetOutput::from_type(DataType::Struct(vec![
2406                        Field::new("tenkan".into(), DataType::Float64),
2407                        Field::new("kijun".into(), DataType::Float64),
2408                        Field::new("senkou_a".into(), DataType::Float64),
2409                        Field::new("senkou_b".into(), DataType::Float64),
2410                    ])),
2411                )
2412                .alias("ichimoku_data")])
2413    }
2414}
2415
2416#[cfg(test)]
2417mod tests {
2418    use super::*;
2419
2420    #[test]
2421    fn test_polars_heikin_ashi() -> PolarsResult<()> {
2422        let df = df![
2423            "open" => [10.0, 11.0],
2424            "high" => [12.0, 13.0],
2425            "low" => [8.0, 10.0],
2426            "close" => [11.0, 12.0]
2427        ]?;
2428
2429        let out = df
2430            .lazy()
2431            .ta()
2432            .heikin_ashi("open", "high", "low", "close")
2433            .collect()?;
2434
2435        let ha = out.column("heikin_ashi_data")?.struct_()?;
2436        assert_eq!(
2437            ha.field_by_name("ha_open".into())?.f64()?.get(0),
2438            Some(10.5)
2439        );
2440        assert_eq!(
2441            ha.field_by_name("ha_close".into())?.f64()?.get(0),
2442            Some(10.25)
2443        );
2444
2445        Ok(())
2446    }
2447
2448    #[test]
2449    fn test_polars_tema_zlema() -> PolarsResult<()> {
2450        let df = df![
2451            "price" => [1.0, 2.0, 3.0, 4.0, 5.0]
2452        ]?;
2453
2454        let out = df.clone().lazy().ta().tema("price", 3).collect()?;
2455
2456        let tema = out.column("tema")?.f64()?;
2457        assert!(tema.get(4).is_some());
2458
2459        let out2 = df.lazy().ta().zlema("price", 3).collect()?;
2460
2461        let zlema = out2.column("zlema")?.f64()?;
2462        assert!(zlema.get(4).is_some());
2463
2464        Ok(())
2465    }
2466
2467    #[test]
2468    fn test_polars_atr_ts() -> PolarsResult<()> {
2469        let df = df![
2470            "high" => [10.0, 12.0, 11.0],
2471            "low" => [8.0, 10.0, 9.0],
2472            "close" => [9.0, 11.0, 10.0]
2473        ]?;
2474
2475        let out = df
2476            .lazy()
2477            .ta()
2478            .atr_trailing_stop("high", "low", "close", 14, 2.5)
2479            .collect()?;
2480
2481        let atr_ts = out.column("atr_ts_data")?.struct_()?;
2482        assert!(atr_ts.field_by_name("stop".into())?.f64()?.get(0).is_some());
2483        assert!(
2484            atr_ts
2485                .field_by_name("direction".into())?
2486                .f64()?
2487                .get(0)
2488                .is_some()
2489        );
2490
2491        Ok(())
2492    }
2493
2494    #[test]
2495    fn test_polars_pivot_points() -> PolarsResult<()> {
2496        let df = df![
2497            "high" => [10.0, 12.0, 11.0],
2498            "low" => [8.0, 10.0, 9.0],
2499            "close" => [9.0, 11.0, 10.0]
2500        ]?;
2501
2502        let out = df
2503            .lazy()
2504            .ta()
2505            .pivot_points("high", "low", "close")
2506            .collect()?;
2507
2508        let pivot = out.column("pivot_points_data")?.struct_()?;
2509        assert!(pivot.field_by_name("p".into())?.f64()?.get(0).is_some());
2510        assert!(pivot.field_by_name("r1".into())?.f64()?.get(0).is_some());
2511
2512        Ok(())
2513    }
2514
2515    #[test]
2516    fn test_polars_fractals() -> PolarsResult<()> {
2517        let df = df![
2518            "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
2519            "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
2520        ]?;
2521
2522        let out = df
2523            .lazy()
2524            .ta()
2525            .bill_williams_fractals("high", "low")
2526            .collect()?;
2527
2528        let fractals = out.column("fractals_data")?.struct_()?;
2529        assert!(
2530            fractals
2531                .field_by_name("bearish".into())?
2532                .bool()?
2533                .get(4)
2534                .unwrap()
2535        );
2536        assert!(
2537            fractals
2538                .field_by_name("bullish".into())?
2539                .bool()?
2540                .get(4)
2541                .unwrap()
2542        );
2543
2544        Ok(())
2545    }
2546
2547    #[test]
2548    fn test_polars_ichimoku() -> PolarsResult<()> {
2549        let df = df![
2550            "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
2551            "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
2552        ]?;
2553
2554        let out = df
2555            .lazy()
2556            .ta()
2557            .ichimoku_cloud("high", "low", 9, 26, 52)
2558            .collect()?;
2559
2560        let ichimoku = out.column("ichimoku_data")?.struct_()?;
2561        assert!(
2562            ichimoku
2563                .field_by_name("tenkan".into())?
2564                .f64()?
2565                .get(4)
2566                .is_some()
2567        );
2568        assert!(
2569            ichimoku
2570                .field_by_name("kijun".into())?
2571                .f64()?
2572                .get(4)
2573                .is_some()
2574        );
2575
2576        Ok(())
2577    }
2578
2579    #[test]
2580    fn test_polars_wavetrend() -> PolarsResult<()> {
2581        let df = df![
2582            "high" => [10.0, 12.0, 11.0],
2583            "low" => [8.0, 10.0, 9.0],
2584            "close" => [9.0, 11.0, 10.0]
2585        ]?;
2586
2587        let out = df
2588            .lazy()
2589            .ta()
2590            .wavetrend("high", "low", "close", 10, 21, 4)
2591            .collect()?;
2592
2593        let wt = out.column("wavetrend_data")?.struct_()?;
2594        assert!(wt.field_by_name("wt1".into())?.f64()?.get(0).is_some());
2595        assert!(wt.field_by_name("wt2".into())?.f64()?.get(0).is_some());
2596
2597        Ok(())
2598    }
2599
2600    #[test]
2601    fn test_polars_vortex() -> PolarsResult<()> {
2602        let df = df![
2603            "high" => [10.0, 12.0, 11.0],
2604            "low" => [8.0, 10.0, 9.0],
2605            "close" => [9.0, 11.0, 10.0]
2606        ]?;
2607
2608        let out = df
2609            .lazy()
2610            .ta()
2611            .vortex_indicator("high", "low", "close", 14)
2612            .collect()?;
2613
2614        let vortex = out.column("vortex_data")?.struct_()?;
2615        assert!(
2616            vortex
2617                .field_by_name("vi_plus".into())?
2618                .f64()?
2619                .get(0)
2620                .is_some()
2621        );
2622        assert!(
2623            vortex
2624                .field_by_name("vi_minus".into())?
2625                .f64()?
2626                .get(0)
2627                .is_some()
2628        );
2629
2630        Ok(())
2631    }
2632
2633    #[test]
2634    fn test_polars_ttm_squeeze() -> PolarsResult<()> {
2635        let df = df![
2636            "high" => [11.0, 12.0, 13.0, 14.0],
2637            "low" => [9.0, 10.0, 11.0, 12.0],
2638            "close" => [10.0, 11.0, 12.0, 13.0]
2639        ]?;
2640
2641        let out = df
2642            .lazy()
2643            .ta()
2644            .ttm_squeeze("high", "low", "close", 20, 2.0, 1.5)
2645            .collect()?;
2646
2647        let ttm = out.column("ttm_squeeze_data")?.struct_()?;
2648        assert!(
2649            ttm.field_by_name("histogram".into())?
2650                .f64()?
2651                .get(0)
2652                .is_some()
2653        );
2654        assert!(
2655            ttm.field_by_name("is_squeezed".into())?
2656                .bool()?
2657                .get(0)
2658                .is_some()
2659        );
2660
2661        Ok(())
2662    }
2663
2664    #[test]
2665    fn test_polars_donchian() -> PolarsResult<()> {
2666        let df = df![
2667            "high" => [10.0, 12.0, 11.0, 13.0, 15.0],
2668            "low" => [8.0, 7.0, 9.0, 10.0, 12.0]
2669        ]?;
2670
2671        let out = df
2672            .lazy()
2673            .ta()
2674            .donchian_channels("high", "low", 3)
2675            .collect()?;
2676
2677        let donchian = out.column("donchian_data")?.struct_()?;
2678        // bar 4: H=13, L=10. Window (12,7), (11,9), (13,10). Upper=13, Lower=7, Middle=10
2679        assert_eq!(
2680            donchian.field_by_name("upper".into())?.f64()?.get(3),
2681            Some(13.0)
2682        );
2683        assert_eq!(
2684            donchian.field_by_name("middle".into())?.f64()?.get(3),
2685            Some(10.0)
2686        );
2687        assert_eq!(
2688            donchian.field_by_name("lower".into())?.f64()?.get(3),
2689            Some(7.0)
2690        );
2691
2692        Ok(())
2693    }
2694
2695    #[test]
2696    fn test_polars_alma() -> PolarsResult<()> {
2697        let df = df![
2698            "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
2699        ]?;
2700
2701        let out = df.lazy().ta().alma("price", 9, 0.85, 6.0).collect()?;
2702
2703        let alma = out.column("alma")?.f64()?;
2704        assert!(alma.get(9).is_some());
2705
2706        Ok(())
2707    }
2708
2709    #[test]
2710    fn test_polars_keltner() -> PolarsResult<()> {
2711        let df = df![
2712            "high" => [12.0],
2713            "low" => [8.0],
2714            "close" => [10.0]
2715        ]?;
2716
2717        let out = df
2718            .lazy()
2719            .ta()
2720            .keltner_channels("high", "low", "close", 3, 3, 2.0)
2721            .collect()?;
2722
2723        let keltner = out.column("keltner_data")?.struct_()?;
2724        assert_eq!(
2725            keltner.field_by_name("middle".into())?.f64()?.get(0),
2726            Some(10.0)
2727        );
2728        assert_eq!(
2729            keltner.field_by_name("upper".into())?.f64()?.get(0),
2730            Some(18.0)
2731        );
2732        assert_eq!(
2733            keltner.field_by_name("lower".into())?.f64()?.get(0),
2734            Some(2.0)
2735        );
2736
2737        Ok(())
2738    }
2739
2740    #[test]
2741    fn test_polars_hma() -> PolarsResult<()> {
2742        let df = df![
2743            "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
2744        ]?;
2745
2746        let out = df.lazy().ta().hma("price", 4).collect()?;
2747
2748        let hma = out.column("hma")?.f64()?;
2749        assert!(hma.get(9).is_some());
2750
2751        Ok(())
2752    }
2753
2754    #[test]
2755    fn test_polars_anchored_vwap() -> PolarsResult<()> {
2756        let df = df![
2757            "price" => [10.0, 12.0, 15.0, 16.0],
2758            "volume" => [100.0, 200.0, 100.0, 100.0],
2759            "anchor" => [false, false, true, false]
2760        ]?;
2761
2762        let out = df
2763            .lazy()
2764            .ta()
2765            .anchored_vwap("price", "volume", "anchor")
2766            .collect()?;
2767
2768        let avwap = out.column("avwap")?.f64()?;
2769        assert_eq!(avwap.get(0), Some(10.0));
2770        assert_eq!(avwap.get(1), Some(11.333333333333334));
2771        assert_eq!(avwap.get(2), Some(15.0));
2772        assert_eq!(avwap.get(3), Some(15.5));
2773
2774        Ok(())
2775    }
2776
2777    #[test]
2778    fn test_polars_math_transforms() -> PolarsResult<()> {
2779        let df = df![
2780            "val" => [0.0, 1.5707963267948966] // 0, PI/2
2781        ]?;
2782
2783        let out = df.lazy().ta().sin("val").collect()?;
2784
2785        let sin = out.column("sin")?.f64()?;
2786        assert!((sin.get(0).unwrap() - 0.0).abs() < 1e-10);
2787        assert!((sin.get(1).unwrap() - 1.0).abs() < 1e-10);
2788
2789        Ok(())
2790    }
2791
2792    #[test]
2793    fn test_polars_math_operators() -> PolarsResult<()> {
2794        let df = df![
2795            "v1" => [10.0, 20.0],
2796            "v2" => [5.0, 30.0]
2797        ]?;
2798
2799        let out = df.lazy().ta().add("v1", "v2").ta().max("v1", 2).collect()?;
2800
2801        let add = out.column("add")?.f64()?;
2802        assert_eq!(add.get(0), Some(15.0));
2803        assert_eq!(add.get(1), Some(50.0));
2804
2805        let max = out.column("max")?.f64()?;
2806        assert_eq!(max.get(1), Some(20.0));
2807
2808        Ok(())
2809    }
2810
2811    #[test]
2812    fn test_polars_vpn() -> PolarsResult<()> {
2813        let df = df![
2814            "high" => [10.0, 11.0, 12.0],
2815            "low" => [9.0, 10.0, 11.0],
2816            "close" => [9.5, 10.5, 11.5],
2817            "volume" => [1000.0, 1100.0, 1200.0]
2818        ]?;
2819
2820        let out = df.lazy().ta().vpn("high", "low", "close", "volume", 30, 3).collect()?;
2821        let vpn = out.column("vpn")?.f64()?;
2822        assert!(vpn.get(2).is_some());
2823        Ok(())
2824    }
2825
2826    #[test]
2827    fn test_polars_gap_momentum() -> PolarsResult<()> {
2828        let df = df![
2829            "open" => [10.0, 11.0, 10.0],
2830            "close" => [10.5, 10.5, 9.5]
2831        ]?;
2832
2833        let out = df.lazy().ta().gap_momentum("open", "close", 10, 5).collect()?;
2834        let gm = out.column("gap_momentum")?.struct_()?;
2835        assert!(gm.field_by_name("gap_ratio".into())?.f64()?.get(2).is_some());
2836        assert!(gm.field_by_name("gap_signal".into())?.f64()?.get(2).is_some());
2837        Ok(())
2838    }
2839
2840    #[test]
2841    fn test_polars_autotune() -> PolarsResult<()> {
2842        let df = df![
2843            "price" => [100.0; 50]
2844        ]?;
2845
2846        let out = df.lazy().ta().autotune_filter("price", 20, 0.25).collect()?;
2847        let at = out.column("autotune")?.f64()?;
2848        assert!(at.get(49).is_some());
2849        Ok(())
2850    }
2851
2852    #[test]
2853    fn test_polars_adaptive_ema() -> PolarsResult<()> {
2854        let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
2855        let out = df.lazy().ta().adaptive_ema("h", "l", "c", 10, 2).collect()?;
2856        assert!(out.column("adaptive_ema")?.f64()?.get(2).is_some());
2857        Ok(())
2858    }
2859
2860    #[test]
2861    fn test_polars_obvm() -> PolarsResult<()> {
2862        let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
2863        let out = df.lazy().ta().obvm("h", "l", "c", "v", 10, 3).collect()?;
2864        let data = out.column("obvm_data")?.struct_()?;
2865        assert!(data.field_by_name("obvm".into())?.f64()?.get(1).is_some());
2866        Ok(())
2867    }
2868
2869    #[test]
2870    fn test_polars_vfi() -> PolarsResult<()> {
2871        let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
2872        let out = df.lazy().ta().vfi("h", "l", "c", "v", 10, 0.2, 2.5, 3).collect()?;
2873        assert!(out.column("vfi")?.f64()?.get(1).is_some());
2874        Ok(())
2875    }
2876
2877    #[test]
2878    fn test_polars_sdo() -> PolarsResult<()> {
2879        let df = df!["p" => [10.0, 11.0, 12.0]]?;
2880        let out = df.lazy().ta().sdo("p", 2, 5, 3).collect()?;
2881        assert!(out.column("sdo")?.f64()?.get(2).is_some());
2882        Ok(())
2883    }
2884
2885    #[test]
2886    fn test_polars_rsmk() -> PolarsResult<()> {
2887        let df = df!["p" => [10.0, 11.0], "b" => [100.0, 101.0]]?;
2888        let out = df.lazy().ta().rsmk("p", "b", 90, 3).collect()?;
2889        assert!(out.column("rsmk")?.f64()?.get(1).is_some());
2890        Ok(())
2891    }
2892
2893    #[test]
2894    fn test_polars_rodc() -> PolarsResult<()> {
2895        let df = df!["p" => [10.0, 11.0, 10.0, 11.0, 12.0]]?;
2896        let out = df.lazy().ta().rodc("p", 10, 0.5, 3).collect()?;
2897        assert!(out.column("rodc")?.f64()?.get(4).is_some());
2898        Ok(())
2899    }
2900
2901    #[test]
2902    fn test_polars_reverse_ema() -> PolarsResult<()> {
2903        let df = df!["p" => [10.0, 11.0, 12.0]]?;
2904        let out = df.lazy().ta().reverse_ema("p", 0.1).collect()?;
2905        assert!(out.column("reverse_ema")?.f64()?.get(2).is_some());
2906        Ok(())
2907    }
2908
2909    #[test]
2910    fn test_polars_harrington_adx() -> PolarsResult<()> {
2911        let df = df!["h" => [10.0, 11.0, 12.0], "l" => [9.0, 10.0, 11.0], "c" => [9.5, 10.5, 11.5]]?;
2912        let out = df.lazy().ta().harrington_adx("h", "l", "c", 10, 1).collect()?;
2913        assert!(out.column("harrington_adx")?.f64()?.get(2).is_some());
2914        Ok(())
2915    }
2916
2917    #[test]
2918    fn test_polars_tradj_ema() -> PolarsResult<()> {
2919        let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
2920        let out = df.lazy().ta().tradj_ema("h", "l", "c", 10, 2, 0.5).collect()?;
2921        assert!(out.column("tradj_ema")?.f64()?.get(2).is_some());
2922        Ok(())
2923    }
2924
2925    #[test]
2926    fn test_polars_sve_volatility_bands() -> PolarsResult<()> {
2927        let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
2928        let out = df.lazy().ta().sve_volatility_bands("h", "l", "c", 10, 1.5, 1.0, 3).collect()?;
2929        let data = out.column("sve_bands_data")?.struct_()?;
2930        assert!(data.field_by_name("upper".into())?.f64()?.get(2).is_some());
2931        Ok(())
2932    }
2933
2934    #[test]
2935    fn test_polars_exp_dev_bands() -> PolarsResult<()> {
2936        let df = df!["p" => [10.0, 11.0, 12.0, 11.0, 10.0]]?;
2937        let out = df.lazy().ta().exp_dev_bands("p", 10, 2.0, true).collect()?;
2938        let data = out.column("exp_dev_bands_data")?.struct_()?;
2939        assert!(data.field_by_name("upper".into())?.f64()?.get(4).is_some());
2940        Ok(())
2941    }
2942}
2943
2944impl QuantWaveExt for LazyFrame {
2945    fn ta(&self) -> QuantWaveNamespace<'_> {
2946        QuantWaveNamespace(self)
2947    }
2948}