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 keltner_channels(
1209        self,
1210        high: &str,
1211        low: &str,
1212        close: &str,
1213        ema_period: usize,
1214        atr_period: usize,
1215        multiplier: f64,
1216    ) -> LazyFrame {
1217        let high = high.to_string();
1218        let low = low.to_string();
1219        let close = close.to_string();
1220
1221        self.0
1222            .clone()
1223            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1224                .map(
1225                    move |s| {
1226                        let ca = s.struct_()?;
1227                        let s_high = ca.field_by_name(&high)?;
1228                        let s_low = ca.field_by_name(&low)?;
1229                        let s_close = ca.field_by_name(&close)?;
1230
1231                        let high = s_high.f64()?;
1232                        let low = s_low.f64()?;
1233                        let close = s_close.f64()?;
1234
1235                        let mut kc = quantwave_core::KeltnerChannels::new(
1236                            ema_period, atr_period, multiplier,
1237                        );
1238                        let mut uppers = Vec::with_capacity(s.len());
1239                        let mut middles = Vec::with_capacity(s.len());
1240                        let mut lowers = Vec::with_capacity(s.len());
1241
1242                        for i in 0..s.len() {
1243                            let h = high.get(i).unwrap_or(0.0);
1244                            let l = low.get(i).unwrap_or(0.0);
1245                            let c = close.get(i).unwrap_or(0.0);
1246                            let (upper, middle, lower) = kc.next((h, l, c));
1247                            uppers.push(upper);
1248                            middles.push(middle);
1249                            lowers.push(lower);
1250                        }
1251
1252                        let upper_series = Series::new("upper".into(), uppers);
1253                        let middle_series = Series::new("middle".into(), middles);
1254                        let lower_series = Series::new("lower".into(), lowers);
1255
1256                        let out = StructChunked::from_series(
1257                            "keltner_output".into(),
1258                            s.len(),
1259                            [upper_series, middle_series, lower_series].iter(),
1260                        )?;
1261                        Ok(Some(Column::from(out.into_series())))
1262                    },
1263                    GetOutput::from_type(DataType::Struct(vec![
1264                        Field::new("upper".into(), DataType::Float64),
1265                        Field::new("middle".into(), DataType::Float64),
1266                        Field::new("lower".into(), DataType::Float64),
1267                    ])),
1268                )
1269                .alias("keltner_data")])
1270    }
1271
1272    pub fn alma(self, name: &str, period: usize, offset: f64, sigma: f64) -> LazyFrame {
1273        let name = name.to_string();
1274        self.0.clone().with_columns([col(&name)
1275            .map(
1276                move |s| {
1277                    let ca = s.f64()?;
1278                    let mut alma = quantwave_core::ALMA::new(period, offset, sigma);
1279                    let mut values = Vec::with_capacity(s.len());
1280
1281                    for i in 0..s.len() {
1282                        let val = ca.get(i).unwrap_or(0.0);
1283                        values.push(alma.next(val));
1284                    }
1285
1286                    Ok(Some(Column::from(Series::new("alma".into(), values))))
1287                },
1288                GetOutput::from_type(DataType::Float64),
1289            )
1290            .alias("alma")])
1291    }
1292
1293    pub fn donchian_channels(self, high: &str, low: &str, period: usize) -> LazyFrame {
1294        let high = high.to_string();
1295        let low = low.to_string();
1296
1297        self.0
1298            .clone()
1299            .with_columns([as_struct(vec![col(&high), col(&low)])
1300                .map(
1301                    move |s| {
1302                        let ca = s.struct_()?;
1303                        let s_high = ca.field_by_name(&high)?;
1304                        let s_low = ca.field_by_name(&low)?;
1305
1306                        let high = s_high.f64()?;
1307                        let low = s_low.f64()?;
1308
1309                        let mut dc = quantwave_core::DonchianChannels::new(period);
1310                        let mut uppers = Vec::with_capacity(s.len());
1311                        let mut middles = Vec::with_capacity(s.len());
1312                        let mut lowers = Vec::with_capacity(s.len());
1313
1314                        for i in 0..s.len() {
1315                            let h = high.get(i).unwrap_or(0.0);
1316                            let l = low.get(i).unwrap_or(0.0);
1317                            let (upper, middle, lower) = dc.next((h, l));
1318                            uppers.push(upper);
1319                            middles.push(middle);
1320                            lowers.push(lower);
1321                        }
1322
1323                        let upper_series = Series::new("upper".into(), uppers);
1324                        let middle_series = Series::new("middle".into(), middles);
1325                        let lower_series = Series::new("lower".into(), lowers);
1326
1327                        let out = StructChunked::from_series(
1328                            "donchian_output".into(),
1329                            s.len(),
1330                            [upper_series, middle_series, lower_series].iter(),
1331                        )?;
1332                        Ok(Some(Column::from(out.into_series())))
1333                    },
1334                    GetOutput::from_type(DataType::Struct(vec![
1335                        Field::new("upper".into(), DataType::Float64),
1336                        Field::new("middle".into(), DataType::Float64),
1337                        Field::new("lower".into(), DataType::Float64),
1338                    ])),
1339                )
1340                .alias("donchian_data")])
1341    }
1342
1343    pub fn ttm_squeeze(
1344        self,
1345        high: &str,
1346        low: &str,
1347        close: &str,
1348        period: usize,
1349        multiplier_bb: f64,
1350        multiplier_kc: f64,
1351    ) -> LazyFrame {
1352        let high = high.to_string();
1353        let low = low.to_string();
1354        let close = close.to_string();
1355
1356        self.0
1357            .clone()
1358            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1359                .map(
1360                    move |s| {
1361                        let ca = s.struct_()?;
1362                        let s_high = ca.field_by_name(&high)?;
1363                        let s_low = ca.field_by_name(&low)?;
1364                        let s_close = ca.field_by_name(&close)?;
1365
1366                        let high = s_high.f64()?;
1367                        let low = s_low.f64()?;
1368                        let close = s_close.f64()?;
1369
1370                        let mut ttm =
1371                            quantwave_core::TTMSqueeze::new(period, multiplier_bb, multiplier_kc);
1372                        let mut histograms = Vec::with_capacity(s.len());
1373                        let mut squeezed = Vec::with_capacity(s.len());
1374
1375                        for i in 0..s.len() {
1376                            let h = high.get(i).unwrap_or(0.0);
1377                            let l = low.get(i).unwrap_or(0.0);
1378                            let c = close.get(i).unwrap_or(0.0);
1379                            let (hist, is_sq) = ttm.next((h, l, c));
1380                            histograms.push(hist);
1381                            squeezed.push(is_sq);
1382                        }
1383
1384                        let hist_series = Series::new("histogram".into(), histograms);
1385                        let squeezed_series = Series::new("is_squeezed".into(), squeezed);
1386
1387                        let out = StructChunked::from_series(
1388                            "ttm_squeeze_output".into(),
1389                            s.len(),
1390                            [hist_series, squeezed_series].iter(),
1391                        )?;
1392                        Ok(Some(Column::from(out.into_series())))
1393                    },
1394                    GetOutput::from_type(DataType::Struct(vec![
1395                        Field::new("histogram".into(), DataType::Float64),
1396                        Field::new("is_squeezed".into(), DataType::Boolean),
1397                    ])),
1398                )
1399                .alias("ttm_squeeze_data")])
1400    }
1401
1402    pub fn vortex_indicator(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
1403        let high = high.to_string();
1404        let low = low.to_string();
1405        let close = close.to_string();
1406
1407        self.0
1408            .clone()
1409            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1410                .map(
1411                    move |s| {
1412                        let ca = s.struct_()?;
1413                        let s_high = ca.field_by_name(&high)?;
1414                        let s_low = ca.field_by_name(&low)?;
1415                        let s_close = ca.field_by_name(&close)?;
1416
1417                        let high = s_high.f64()?;
1418                        let low = s_low.f64()?;
1419                        let close = s_close.f64()?;
1420
1421                        let mut vi = quantwave_core::VortexIndicator::new(period);
1422                        let mut plus_vals = Vec::with_capacity(s.len());
1423                        let mut minus_vals = Vec::with_capacity(s.len());
1424
1425                        for i in 0..s.len() {
1426                            let h = high.get(i).unwrap_or(0.0);
1427                            let l = low.get(i).unwrap_or(0.0);
1428                            let c = close.get(i).unwrap_or(0.0);
1429                            let (plus, minus) = vi.next((h, l, c));
1430                            plus_vals.push(plus);
1431                            minus_vals.push(minus);
1432                        }
1433
1434                        let plus_series = Series::new("vi_plus".into(), plus_vals);
1435                        let minus_series = Series::new("vi_minus".into(), minus_vals);
1436
1437                        let out = StructChunked::from_series(
1438                            "vortex_output".into(),
1439                            s.len(),
1440                            [plus_series, minus_series].iter(),
1441                        )?;
1442                        Ok(Some(Column::from(out.into_series())))
1443                    },
1444                    GetOutput::from_type(DataType::Struct(vec![
1445                        Field::new("vi_plus".into(), DataType::Float64),
1446                        Field::new("vi_minus".into(), DataType::Float64),
1447                    ])),
1448                )
1449                .alias("vortex_data")])
1450    }
1451
1452    pub fn heikin_ashi(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
1453        let open = open.to_string();
1454        let high = high.to_string();
1455        let low = low.to_string();
1456        let close = close.to_string();
1457
1458        self.0.clone().with_columns([as_struct(vec![
1459            col(&open),
1460            col(&high),
1461            col(&low),
1462            col(&close),
1463        ])
1464        .map(
1465            move |s| {
1466                let ca = s.struct_()?;
1467                let s_open = ca.field_by_name(&open)?;
1468                let s_high = ca.field_by_name(&high)?;
1469                let s_low = ca.field_by_name(&low)?;
1470                let s_close = ca.field_by_name(&close)?;
1471
1472                let open = s_open.f64()?;
1473                let high = s_high.f64()?;
1474                let low = s_low.f64()?;
1475                let close = s_close.f64()?;
1476
1477                let mut ha = quantwave_core::HeikinAshi::new();
1478                let mut ha_opens = Vec::with_capacity(s.len());
1479                let mut ha_highs = Vec::with_capacity(s.len());
1480                let mut ha_lows = Vec::with_capacity(s.len());
1481                let mut ha_closes = Vec::with_capacity(s.len());
1482
1483                for i in 0..s.len() {
1484                    let o = open.get(i).unwrap_or(0.0);
1485                    let h = high.get(i).unwrap_or(0.0);
1486                    let l = low.get(i).unwrap_or(0.0);
1487                    let c = close.get(i).unwrap_or(0.0);
1488                    let (ha_o, ha_h, ha_l, ha_c) = ha.next((o, h, l, c));
1489                    ha_opens.push(ha_o);
1490                    ha_highs.push(ha_h);
1491                    ha_lows.push(ha_l);
1492                    ha_closes.push(ha_c);
1493                }
1494
1495                let o_series = Series::new("ha_open".into(), ha_opens);
1496                let h_series = Series::new("ha_high".into(), ha_highs);
1497                let l_series = Series::new("ha_low".into(), ha_lows);
1498                let c_series = Series::new("ha_close".into(), ha_closes);
1499
1500                let out = StructChunked::from_series(
1501                    "heikin_ashi_output".into(),
1502                    s.len(),
1503                    [o_series, h_series, l_series, c_series].iter(),
1504                )?;
1505                Ok(Some(Column::from(out.into_series())))
1506            },
1507            GetOutput::from_type(DataType::Struct(vec![
1508                Field::new("ha_open".into(), DataType::Float64),
1509                Field::new("ha_high".into(), DataType::Float64),
1510                Field::new("ha_low".into(), DataType::Float64),
1511                Field::new("ha_close".into(), DataType::Float64),
1512            ])),
1513        )
1514        .alias("heikin_ashi_data")])
1515    }
1516
1517    pub fn wavetrend(
1518        self,
1519        high: &str,
1520        low: &str,
1521        close: &str,
1522        n1: usize,
1523        n2: usize,
1524        n3: usize,
1525    ) -> LazyFrame {
1526        let high = high.to_string();
1527        let low = low.to_string();
1528        let close = close.to_string();
1529
1530        self.0
1531            .clone()
1532            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1533                .map(
1534                    move |s| {
1535                        let ca = s.struct_()?;
1536                        let s_high = ca.field_by_name(&high)?;
1537                        let s_low = ca.field_by_name(&low)?;
1538                        let s_close = ca.field_by_name(&close)?;
1539
1540                        let high = s_high.f64()?;
1541                        let low = s_low.f64()?;
1542                        let close = s_close.f64()?;
1543
1544                        let mut wt = quantwave_core::WaveTrend::new(n1, n2, n3);
1545                        let mut wt1_vals = Vec::with_capacity(s.len());
1546                        let mut wt2_vals = Vec::with_capacity(s.len());
1547
1548                        for i in 0..s.len() {
1549                            let h = high.get(i).unwrap_or(0.0);
1550                            let l = low.get(i).unwrap_or(0.0);
1551                            let c = close.get(i).unwrap_or(0.0);
1552                            let (wt1, wt2) = wt.next((h, l, c));
1553                            wt1_vals.push(wt1);
1554                            wt2_vals.push(wt2);
1555                        }
1556
1557                        let wt1_series = Series::new("wt1".into(), wt1_vals);
1558                        let wt2_series = Series::new("wt2".into(), wt2_vals);
1559
1560                        let out = StructChunked::from_series(
1561                            "wavetrend_output".into(),
1562                            s.len(),
1563                            [wt1_series, wt2_series].iter(),
1564                        )?;
1565                        Ok(Some(Column::from(out.into_series())))
1566                    },
1567                    GetOutput::from_type(DataType::Struct(vec![
1568                        Field::new("wt1".into(), DataType::Float64),
1569                        Field::new("wt2".into(), DataType::Float64),
1570                    ])),
1571                )
1572                .alias("wavetrend_data")])
1573    }
1574
1575    pub fn tema(self, name: &str, period: usize) -> LazyFrame {
1576        let name = name.to_string();
1577        self.0.clone().with_columns([col(&name)
1578            .map(
1579                move |s| {
1580                    let ca = s.f64()?;
1581                    let mut tema = quantwave_core::TEMA::new(period);
1582                    let mut values = Vec::with_capacity(s.len());
1583
1584                    for i in 0..s.len() {
1585                        let val = ca.get(i).unwrap_or(0.0);
1586                        values.push(tema.next(val));
1587                    }
1588
1589                    Ok(Some(Column::from(Series::new("tema".into(), values))))
1590                },
1591                GetOutput::from_type(DataType::Float64),
1592            )
1593            .alias("tema")])
1594    }
1595
1596    pub fn zlema(self, name: &str, period: usize) -> LazyFrame {
1597        let name = name.to_string();
1598        self.0.clone().with_columns([col(&name)
1599            .map(
1600                move |s| {
1601                    let ca = s.f64()?;
1602                    let mut zlema = quantwave_core::ZLEMA::new(period);
1603                    let mut values = Vec::with_capacity(s.len());
1604
1605                    for i in 0..s.len() {
1606                        let val = ca.get(i).unwrap_or(0.0);
1607                        values.push(zlema.next(val));
1608                    }
1609
1610                    Ok(Some(Column::from(Series::new("zlema".into(), values))))
1611                },
1612                GetOutput::from_type(DataType::Float64),
1613            )
1614            .alias("zlema")])
1615    }
1616
1617    pub fn atr_trailing_stop(
1618        self,
1619        high: &str,
1620        low: &str,
1621        close: &str,
1622        period: usize,
1623        multiplier: f64,
1624    ) -> LazyFrame {
1625        let high = high.to_string();
1626        let low = low.to_string();
1627        let close = close.to_string();
1628
1629        self.0
1630            .clone()
1631            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1632                .map(
1633                    move |s| {
1634                        let ca = s.struct_()?;
1635                        let s_high = ca.field_by_name(&high)?;
1636                        let s_low = ca.field_by_name(&low)?;
1637                        let s_close = ca.field_by_name(&close)?;
1638
1639                        let high = s_high.f64()?;
1640                        let low = s_low.f64()?;
1641                        let close = s_close.f64()?;
1642
1643                        let mut atr_ts = quantwave_core::ATRTrailingStop::new(period, multiplier);
1644                        let mut stops = Vec::with_capacity(s.len());
1645                        let mut directions = Vec::with_capacity(s.len());
1646
1647                        for i in 0..s.len() {
1648                            let h = high.get(i).unwrap_or(0.0);
1649                            let l = low.get(i).unwrap_or(0.0);
1650                            let c = close.get(i).unwrap_or(0.0);
1651                            let (stop, dir) = atr_ts.next((h, l, c));
1652                            stops.push(stop);
1653                            directions.push(dir as f64);
1654                        }
1655
1656                        let stop_series = Series::new("stop".into(), stops);
1657                        let dir_series = Series::new("direction".into(), directions);
1658
1659                        let out = StructChunked::from_series(
1660                            "atr_ts_output".into(),
1661                            s.len(),
1662                            [stop_series, dir_series].iter(),
1663                        )?;
1664                        Ok(Some(Column::from(out.into_series())))
1665                    },
1666                    GetOutput::from_type(DataType::Struct(vec![
1667                        Field::new("stop".into(), DataType::Float64),
1668                        Field::new("direction".into(), DataType::Float64),
1669                    ])),
1670                )
1671                .alias("atr_ts_data")])
1672    }
1673
1674    pub fn pivot_points(self, high: &str, low: &str, close: &str) -> LazyFrame {
1675        let high = high.to_string();
1676        let low = low.to_string();
1677        let close = close.to_string();
1678
1679        self.0
1680            .clone()
1681            .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
1682                .map(
1683                    move |s| {
1684                        let ca = s.struct_()?;
1685                        let s_high = ca.field_by_name(&high)?;
1686                        let s_low = ca.field_by_name(&low)?;
1687                        let s_close = ca.field_by_name(&close)?;
1688
1689                        let high = s_high.f64()?;
1690                        let low = s_low.f64()?;
1691                        let close = s_close.f64()?;
1692
1693                        let mut pivot = quantwave_core::PivotPoints::new();
1694                        let mut p_vals = Vec::with_capacity(s.len());
1695                        let mut r1_vals = Vec::with_capacity(s.len());
1696                        let mut s1_vals = Vec::with_capacity(s.len());
1697                        let mut r2_vals = Vec::with_capacity(s.len());
1698                        let mut s2_vals = Vec::with_capacity(s.len());
1699
1700                        for i in 0..s.len() {
1701                            let h = high.get(i).unwrap_or(0.0);
1702                            let l = low.get(i).unwrap_or(0.0);
1703                            let c = close.get(i).unwrap_or(0.0);
1704                            let (p, r1, s1, r2, s2) = pivot.next((h, l, c));
1705                            p_vals.push(p);
1706                            r1_vals.push(r1);
1707                            s1_vals.push(s1);
1708                            r2_vals.push(r2);
1709                            s2_vals.push(s2);
1710                        }
1711
1712                        let p_series = Series::new("p".into(), p_vals);
1713                        let r1_series = Series::new("r1".into(), r1_vals);
1714                        let s1_series = Series::new("s1".into(), s1_vals);
1715                        let r2_series = Series::new("r2".into(), r2_vals);
1716                        let s2_series = Series::new("s2".into(), s2_vals);
1717
1718                        let out = StructChunked::from_series(
1719                            "pivot_output".into(),
1720                            s.len(),
1721                            [p_series, r1_series, s1_series, r2_series, s2_series].iter(),
1722                        )?;
1723                        Ok(Some(Column::from(out.into_series())))
1724                    },
1725                    GetOutput::from_type(DataType::Struct(vec![
1726                        Field::new("p".into(), DataType::Float64),
1727                        Field::new("r1".into(), DataType::Float64),
1728                        Field::new("s1".into(), DataType::Float64),
1729                        Field::new("r2".into(), DataType::Float64),
1730                        Field::new("s2".into(), DataType::Float64),
1731                    ])),
1732                )
1733                .alias("pivot_points_data")])
1734    }
1735
1736    pub fn bill_williams_fractals(self, high: &str, low: &str) -> LazyFrame {
1737        let high = high.to_string();
1738        let low = low.to_string();
1739
1740        self.0
1741            .clone()
1742            .with_columns([as_struct(vec![col(&high), col(&low)])
1743                .map(
1744                    move |s| {
1745                        let ca = s.struct_()?;
1746                        let s_high = ca.field_by_name(&high)?;
1747                        let s_low = ca.field_by_name(&low)?;
1748
1749                        let high = s_high.f64()?;
1750                        let low = s_low.f64()?;
1751
1752                        let mut fractals = quantwave_core::BillWilliamsFractals::new();
1753                        let mut bearish_vals = Vec::with_capacity(s.len());
1754                        let mut bullish_vals = Vec::with_capacity(s.len());
1755
1756                        for i in 0..s.len() {
1757                            let h = high.get(i).unwrap_or(0.0);
1758                            let l = low.get(i).unwrap_or(0.0);
1759                            let (bear, bull) = fractals.next((h, l));
1760                            bearish_vals.push(bear);
1761                            bullish_vals.push(bull);
1762                        }
1763
1764                        let bearish_series = Series::new("bearish".into(), bearish_vals);
1765                        let bullish_series = Series::new("bullish".into(), bullish_vals);
1766
1767                        let out = StructChunked::from_series(
1768                            "fractals_output".into(),
1769                            s.len(),
1770                            [bearish_series, bullish_series].iter(),
1771                        )?;
1772                        Ok(Some(Column::from(out.into_series())))
1773                    },
1774                    GetOutput::from_type(DataType::Struct(vec![
1775                        Field::new("bearish".into(), DataType::Boolean),
1776                        Field::new("bullish".into(), DataType::Boolean),
1777                    ])),
1778                )
1779                .alias("fractals_data")])
1780    }
1781
1782    pub fn ichimoku_cloud(
1783        self,
1784        high: &str,
1785        low: &str,
1786        p1: usize,
1787        p2: usize,
1788        p3: usize,
1789    ) -> LazyFrame {
1790        let high = high.to_string();
1791        let low = low.to_string();
1792
1793        self.0
1794            .clone()
1795            .with_columns([as_struct(vec![col(&high), col(&low)])
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
1802                        let high = s_high.f64()?;
1803                        let low = s_low.f64()?;
1804
1805                        let mut ic = quantwave_core::IchimokuCloud::new(p1, p2, p3);
1806                        let mut t_vals = Vec::with_capacity(s.len());
1807                        let mut k_vals = Vec::with_capacity(s.len());
1808                        let mut sa_vals = Vec::with_capacity(s.len());
1809                        let mut sb_vals = Vec::with_capacity(s.len());
1810
1811                        for i in 0..s.len() {
1812                            let h = high.get(i).unwrap_or(0.0);
1813                            let l = low.get(i).unwrap_or(0.0);
1814                            let (t, k, sa, sb) = ic.next((h, l));
1815                            t_vals.push(t);
1816                            k_vals.push(k);
1817                            sa_vals.push(sa);
1818                            sb_vals.push(sb);
1819                        }
1820
1821                        let t_series = Series::new("tenkan".into(), t_vals);
1822                        let k_series = Series::new("kijun".into(), k_vals);
1823                        let sa_series = Series::new("senkou_a".into(), sa_vals);
1824                        let sb_series = Series::new("senkou_b".into(), sb_vals);
1825
1826                        let out = StructChunked::from_series(
1827                            "ichimoku_output".into(),
1828                            s.len(),
1829                            [t_series, k_series, sa_series, sb_series].iter(),
1830                        )?;
1831                        Ok(Some(Column::from(out.into_series())))
1832                    },
1833                    GetOutput::from_type(DataType::Struct(vec![
1834                        Field::new("tenkan".into(), DataType::Float64),
1835                        Field::new("kijun".into(), DataType::Float64),
1836                        Field::new("senkou_a".into(), DataType::Float64),
1837                        Field::new("senkou_b".into(), DataType::Float64),
1838                    ])),
1839                )
1840                .alias("ichimoku_data")])
1841    }
1842}
1843
1844#[cfg(test)]
1845mod tests {
1846    use super::*;
1847
1848    #[test]
1849    fn test_polars_heikin_ashi() -> PolarsResult<()> {
1850        let df = df![
1851            "open" => [10.0, 11.0],
1852            "high" => [12.0, 13.0],
1853            "low" => [8.0, 10.0],
1854            "close" => [11.0, 12.0]
1855        ]?;
1856
1857        let out = df
1858            .lazy()
1859            .ta()
1860            .heikin_ashi("open", "high", "low", "close")
1861            .collect()?;
1862
1863        let ha = out.column("heikin_ashi_data")?.struct_()?;
1864        assert_eq!(
1865            ha.field_by_name("ha_open".into())?.f64()?.get(0),
1866            Some(10.5)
1867        );
1868        assert_eq!(
1869            ha.field_by_name("ha_close".into())?.f64()?.get(0),
1870            Some(10.25)
1871        );
1872
1873        Ok(())
1874    }
1875
1876    #[test]
1877    fn test_polars_tema_zlema() -> PolarsResult<()> {
1878        let df = df![
1879            "price" => [1.0, 2.0, 3.0, 4.0, 5.0]
1880        ]?;
1881
1882        let out = df.clone().lazy().ta().tema("price", 3).collect()?;
1883
1884        let tema = out.column("tema")?.f64()?;
1885        assert!(tema.get(4).is_some());
1886
1887        let out2 = df.lazy().ta().zlema("price", 3).collect()?;
1888
1889        let zlema = out2.column("zlema")?.f64()?;
1890        assert!(zlema.get(4).is_some());
1891
1892        Ok(())
1893    }
1894
1895    #[test]
1896    fn test_polars_atr_ts() -> PolarsResult<()> {
1897        let df = df![
1898            "high" => [10.0, 12.0, 11.0],
1899            "low" => [8.0, 10.0, 9.0],
1900            "close" => [9.0, 11.0, 10.0]
1901        ]?;
1902
1903        let out = df
1904            .lazy()
1905            .ta()
1906            .atr_trailing_stop("high", "low", "close", 14, 2.5)
1907            .collect()?;
1908
1909        let atr_ts = out.column("atr_ts_data")?.struct_()?;
1910        assert!(atr_ts.field_by_name("stop".into())?.f64()?.get(0).is_some());
1911        assert!(
1912            atr_ts
1913                .field_by_name("direction".into())?
1914                .f64()?
1915                .get(0)
1916                .is_some()
1917        );
1918
1919        Ok(())
1920    }
1921
1922    #[test]
1923    fn test_polars_pivot_points() -> PolarsResult<()> {
1924        let df = df![
1925            "high" => [10.0, 12.0, 11.0],
1926            "low" => [8.0, 10.0, 9.0],
1927            "close" => [9.0, 11.0, 10.0]
1928        ]?;
1929
1930        let out = df
1931            .lazy()
1932            .ta()
1933            .pivot_points("high", "low", "close")
1934            .collect()?;
1935
1936        let pivot = out.column("pivot_points_data")?.struct_()?;
1937        assert!(pivot.field_by_name("p".into())?.f64()?.get(0).is_some());
1938        assert!(pivot.field_by_name("r1".into())?.f64()?.get(0).is_some());
1939
1940        Ok(())
1941    }
1942
1943    #[test]
1944    fn test_polars_fractals() -> PolarsResult<()> {
1945        let df = df![
1946            "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
1947            "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
1948        ]?;
1949
1950        let out = df
1951            .lazy()
1952            .ta()
1953            .bill_williams_fractals("high", "low")
1954            .collect()?;
1955
1956        let fractals = out.column("fractals_data")?.struct_()?;
1957        assert!(
1958            fractals
1959                .field_by_name("bearish".into())?
1960                .bool()?
1961                .get(4)
1962                .unwrap()
1963        );
1964        assert!(
1965            fractals
1966                .field_by_name("bullish".into())?
1967                .bool()?
1968                .get(4)
1969                .unwrap()
1970        );
1971
1972        Ok(())
1973    }
1974
1975    #[test]
1976    fn test_polars_ichimoku() -> PolarsResult<()> {
1977        let df = df![
1978            "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
1979            "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
1980        ]?;
1981
1982        let out = df
1983            .lazy()
1984            .ta()
1985            .ichimoku_cloud("high", "low", 9, 26, 52)
1986            .collect()?;
1987
1988        let ichimoku = out.column("ichimoku_data")?.struct_()?;
1989        assert!(
1990            ichimoku
1991                .field_by_name("tenkan".into())?
1992                .f64()?
1993                .get(4)
1994                .is_some()
1995        );
1996        assert!(
1997            ichimoku
1998                .field_by_name("kijun".into())?
1999                .f64()?
2000                .get(4)
2001                .is_some()
2002        );
2003
2004        Ok(())
2005    }
2006
2007    #[test]
2008    fn test_polars_wavetrend() -> PolarsResult<()> {
2009        let df = df![
2010            "high" => [10.0, 12.0, 11.0],
2011            "low" => [8.0, 10.0, 9.0],
2012            "close" => [9.0, 11.0, 10.0]
2013        ]?;
2014
2015        let out = df
2016            .lazy()
2017            .ta()
2018            .wavetrend("high", "low", "close", 10, 21, 4)
2019            .collect()?;
2020
2021        let wt = out.column("wavetrend_data")?.struct_()?;
2022        assert!(wt.field_by_name("wt1".into())?.f64()?.get(0).is_some());
2023        assert!(wt.field_by_name("wt2".into())?.f64()?.get(0).is_some());
2024
2025        Ok(())
2026    }
2027
2028    #[test]
2029    fn test_polars_vortex() -> PolarsResult<()> {
2030        let df = df![
2031            "high" => [10.0, 12.0, 11.0],
2032            "low" => [8.0, 10.0, 9.0],
2033            "close" => [9.0, 11.0, 10.0]
2034        ]?;
2035
2036        let out = df
2037            .lazy()
2038            .ta()
2039            .vortex_indicator("high", "low", "close", 14)
2040            .collect()?;
2041
2042        let vortex = out.column("vortex_data")?.struct_()?;
2043        assert!(
2044            vortex
2045                .field_by_name("vi_plus".into())?
2046                .f64()?
2047                .get(0)
2048                .is_some()
2049        );
2050        assert!(
2051            vortex
2052                .field_by_name("vi_minus".into())?
2053                .f64()?
2054                .get(0)
2055                .is_some()
2056        );
2057
2058        Ok(())
2059    }
2060
2061    #[test]
2062    fn test_polars_ttm_squeeze() -> PolarsResult<()> {
2063        let df = df![
2064            "high" => [11.0, 12.0, 13.0, 14.0],
2065            "low" => [9.0, 10.0, 11.0, 12.0],
2066            "close" => [10.0, 11.0, 12.0, 13.0]
2067        ]?;
2068
2069        let out = df
2070            .lazy()
2071            .ta()
2072            .ttm_squeeze("high", "low", "close", 20, 2.0, 1.5)
2073            .collect()?;
2074
2075        let ttm = out.column("ttm_squeeze_data")?.struct_()?;
2076        assert!(
2077            ttm.field_by_name("histogram".into())?
2078                .f64()?
2079                .get(0)
2080                .is_some()
2081        );
2082        assert!(
2083            ttm.field_by_name("is_squeezed".into())?
2084                .bool()?
2085                .get(0)
2086                .is_some()
2087        );
2088
2089        Ok(())
2090    }
2091
2092    #[test]
2093    fn test_polars_donchian() -> PolarsResult<()> {
2094        let df = df![
2095            "high" => [10.0, 12.0, 11.0, 13.0, 15.0],
2096            "low" => [8.0, 7.0, 9.0, 10.0, 12.0]
2097        ]?;
2098
2099        let out = df
2100            .lazy()
2101            .ta()
2102            .donchian_channels("high", "low", 3)
2103            .collect()?;
2104
2105        let donchian = out.column("donchian_data")?.struct_()?;
2106        // bar 4: H=13, L=10. Window (12,7), (11,9), (13,10). Upper=13, Lower=7, Middle=10
2107        assert_eq!(
2108            donchian.field_by_name("upper".into())?.f64()?.get(3),
2109            Some(13.0)
2110        );
2111        assert_eq!(
2112            donchian.field_by_name("middle".into())?.f64()?.get(3),
2113            Some(10.0)
2114        );
2115        assert_eq!(
2116            donchian.field_by_name("lower".into())?.f64()?.get(3),
2117            Some(7.0)
2118        );
2119
2120        Ok(())
2121    }
2122
2123    #[test]
2124    fn test_polars_alma() -> PolarsResult<()> {
2125        let df = df![
2126            "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
2127        ]?;
2128
2129        let out = df.lazy().ta().alma("price", 9, 0.85, 6.0).collect()?;
2130
2131        let alma = out.column("alma")?.f64()?;
2132        assert!(alma.get(9).is_some());
2133
2134        Ok(())
2135    }
2136
2137    #[test]
2138    fn test_polars_keltner() -> PolarsResult<()> {
2139        let df = df![
2140            "high" => [12.0],
2141            "low" => [8.0],
2142            "close" => [10.0]
2143        ]?;
2144
2145        let out = df
2146            .lazy()
2147            .ta()
2148            .keltner_channels("high", "low", "close", 3, 3, 2.0)
2149            .collect()?;
2150
2151        let keltner = out.column("keltner_data")?.struct_()?;
2152        assert_eq!(
2153            keltner.field_by_name("middle".into())?.f64()?.get(0),
2154            Some(10.0)
2155        );
2156        assert_eq!(
2157            keltner.field_by_name("upper".into())?.f64()?.get(0),
2158            Some(18.0)
2159        );
2160        assert_eq!(
2161            keltner.field_by_name("lower".into())?.f64()?.get(0),
2162            Some(2.0)
2163        );
2164
2165        Ok(())
2166    }
2167
2168    #[test]
2169    fn test_polars_hma() -> PolarsResult<()> {
2170        let df = df![
2171            "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
2172        ]?;
2173
2174        let out = df.lazy().ta().hma("price", 4).collect()?;
2175
2176        let hma = out.column("hma")?.f64()?;
2177        assert!(hma.get(9).is_some());
2178
2179        Ok(())
2180    }
2181
2182    #[test]
2183    fn test_polars_anchored_vwap() -> PolarsResult<()> {
2184        let df = df![
2185            "price" => [10.0, 12.0, 15.0, 16.0],
2186            "volume" => [100.0, 200.0, 100.0, 100.0],
2187            "anchor" => [false, false, true, false]
2188        ]?;
2189
2190        let out = df
2191            .lazy()
2192            .ta()
2193            .anchored_vwap("price", "volume", "anchor")
2194            .collect()?;
2195
2196        let avwap = out.column("avwap")?.f64()?;
2197        assert_eq!(avwap.get(0), Some(10.0));
2198        assert_eq!(avwap.get(1), Some(11.333333333333334));
2199        assert_eq!(avwap.get(2), Some(15.0));
2200        assert_eq!(avwap.get(3), Some(15.5));
2201
2202        Ok(())
2203    }
2204
2205    #[test]
2206    fn test_polars_math_transforms() -> PolarsResult<()> {
2207        let df = df![
2208            "val" => [0.0, 1.5707963267948966] // 0, PI/2
2209        ]?;
2210
2211        let out = df.lazy().ta().sin("val").collect()?;
2212
2213        let sin = out.column("sin")?.f64()?;
2214        assert!((sin.get(0).unwrap() - 0.0).abs() < 1e-10);
2215        assert!((sin.get(1).unwrap() - 1.0).abs() < 1e-10);
2216
2217        Ok(())
2218    }
2219
2220    #[test]
2221    fn test_polars_math_operators() -> PolarsResult<()> {
2222        let df = df![
2223            "v1" => [10.0, 20.0],
2224            "v2" => [5.0, 30.0]
2225        ]?;
2226
2227        let out = df.lazy().ta().add("v1", "v2").ta().max("v1", 2).collect()?;
2228
2229        let add = out.column("add")?.f64()?;
2230        assert_eq!(add.get(0), Some(15.0));
2231        assert_eq!(add.get(1), Some(50.0));
2232
2233        let max = out.column("max")?.f64()?;
2234        assert_eq!(max.get(1), Some(20.0));
2235
2236        Ok(())
2237    }
2238}
2239
2240impl QuantWaveExt for LazyFrame {
2241    fn ta(&self) -> QuantWaveNamespace<'_> {
2242        QuantWaveNamespace(self)
2243    }
2244}