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