1use polars::prelude::*;
2use quantwave_core::traits::Next;
3use quantwave_core::*;
4
5pub mod bt;
6pub mod features; pub mod prelude {
9 pub use crate::bt::{BtNamespace, BtOptions, QuantWaveBtExt};
10 pub use quantwave_backtest::{run_param_sweep, single_param_variants, SweepVariant};
11 pub use crate::{QuantWaveExt, QuantWaveNamespace};
12 pub use crate::features::TaFeaturesNamespace; }
14
15pub trait QuantWaveExt {
16 fn ta(&self) -> QuantWaveNamespace<'_>;
17}
18
19pub struct QuantWaveNamespace<'a>(&'a LazyFrame);
20
21impl<'a> QuantWaveNamespace<'a> {
22 pub fn acos(self, name: &str) -> LazyFrame {
23 self.math_transform_1_in_1_out::<ACOS>(name, "acos")
24 }
25 pub fn asin(self, name: &str) -> LazyFrame {
26 self.math_transform_1_in_1_out::<ASIN>(name, "asin")
27 }
28 pub fn atan(self, name: &str) -> LazyFrame {
29 self.math_transform_1_in_1_out::<ATAN>(name, "atan")
30 }
31 pub fn ceil(self, name: &str) -> LazyFrame {
32 self.math_transform_1_in_1_out::<CEIL>(name, "ceil")
33 }
34 pub fn cos(self, name: &str) -> LazyFrame {
35 self.math_transform_1_in_1_out::<COS>(name, "cos")
36 }
37 pub fn cosh(self, name: &str) -> LazyFrame {
38 self.math_transform_1_in_1_out::<COSH>(name, "cosh")
39 }
40 pub fn exp(self, name: &str) -> LazyFrame {
41 self.math_transform_1_in_1_out::<EXP>(name, "exp")
42 }
43 pub fn floor(self, name: &str) -> LazyFrame {
44 self.math_transform_1_in_1_out::<FLOOR>(name, "floor")
45 }
46 pub fn ln(self, name: &str) -> LazyFrame {
47 self.math_transform_1_in_1_out::<LN>(name, "ln")
48 }
49 pub fn log10(self, name: &str) -> LazyFrame {
50 self.math_transform_1_in_1_out::<LOG10>(name, "log10")
51 }
52 pub fn sin(self, name: &str) -> LazyFrame {
53 self.math_transform_1_in_1_out::<SIN>(name, "sin")
54 }
55 pub fn sinh(self, name: &str) -> LazyFrame {
56 self.math_transform_1_in_1_out::<SINH>(name, "sinh")
57 }
58 pub fn sqrt(self, name: &str) -> LazyFrame {
59 self.math_transform_1_in_1_out::<SQRT>(name, "sqrt")
60 }
61 pub fn tan(self, name: &str) -> LazyFrame {
62 self.math_transform_1_in_1_out::<TAN>(name, "tan")
63 }
64 pub fn tanh(self, name: &str) -> LazyFrame {
65 self.math_transform_1_in_1_out::<TANH>(name, "tanh")
66 }
67
68 pub fn add(self, in1: &str, in2: &str) -> LazyFrame {
69 self.math_operator_2_in_1_out::<ADD>(in1, in2, "add")
70 }
71 pub fn sub(self, in1: &str, in2: &str) -> LazyFrame {
72 self.math_operator_2_in_1_out::<SUB>(in1, in2, "sub")
73 }
74 pub fn mult(self, in1: &str, in2: &str) -> LazyFrame {
75 self.math_operator_2_in_1_out::<MULT>(in1, in2, "mult")
76 }
77 pub fn div(self, in1: &str, in2: &str) -> LazyFrame {
78 self.math_operator_2_in_1_out::<DIV>(in1, in2, "div")
79 }
80
81 pub fn max(self, name: &str, period: usize) -> LazyFrame {
82 self.math_operator_1_in_1_out_period::<MAX>(name, period, "max")
83 }
84 pub fn maxindex(self, name: &str, period: usize) -> LazyFrame {
85 self.math_operator_1_in_1_out_period::<MAXINDEX>(name, period, "maxindex")
86 }
87 pub fn min(self, name: &str, period: usize) -> LazyFrame {
88 self.math_operator_1_in_1_out_period::<MIN>(name, period, "min")
89 }
90 pub fn minindex(self, name: &str, period: usize) -> LazyFrame {
91 self.math_operator_1_in_1_out_period::<MININDEX>(name, period, "minindex")
92 }
93 pub fn sum(self, name: &str, period: usize) -> LazyFrame {
94 self.math_operator_1_in_1_out_period::<SUM>(name, period, "sum")
95 }
96
97 pub fn sma(self, name: &str, period: usize) -> LazyFrame {
98 self.math_operator_1_in_1_out_period::<SMA>(name, period, "sma")
99 }
100 pub fn ema(self, name: &str, period: usize) -> LazyFrame {
101 self.math_operator_1_in_1_out_period::<EMA>(name, period, "ema")
102 }
103 pub fn wma(self, name: &str, period: usize) -> LazyFrame {
104 self.math_operator_1_in_1_out_period::<WMA>(name, period, "wma")
105 }
106 pub fn dema(self, name: &str, period: usize) -> LazyFrame {
107 self.math_operator_1_in_1_out_period::<DEMA>(name, period, "dema")
108 }
109 pub fn trima(self, name: &str, period: usize) -> LazyFrame {
110 self.math_operator_1_in_1_out_period::<TRIMA>(name, period, "trima")
111 }
112 pub fn kama(self, name: &str, period: usize) -> LazyFrame {
113 self.math_operator_1_in_1_out_period::<KAMA>(name, period, "kama")
114 }
115 pub fn midpoint(self, name: &str, period: usize) -> LazyFrame {
116 self.math_operator_1_in_1_out_period::<MIDPOINT>(name, period, "midpoint")
117 }
118 pub fn ht_trendline(self, name: &str) -> LazyFrame {
119 self.math_transform_1_in_1_out::<HT_TRENDLINE>(name, "ht_trendline")
120 }
121 pub fn midprice(self, high: &str, low: &str, period: usize) -> LazyFrame {
122 self.math_operator_2_in_1_out_period::<MIDPRICE>(high, low, period, "midprice")
123 }
124
125 pub fn rsi(self, name: &str, period: usize) -> LazyFrame {
126 self.math_operator_1_in_1_out_period::<RSI>(name, period, "rsi")
127 }
128 pub fn mom(self, name: &str, period: usize) -> LazyFrame {
129 self.math_operator_1_in_1_out_period::<MOM>(name, period, "mom")
130 }
131 pub fn roc(self, name: &str, period: usize) -> LazyFrame {
132 self.math_operator_1_in_1_out_period::<ROC>(name, period, "roc")
133 }
134 pub fn rocp(self, name: &str, period: usize) -> LazyFrame {
135 self.math_operator_1_in_1_out_period::<ROCP>(name, period, "rocp")
136 }
137 pub fn rocr(self, name: &str, period: usize) -> LazyFrame {
138 self.math_operator_1_in_1_out_period::<ROCR>(name, period, "rocr")
139 }
140 pub fn rocr100(self, name: &str, period: usize) -> LazyFrame {
141 self.math_operator_1_in_1_out_period::<ROCR100>(name, period, "rocr100")
142 }
143 pub fn trix(self, name: &str, period: usize) -> LazyFrame {
144 self.math_operator_1_in_1_out_period::<TRIX>(name, period, "trix")
145 }
146 pub fn cmo(self, name: &str, period: usize) -> LazyFrame {
147 self.math_operator_1_in_1_out_period::<CMO>(name, period, "cmo")
148 }
149
150 pub fn frac_diff(self, name: &str, d: f64, threshold: f64) -> LazyFrame {
152 let name = name.to_string();
153 self.0.clone().with_columns([col(&name)
154 .map(
155 move |s| {
156 let ca = s.f64()?;
157 let mut indicator = FracDiff::new(d, threshold);
158 let mut values = Vec::with_capacity(s.len());
159 for i in 0..s.len() {
160 let val = ca.get(i).unwrap_or(f64::NAN);
161 values.push(indicator.next(val));
162 }
163 Ok(Some(Column::from(Series::new("frac_diff".into(), values))))
164 },
165 GetOutput::from_type(DataType::Float64),
166 )
167 .alias("frac_diff")])
168 }
169
170 pub fn adx(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
171 self.ta_3_in_1_out_period::<ADX>(high, low, close, period, "adx")
172 }
173 pub fn adxr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
174 self.ta_3_in_1_out_period::<ADXR>(high, low, close, period, "adxr")
175 }
176 pub fn cci(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
177 self.ta_3_in_1_out_period::<CCI>(high, low, close, period, "cci")
178 }
179 pub fn willr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
180 self.ta_3_in_1_out_period::<WILLR>(high, low, close, period, "willr")
181 }
182 pub fn dx(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
183 self.ta_3_in_1_out_period::<DX>(high, low, close, period, "dx")
184 }
185 pub fn plus_di(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
186 self.ta_3_in_1_out_period::<PLUS_DI>(high, low, close, period, "plus_di")
187 }
188 pub fn minus_di(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
189 self.ta_3_in_1_out_period::<MINUS_DI>(high, low, close, period, "minus_di")
190 }
191
192 pub fn ta_atr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
193 self.ta_3_in_1_out_period::<TaATR>(high, low, close, period, "ta_atr")
194 }
195 pub fn ta_natr(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
196 self.ta_3_in_1_out_period::<TaNATR>(high, low, close, period, "ta_natr")
197 }
198 pub fn ta_trange(self, high: &str, low: &str, close: &str) -> LazyFrame {
199 self.ta_3_in_1_out_default::<TaTRANGE>(high, low, close, "ta_trange")
200 }
201
202 pub fn obv(self, close: &str, volume: &str) -> LazyFrame {
203 self.math_operator_2_in_1_out::<OBV>(close, volume, "obv")
204 }
205 pub fn ad(self, high: &str, low: &str, close: &str, volume: &str) -> LazyFrame {
206 self.ta_4_in_1_out_default::<AD>(high, low, close, volume, "ad")
207 }
208 pub fn adosc(
209 self,
210 high: &str,
211 low: &str,
212 close: &str,
213 volume: &str,
214 fast: usize,
215 slow: usize,
216 ) -> LazyFrame {
217 let high_str = high.to_string();
218 let low_str = low.to_string();
219 let close_str = close.to_string();
220 let volume_str = volume.to_string();
221 self.0.clone().with_columns([as_struct(vec![
222 col(&high_str),
223 col(&low_str),
224 col(&close_str),
225 col(&volume_str),
226 ])
227 .map(
228 move |s| {
229 let ca = s.struct_()?;
230 let s_h = ca.field_by_name(&high_str)?;
231 let s_l = ca.field_by_name(&low_str)?;
232 let s_c = ca.field_by_name(&close_str)?;
233 let s_v = ca.field_by_name(&volume_str)?;
234
235 let high = s_h.f64()?;
236 let low = s_l.f64()?;
237 let close = s_c.f64()?;
238 let volume = s_v.f64()?;
239
240 let mut indicator = ADOSC::new(fast, slow);
241 let mut values = Vec::with_capacity(s.len());
242
243 for i in 0..s.len() {
244 let h = high.get(i).unwrap_or(f64::NAN);
245 let l = low.get(i).unwrap_or(f64::NAN);
246 let c = close.get(i).unwrap_or(f64::NAN);
247 let v = volume.get(i).unwrap_or(f64::NAN);
248 values.push(indicator.next((h, l, c, v)));
249 }
250
251 Ok(Some(Column::from(Series::new("adosc".into(), values))))
252 },
253 GetOutput::from_type(DataType::Float64),
254 )
255 .alias("adosc")])
256 }
257
258 pub fn aroon(self, high: &str, low: &str, period: usize) -> LazyFrame {
259 let high_str = high.to_string();
260 let low_str = low.to_string();
261 self.0
262 .clone()
263 .with_columns([as_struct(vec![col(&high_str), col(&low_str)])
264 .map(
265 move |s| {
266 let ca = s.struct_()?;
267 let s_h = ca.field_by_name(&high_str)?;
268 let s_l = ca.field_by_name(&low_str)?;
269 let high = s_h.f64()?;
270 let low = s_l.f64()?;
271
272 let mut indicator = AROON::new(period);
273 let mut up_vals = Vec::with_capacity(s.len());
274 let mut down_vals = Vec::with_capacity(s.len());
275
276 for i in 0..s.len() {
277 let h = high.get(i).unwrap_or(f64::NAN);
278 let l = low.get(i).unwrap_or(f64::NAN);
279 let (up, down) = indicator.next((h, l));
280 up_vals.push(up);
281 down_vals.push(down);
282 }
283
284 let s_up = Series::new("aroon_up".into(), up_vals);
285 let s_down = Series::new("aroon_down".into(), down_vals);
286 let struct_series = StructChunked::from_series(
287 "aroon_result".into(),
288 s.len(),
289 [s_up, s_down].iter(),
290 )?;
291 Ok(Some(Column::from(struct_series.into_series())))
292 },
293 GetOutput::from_type(DataType::Struct(vec![
294 Field::new("aroon_up".into(), DataType::Float64),
295 Field::new("aroon_down".into(), DataType::Float64),
296 ])),
297 )
298 .alias("aroon")])
299 }
300
301 #[allow(clippy::too_many_arguments)]
302 pub fn stoch(
303 self,
304 high: &str,
305 low: &str,
306 close: &str,
307 fastk: usize,
308 slowk: usize,
309 slowk_matype: talib::MaType,
310 slowd: usize,
311 slowd_matype: talib::MaType,
312 ) -> LazyFrame {
313 let high_str = high.to_string();
314 let low_str = low.to_string();
315 let close_str = close.to_string();
316 self.0.clone().with_columns([as_struct(vec![
317 col(&high_str),
318 col(&low_str),
319 col(&close_str),
320 ])
321 .map(
322 move |s| {
323 let ca = s.struct_()?;
324 let s_h = ca.field_by_name(&high_str)?;
325 let s_l = ca.field_by_name(&low_str)?;
326 let s_c = ca.field_by_name(&close_str)?;
327 let high = s_h.f64()?;
328 let low = s_l.f64()?;
329 let close = s_c.f64()?;
330
331 let mut indicator = STOCH::new(fastk, slowk, slowk_matype, slowd, slowd_matype);
332 let mut k_vals = Vec::with_capacity(s.len());
333 let mut d_vals = Vec::with_capacity(s.len());
334
335 for i in 0..s.len() {
336 let h = high.get(i).unwrap_or(f64::NAN);
337 let l = low.get(i).unwrap_or(f64::NAN);
338 let c = close.get(i).unwrap_or(f64::NAN);
339 let (k, d) = indicator.next((h, l, c));
340 k_vals.push(k);
341 d_vals.push(d);
342 }
343
344 let s_k = Series::new("slowk".into(), k_vals);
345 let s_d = Series::new("slowd".into(), d_vals);
346 let struct_series =
347 StructChunked::from_series("stoch_result".into(), s.len(), [s_k, s_d].iter())?;
348 Ok(Some(Column::from(struct_series.into_series())))
349 },
350 GetOutput::from_type(DataType::Struct(vec![
351 Field::new("slowk".into(), DataType::Float64),
352 Field::new("slowd".into(), DataType::Float64),
353 ])),
354 )
355 .alias("stoch")])
356 }
357
358 pub fn avgprice(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
359 self.ta_4_in_1_out_default::<AVGPRICE>(open, high, low, close, "avgprice")
360 }
361 pub fn medprice(self, high: &str, low: &str) -> LazyFrame {
362 self.math_operator_2_in_1_out::<MEDPRICE>(high, low, "medprice")
363 }
364 pub fn typprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
365 self.ta_3_in_1_out_default::<TYPPRICE>(high, low, close, "typprice")
366 }
367 pub fn wclprice(self, high: &str, low: &str, close: &str) -> LazyFrame {
368 self.ta_3_in_1_out_default::<WCLPRICE>(high, low, close, "wclprice")
369 }
370
371 pub fn ht_dcperiod(self, name: &str) -> LazyFrame {
372 self.math_transform_1_in_1_out::<HT_DCPERIOD>(name, "ht_dcperiod")
373 }
374 pub fn ht_dcphase(self, name: &str) -> LazyFrame {
375 self.math_transform_1_in_1_out::<HT_DCPHASE>(name, "ht_dcphase")
376 }
377 pub fn ht_trendmode(self, name: &str) -> LazyFrame {
378 self.math_transform_1_in_1_out::<HT_TRENDMODE>(name, "ht_trendmode")
379 }
380
381 pub fn ta_stddev(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
382 let name_str = name.to_string();
383 self.0.clone().with_columns([col(&name_str)
384 .map(
385 move |s| {
386 let ca = s.f64()?;
387 let mut indicator = TaSTDDEV::new(period, nbdev);
388 let mut values = Vec::with_capacity(s.len());
389 for i in 0..s.len() {
390 let val = ca.get(i).unwrap_or(f64::NAN);
391 values.push(indicator.next(val));
392 }
393 Ok(Some(Column::from(Series::new("ta_stddev".into(), values))))
394 },
395 GetOutput::from_type(DataType::Float64),
396 )
397 .alias("ta_stddev")])
398 }
399 pub fn ta_var(self, name: &str, period: usize, nbdev: f64) -> LazyFrame {
400 let name_str = name.to_string();
401 self.0.clone().with_columns([col(&name_str)
402 .map(
403 move |s| {
404 let ca = s.f64()?;
405 let mut indicator = TaVAR::new(period, nbdev);
406 let mut values = Vec::with_capacity(s.len());
407 for i in 0..s.len() {
408 let val = ca.get(i).unwrap_or(f64::NAN);
409 values.push(indicator.next(val));
410 }
411 Ok(Some(Column::from(Series::new("ta_var".into(), values))))
412 },
413 GetOutput::from_type(DataType::Float64),
414 )
415 .alias("ta_var")])
416 }
417 pub fn ta_beta(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
418 self.math_operator_2_in_1_out_period::<TaBETA>(in1, in2, period, "ta_beta")
419 }
420 pub fn ta_correl(self, in1: &str, in2: &str, period: usize) -> LazyFrame {
421 self.math_operator_2_in_1_out_period::<TaCORREL>(in1, in2, period, "ta_correl")
422 }
423 pub fn ta_linearreg(self, name: &str, period: usize) -> LazyFrame {
424 self.math_operator_1_in_1_out_period::<TaLINEARREG>(name, period, "ta_linearreg")
425 }
426 pub fn ta_linearreg_slope(self, name: &str, period: usize) -> LazyFrame {
427 self.math_operator_1_in_1_out_period::<TaLINEARREG_SLOPE>(
428 name,
429 period,
430 "ta_linearreg_slope",
431 )
432 }
433 pub fn ta_linearreg_intercept(self, name: &str, period: usize) -> LazyFrame {
434 self.math_operator_1_in_1_out_period::<TaLINEARREG_INTERCEPT>(
435 name,
436 period,
437 "ta_linearreg_intercept",
438 )
439 }
440 pub fn ta_linearreg_angle(self, name: &str, period: usize) -> LazyFrame {
441 self.math_operator_1_in_1_out_period::<TaLINEARREG_ANGLE>(
442 name,
443 period,
444 "ta_linearreg_angle",
445 )
446 }
447 pub fn ta_tsf(self, name: &str, period: usize) -> LazyFrame {
448 self.math_operator_1_in_1_out_period::<TaTSF>(name, period, "ta_tsf")
449 }
450
451 pub fn cdl_2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
452 self.ta_4_in_1_out_default::<CDL2CROWS>(open, high, low, close, "cdl_2crows")
453 }
454 pub fn cdl_3blackcrows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
455 self.ta_4_in_1_out_default::<CDL3BLACKCROWS>(open, high, low, close, "cdl_3blackcrows")
456 }
457 pub fn cdl_3inside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
458 self.ta_4_in_1_out_default::<CDL3INSIDE>(open, high, low, close, "cdl_3inside")
459 }
460 pub fn cdl_3linestrike(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
461 self.ta_4_in_1_out_default::<CDL3LINESTRIKE>(open, high, low, close, "cdl_3linestrike")
462 }
463 pub fn cdl_3outside(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
464 self.ta_4_in_1_out_default::<CDL3OUTSIDE>(open, high, low, close, "cdl_3outside")
465 }
466 pub fn cdl_3starsinsouth(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
467 self.ta_4_in_1_out_default::<CDL3STARSINSOUTH>(open, high, low, close, "cdl_3starsinsouth")
468 }
469 pub fn cdl_3whitesoldiers(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
470 self.ta_4_in_1_out_default::<CDL3WHITESOLDIERS>(
471 open,
472 high,
473 low,
474 close,
475 "cdl_3whitesoldiers",
476 )
477 }
478 pub fn cdl_abandonedbaby(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
479 self.ta_4_in_1_out_default::<CDLABANDONEDBABY>(open, high, low, close, "cdl_abandonedbaby")
480 }
481 pub fn cdl_advanceblock(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
482 self.ta_4_in_1_out_default::<CDLADVANCEBLOCK>(open, high, low, close, "cdl_advanceblock")
483 }
484 pub fn cdl_belthold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
485 self.ta_4_in_1_out_default::<CDLBELTHOLD>(open, high, low, close, "cdl_belthold")
486 }
487 pub fn cdl_breakaway(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
488 self.ta_4_in_1_out_default::<CDLBREAKAWAY>(open, high, low, close, "cdl_breakaway")
489 }
490 pub fn cdl_closingmarubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
491 self.ta_4_in_1_out_default::<CDLCLOSINGMARUBOZU>(
492 open,
493 high,
494 low,
495 close,
496 "cdl_closingmarubozu",
497 )
498 }
499 pub fn cdl_concealbabyswall(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
500 self.ta_4_in_1_out_default::<CDLCONCEALBABYSWALL>(
501 open,
502 high,
503 low,
504 close,
505 "cdl_concealbabyswall",
506 )
507 }
508 pub fn cdl_counterattack(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
509 self.ta_4_in_1_out_default::<CDLCOUNTERATTACK>(open, high, low, close, "cdl_counterattack")
510 }
511 pub fn cdl_darkcloudcover(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
512 self.ta_4_in_1_out_default::<CDLDARKCLOUDCOVER>(
513 open,
514 high,
515 low,
516 close,
517 "cdl_darkcloudcover",
518 )
519 }
520 pub fn cdl_doji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
521 self.ta_4_in_1_out_default::<CDLDOJI>(open, high, low, close, "cdl_doji")
522 }
523 pub fn cdl_dojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
524 self.ta_4_in_1_out_default::<CDLDOJISTAR>(open, high, low, close, "cdl_dojistar")
525 }
526 pub fn cdl_dragonflydoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
527 self.ta_4_in_1_out_default::<CDLDRAGONFLYDOJI>(open, high, low, close, "cdl_dragonflydoji")
528 }
529 pub fn cdl_engulfing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
530 self.ta_4_in_1_out_default::<CDLENGULFING>(open, high, low, close, "cdl_engulfing")
531 }
532 pub fn cdl_eveningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
533 self.ta_4_in_1_out_default::<CDLEVENINGDOJISTAR>(
534 open,
535 high,
536 low,
537 close,
538 "cdl_eveningdojistar",
539 )
540 }
541 pub fn cdl_eveningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
542 self.ta_4_in_1_out_default::<CDLEVENINGSTAR>(open, high, low, close, "cdl_eveningstar")
543 }
544 pub fn cdl_gapsidesidewhite(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
545 self.ta_4_in_1_out_default::<CDLGAPSIDESIDEWHITE>(
546 open,
547 high,
548 low,
549 close,
550 "cdl_gapsidesidewhite",
551 )
552 }
553 pub fn cdl_gravestonedoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
554 self.ta_4_in_1_out_default::<CDLGRAVESTONEDOJI>(
555 open,
556 high,
557 low,
558 close,
559 "cdl_gravestonedoji",
560 )
561 }
562 pub fn cdl_hammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
563 self.ta_4_in_1_out_default::<CDLHAMMER>(open, high, low, close, "cdl_hammer")
564 }
565 pub fn cdl_hangingman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
566 self.ta_4_in_1_out_default::<CDLHANGINGMAN>(open, high, low, close, "cdl_hangingman")
567 }
568 pub fn cdl_harami(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
569 self.ta_4_in_1_out_default::<CDLHARAMI>(open, high, low, close, "cdl_harami")
570 }
571 pub fn cdl_haramicross(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
572 self.ta_4_in_1_out_default::<CDLHARAMICROSS>(open, high, low, close, "cdl_haramicross")
573 }
574 pub fn cdl_highwave(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
575 self.ta_4_in_1_out_default::<CDLHIGHWAVE>(open, high, low, close, "cdl_highwave")
576 }
577 pub fn cdl_hikkake(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
578 self.ta_4_in_1_out_default::<CDLHIKKAKE>(open, high, low, close, "cdl_hikkake")
579 }
580 pub fn cdl_hikkakemod(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
581 self.ta_4_in_1_out_default::<CDLHIKKAKEMOD>(open, high, low, close, "cdl_hikkakemod")
582 }
583 pub fn cdl_homingpigeon(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
584 self.ta_4_in_1_out_default::<CDLHOMINGPIGEON>(open, high, low, close, "cdl_homingpigeon")
585 }
586 pub fn cdl_identical3crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
587 self.ta_4_in_1_out_default::<CDLIDENTICAL3CROWS>(
588 open,
589 high,
590 low,
591 close,
592 "cdl_identical3crows",
593 )
594 }
595 pub fn cdl_inneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
596 self.ta_4_in_1_out_default::<CDLINNECK>(open, high, low, close, "cdl_inneck")
597 }
598 pub fn cdl_invertedhammer(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
599 self.ta_4_in_1_out_default::<CDLINVERTEDHAMMER>(
600 open,
601 high,
602 low,
603 close,
604 "cdl_invertedhammer",
605 )
606 }
607 pub fn cdl_kicking(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
608 self.ta_4_in_1_out_default::<CDLKICKING>(open, high, low, close, "cdl_kicking")
609 }
610 pub fn cdl_kickingbylength(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
611 self.ta_4_in_1_out_default::<CDLKICKINGBYLENGTH>(
612 open,
613 high,
614 low,
615 close,
616 "cdl_kickingbylength",
617 )
618 }
619 pub fn cdl_ladderbottom(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
620 self.ta_4_in_1_out_default::<CDLLADDERBOTTOM>(open, high, low, close, "cdl_ladderbottom")
621 }
622 pub fn cdl_longleggeddoji(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
623 self.ta_4_in_1_out_default::<CDLLONGLEGGEDDOJI>(
624 open,
625 high,
626 low,
627 close,
628 "cdl_longleggeddoji",
629 )
630 }
631 pub fn cdl_longline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
632 self.ta_4_in_1_out_default::<CDLLONGLINE>(open, high, low, close, "cdl_longline")
633 }
634 pub fn cdl_marubozu(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
635 self.ta_4_in_1_out_default::<CDLMARUBOZU>(open, high, low, close, "cdl_marubozu")
636 }
637 pub fn cdl_matchinglow(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
638 self.ta_4_in_1_out_default::<CDLMATCHINGLOW>(open, high, low, close, "cdl_matchinglow")
639 }
640 pub fn cdl_mathold(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
641 self.ta_4_in_1_out_default::<CDLMATHOLD>(open, high, low, close, "cdl_mathold")
642 }
643 pub fn cdl_morningdojistar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
644 self.ta_4_in_1_out_default::<CDLMORNINGDOJISTAR>(
645 open,
646 high,
647 low,
648 close,
649 "cdl_morningdojistar",
650 )
651 }
652 pub fn cdl_morningstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
653 self.ta_4_in_1_out_default::<CDLMORNINGSTAR>(open, high, low, close, "cdl_morningstar")
654 }
655 pub fn cdl_onneck(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
656 self.ta_4_in_1_out_default::<CDLONNECK>(open, high, low, close, "cdl_onneck")
657 }
658 pub fn cdl_piercing(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
659 self.ta_4_in_1_out_default::<CDLPIERCING>(open, high, low, close, "cdl_piercing")
660 }
661 pub fn cdl_rickshawman(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
662 self.ta_4_in_1_out_default::<CDLRICKSHAWMAN>(open, high, low, close, "cdl_rickshawman")
663 }
664 pub fn cdl_risefall3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
665 self.ta_4_in_1_out_default::<CDLRISEFALL3METHODS>(
666 open,
667 high,
668 low,
669 close,
670 "cdl_risefall3methods",
671 )
672 }
673 pub fn cdl_separatinglines(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
674 self.ta_4_in_1_out_default::<CDLSEPARATINGLINES>(
675 open,
676 high,
677 low,
678 close,
679 "cdl_separatinglines",
680 )
681 }
682 pub fn cdl_shootingstar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
683 self.ta_4_in_1_out_default::<CDLSHOOTINGSTAR>(open, high, low, close, "cdl_shootingstar")
684 }
685 pub fn cdl_shortline(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
686 self.ta_4_in_1_out_default::<CDLSHORTLINE>(open, high, low, close, "cdl_shortline")
687 }
688 pub fn cdl_spinningtop(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
689 self.ta_4_in_1_out_default::<CDLSPINNINGTOP>(open, high, low, close, "cdl_spinningtop")
690 }
691 pub fn cdl_stalledpattern(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
692 self.ta_4_in_1_out_default::<CDLSTALLEDPATTERN>(
693 open,
694 high,
695 low,
696 close,
697 "cdl_stalledpattern",
698 )
699 }
700 pub fn cdl_sticksandwich(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
701 self.ta_4_in_1_out_default::<CDLSTICKSANDWICH>(open, high, low, close, "cdl_sticksandwich")
702 }
703 pub fn cdl_takuri(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
704 self.ta_4_in_1_out_default::<CDLTAKURI>(open, high, low, close, "cdl_takuri")
705 }
706 pub fn cdl_tasukigap(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
707 self.ta_4_in_1_out_default::<CDLTASUKIGAP>(open, high, low, close, "cdl_tasukigap")
708 }
709 pub fn cdl_thrusting(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
710 self.ta_4_in_1_out_default::<CDLTHRUSTING>(open, high, low, close, "cdl_thrusting")
711 }
712 pub fn cdl_tristar(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
713 self.ta_4_in_1_out_default::<CDLTRISTAR>(open, high, low, close, "cdl_tristar")
714 }
715 pub fn cdl_unique3river(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
716 self.ta_4_in_1_out_default::<CDLUNIQUE3RIVER>(open, high, low, close, "cdl_unique3river")
717 }
718 pub fn cdl_upsidegap2crows(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
719 self.ta_4_in_1_out_default::<CDLUPSIDEGAP2CROWS>(
720 open,
721 high,
722 low,
723 close,
724 "cdl_upsidegap2crows",
725 )
726 }
727 pub fn cdl_xsidegap3methods(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
728 self.ta_4_in_1_out_default::<CDLXSIDEGAP3METHODS>(
729 open,
730 high,
731 low,
732 close,
733 "cdl_xsidegap3methods",
734 )
735 }
736
737 pub fn macd(self, name: &str, fast: usize, slow: usize, signal: usize) -> LazyFrame {
738 let name_str = name.to_string();
739 self.0.clone().with_columns([col(&name_str)
740 .map(
741 move |s| {
742 let ca = s.f64()?;
743 let mut indicator = MACD::new(fast, slow, signal);
744 let mut macd_vals = Vec::with_capacity(s.len());
745 let mut signal_vals = Vec::with_capacity(s.len());
746 let mut hist_vals = Vec::with_capacity(s.len());
747
748 for i in 0..s.len() {
749 let val = ca.get(i).unwrap_or(f64::NAN);
750 let (m, s_val, h) = indicator.next(val);
751 macd_vals.push(m);
752 signal_vals.push(s_val);
753 hist_vals.push(h);
754 }
755
756 let s_macd = Series::new("macd".into(), macd_vals);
757 let s_signal = Series::new("macd_signal".into(), signal_vals);
758 let s_hist = Series::new("macd_hist".into(), hist_vals);
759
760 let struct_series = StructChunked::from_series(
761 "macd_result".into(),
762 s.len(),
763 [s_macd, s_signal, s_hist].iter(),
764 )?;
765 Ok(Some(Column::from(struct_series.into_series())))
766 },
767 GetOutput::from_type(DataType::Struct(vec![
768 Field::new("macd".into(), DataType::Float64),
769 Field::new("macd_signal".into(), DataType::Float64),
770 Field::new("macd_hist".into(), DataType::Float64),
771 ])),
772 )
773 .alias("macd")])
774 }
775
776 pub fn bbands(
777 self,
778 name: &str,
779 period: usize,
780 nbdevup: f64,
781 nbdevdn: f64,
782 matype: talib::MaType,
783 ) -> LazyFrame {
784 let name_str = name.to_string();
785 self.0.clone().with_columns([col(&name_str)
786 .map(
787 move |s| {
788 let ca = s.f64()?;
789 let mut indicator = BBANDS::new(period, nbdevup, nbdevdn, matype);
790 let mut upper_vals = Vec::with_capacity(s.len());
791 let mut middle_vals = Vec::with_capacity(s.len());
792 let mut lower_vals = Vec::with_capacity(s.len());
793
794 for i in 0..s.len() {
795 let val = ca.get(i).unwrap_or(f64::NAN);
796 let (u, m, l) = indicator.next(val);
797 upper_vals.push(u);
798 middle_vals.push(m);
799 lower_vals.push(l);
800 }
801
802 let s_upper = Series::new("upper".into(), upper_vals);
803 let s_middle = Series::new("middle".into(), middle_vals);
804 let s_lower = Series::new("lower".into(), lower_vals);
805
806 let struct_series = StructChunked::from_series(
807 "bbands_result".into(),
808 s.len(),
809 [s_upper, s_middle, s_lower].iter(),
810 )?;
811 Ok(Some(Column::from(struct_series.into_series())))
812 },
813 GetOutput::from_type(DataType::Struct(vec![
814 Field::new("upper".into(), DataType::Float64),
815 Field::new("middle".into(), DataType::Float64),
816 Field::new("lower".into(), DataType::Float64),
817 ])),
818 )
819 .alias("bbands")])
820 }
821
822
823 pub fn macdext(
824 self,
825 name: &str,
826 fastperiod: usize,
827 fastmatype: talib::MaType,
828 slowperiod: usize,
829 slowmatype: talib::MaType,
830 signalperiod: usize,
831 signalmatype: talib::MaType,
832 ) -> LazyFrame {
833 let name_str = name.to_string();
834 self.0.clone().with_columns([col(&name_str)
835 .map(
836 move |s| {
837 let ca = s.f64()?;
838 let mut indicator = MACDEXT::new(
839 fastperiod,
840 fastmatype,
841 slowperiod,
842 slowmatype,
843 signalperiod,
844 signalmatype,
845 );
846 let mut macd_vals = Vec::with_capacity(s.len());
847 let mut signal_vals = Vec::with_capacity(s.len());
848 let mut hist_vals = Vec::with_capacity(s.len());
849
850 for i in 0..s.len() {
851 let val = ca.get(i).unwrap_or(f64::NAN);
852 let (m, s_val, h) = indicator.next(val);
853 macd_vals.push(m);
854 signal_vals.push(s_val);
855 hist_vals.push(h);
856 }
857
858 let s_macd = Series::new("macd".into(), macd_vals);
859 let s_signal = Series::new("macd_signal".into(), signal_vals);
860 let s_hist = Series::new("macd_hist".into(), hist_vals);
861
862 let struct_series = StructChunked::from_series(
863 "macdext_result".into(),
864 s.len(),
865 [s_macd, s_signal, s_hist].iter(),
866 )?;
867 Ok(Some(Column::from(struct_series.into_series())))
868 },
869 GetOutput::from_type(DataType::Struct(vec![
870 Field::new("macd".into(), DataType::Float64),
871 Field::new("macd_signal".into(), DataType::Float64),
872 Field::new("macd_hist".into(), DataType::Float64),
873 ])),
874 )
875 .alias("macdext")])
876 }
877
878 pub fn macdfix(self, name: &str, signalperiod: usize) -> LazyFrame {
879 let name_str = name.to_string();
880 self.0.clone().with_columns([col(&name_str)
881 .map(
882 move |s| {
883 let ca = s.f64()?;
884 let mut indicator = MACDFIX::new(signalperiod);
885 let mut macd_vals = Vec::with_capacity(s.len());
886 let mut signal_vals = Vec::with_capacity(s.len());
887 let mut hist_vals = Vec::with_capacity(s.len());
888
889 for i in 0..s.len() {
890 let val = ca.get(i).unwrap_or(f64::NAN);
891 let (m, s_val, h) = indicator.next(val);
892 macd_vals.push(m);
893 signal_vals.push(s_val);
894 hist_vals.push(h);
895 }
896
897 let s_macd = Series::new("macd".into(), macd_vals);
898 let s_signal = Series::new("macd_signal".into(), signal_vals);
899 let s_hist = Series::new("macd_hist".into(), hist_vals);
900
901 let struct_series = StructChunked::from_series(
902 "macdfix_result".into(),
903 s.len(),
904 [s_macd, s_signal, s_hist].iter(),
905 )?;
906 Ok(Some(Column::from(struct_series.into_series())))
907 },
908 GetOutput::from_type(DataType::Struct(vec![
909 Field::new("macd".into(), DataType::Float64),
910 Field::new("macd_signal".into(), DataType::Float64),
911 Field::new("macd_hist".into(), DataType::Float64),
912 ])),
913 )
914 .alias("macdfix")])
915 }
916
917 pub fn stochf(
918 self,
919 high: &str,
920 low: &str,
921 close: &str,
922 fastk_period: usize,
923 fastd_period: usize,
924 fastd_matype: talib::MaType,
925 ) -> LazyFrame {
926 let high_str = high.to_string();
927 let low_str = low.to_string();
928 let close_str = close.to_string();
929 self.0.clone().with_columns([as_struct(vec![
930 col(&high_str),
931 col(&low_str),
932 col(&close_str),
933 ])
934 .map(
935 move |s| {
936 let ca = s.struct_()?;
937 let s_h = ca.field_by_name(&high_str)?;
938 let s_l = ca.field_by_name(&low_str)?;
939 let s_c = ca.field_by_name(&close_str)?;
940 let high = s_h.f64()?;
941 let low = s_l.f64()?;
942 let close = s_c.f64()?;
943
944 let mut indicator = STOCHF::new(fastk_period, fastd_period, fastd_matype);
945 let mut k_vals = Vec::with_capacity(s.len());
946 let mut d_vals = Vec::with_capacity(s.len());
947
948 for i in 0..s.len() {
949 let h = high.get(i).unwrap_or(f64::NAN);
950 let l = low.get(i).unwrap_or(f64::NAN);
951 let c = close.get(i).unwrap_or(f64::NAN);
952 let (k, d) = indicator.next((h, l, c));
953 k_vals.push(k);
954 d_vals.push(d);
955 }
956
957 let s_k = Series::new("fastk".into(), k_vals);
958 let s_d = Series::new("fastd".into(), d_vals);
959 let struct_series = StructChunked::from_series(
960 "stochf_result".into(),
961 s.len(),
962 [s_k, s_d].iter(),
963 )?;
964 Ok(Some(Column::from(struct_series.into_series())))
965 },
966 GetOutput::from_type(DataType::Struct(vec![
967 Field::new("fastk".into(), DataType::Float64),
968 Field::new("fastd".into(), DataType::Float64),
969 ])),
970 )
971 .alias("stochf")])
972 }
973
974 pub fn stochrsi(
975 self,
976 name: &str,
977 timeperiod: usize,
978 fastk_period: usize,
979 fastd_period: usize,
980 fastd_matype: talib::MaType,
981 ) -> LazyFrame {
982 let name_str = name.to_string();
983 self.0.clone().with_columns([col(&name_str)
984 .map(
985 move |s| {
986 let ca = s.f64()?;
987 let mut indicator = STOCHRSI::new(timeperiod, fastk_period, fastd_period, fastd_matype);
988 let mut k_vals = Vec::with_capacity(s.len());
989 let mut d_vals = Vec::with_capacity(s.len());
990
991 for i in 0..s.len() {
992 let val = ca.get(i).unwrap_or(f64::NAN);
993 let (k, d) = indicator.next(val);
994 k_vals.push(k);
995 d_vals.push(d);
996 }
997
998 let s_k = Series::new("fastk".into(), k_vals);
999 let s_d = Series::new("fastd".into(), d_vals);
1000
1001 let struct_series = StructChunked::from_series(
1002 "stochrsi_result".into(),
1003 s.len(),
1004 [s_k, s_d].iter(),
1005 )?;
1006 Ok(Some(Column::from(struct_series.into_series())))
1007 },
1008 GetOutput::from_type(DataType::Struct(vec![
1009 Field::new("fastk".into(), DataType::Float64),
1010 Field::new("fastd".into(), DataType::Float64),
1011 ])),
1012 )
1013 .alias("stochrsi")])
1014 }
1015
1016 pub fn apo(
1017 self,
1018 name: &str,
1019 fastperiod: usize,
1020 slowperiod: usize,
1021 matype: talib::MaType,
1022 ) -> LazyFrame {
1023 let name_str = name.to_string();
1024 self.0.clone().with_columns([col(&name_str)
1025 .map(
1026 move |s| {
1027 let ca = s.f64()?;
1028 let mut indicator = APO::new(fastperiod, slowperiod, matype);
1029 let mut values = Vec::with_capacity(s.len());
1030 for i in 0..s.len() {
1031 let val = ca.get(i).unwrap_or(f64::NAN);
1032 values.push(indicator.next(val));
1033 }
1034 Ok(Some(Column::from(Series::new("apo".into(), values))))
1035 },
1036 GetOutput::from_type(DataType::Float64),
1037 )
1038 .alias("apo")])
1039 }
1040
1041 pub fn ppo(
1042 self,
1043 name: &str,
1044 fastperiod: usize,
1045 slowperiod: usize,
1046 matype: talib::MaType,
1047 ) -> LazyFrame {
1048 let name_str = name.to_string();
1049 self.0.clone().with_columns([col(&name_str)
1050 .map(
1051 move |s| {
1052 let ca = s.f64()?;
1053 let mut indicator = PPO::new(fastperiod, slowperiod, matype);
1054 let mut values = Vec::with_capacity(s.len());
1055 for i in 0..s.len() {
1056 let val = ca.get(i).unwrap_or(f64::NAN);
1057 values.push(indicator.next(val));
1058 }
1059 Ok(Some(Column::from(Series::new("ppo".into(), values))))
1060 },
1061 GetOutput::from_type(DataType::Float64),
1062 )
1063 .alias("ppo")])
1064 }
1065
1066 pub fn bop(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
1067 self.ta_4_in_1_out_default::<BOP>(open, high, low, close, "bop")
1068 }
1069
1070 pub fn aroonosc(self, high: &str, low: &str, period: usize) -> LazyFrame {
1071 self.math_operator_2_in_1_out_period::<AROONOSC>(high, low, period, "aroonosc")
1072 }
1073
1074 pub fn mfi(
1075 self,
1076 high: &str,
1077 low: &str,
1078 close: &str,
1079 volume: &str,
1080 period: usize,
1081 ) -> LazyFrame {
1082 let high_str = high.to_string();
1083 let low_str = low.to_string();
1084 let close_str = close.to_string();
1085 let volume_str = volume.to_string();
1086 self.0.clone().with_columns([as_struct(vec![
1087 col(&high_str),
1088 col(&low_str),
1089 col(&close_str),
1090 col(&volume_str),
1091 ])
1092 .map(
1093 move |s| {
1094 let ca = s.struct_()?;
1095 let s_h = ca.field_by_name(&high_str)?;
1096 let s_l = ca.field_by_name(&low_str)?;
1097 let s_c = ca.field_by_name(&close_str)?;
1098 let s_v = ca.field_by_name(&volume_str)?;
1099
1100 let high = s_h.f64()?;
1101 let low = s_l.f64()?;
1102 let close = s_c.f64()?;
1103 let volume = s_v.f64()?;
1104
1105 let mut indicator = MFI::new(period);
1106 let mut values = Vec::with_capacity(s.len());
1107
1108 for i in 0..s.len() {
1109 let h = high.get(i).unwrap_or(f64::NAN);
1110 let l = low.get(i).unwrap_or(f64::NAN);
1111 let c = close.get(i).unwrap_or(f64::NAN);
1112 let v = volume.get(i).unwrap_or(f64::NAN);
1113 values.push(indicator.next((h, l, c, v)));
1114 }
1115
1116 Ok(Some(Column::from(Series::new("mfi".into(), values))))
1117 },
1118 GetOutput::from_type(DataType::Float64),
1119 )
1120 .alias("mfi")])
1121 }
1122
1123 pub fn ultosc(
1124 self,
1125 high: &str,
1126 low: &str,
1127 close: &str,
1128 timeperiod1: usize,
1129 timeperiod2: usize,
1130 timeperiod3: usize,
1131 ) -> LazyFrame {
1132 let high_str = high.to_string();
1133 let low_str = low.to_string();
1134 let close_str = close.to_string();
1135 self.0.clone().with_columns([as_struct(vec![
1136 col(&high_str),
1137 col(&low_str),
1138 col(&close_str),
1139 ])
1140 .map(
1141 move |s| {
1142 let ca = s.struct_()?;
1143 let s_h = ca.field_by_name(&high_str)?;
1144 let s_l = ca.field_by_name(&low_str)?;
1145 let s_c = ca.field_by_name(&close_str)?;
1146 let high = s_h.f64()?;
1147 let low = s_l.f64()?;
1148 let close = s_c.f64()?;
1149
1150 let mut indicator = ULTOSC::new(timeperiod1, timeperiod2, timeperiod3);
1151 let mut values = Vec::with_capacity(s.len());
1152
1153 for i in 0..s.len() {
1154 let h = high.get(i).unwrap_or(f64::NAN);
1155 let l = low.get(i).unwrap_or(f64::NAN);
1156 let c = close.get(i).unwrap_or(f64::NAN);
1157 values.push(indicator.next((h, l, c)));
1158 }
1159
1160 Ok(Some(Column::from(Series::new("ultosc".into(), values))))
1161 },
1162 GetOutput::from_type(DataType::Float64),
1163 )
1164 .alias("ultosc")])
1165 }
1166
1167 pub fn plus_dm(self, high: &str, low: &str, period: usize) -> LazyFrame {
1168 self.math_operator_2_in_1_out_period::<PLUS_DM>(high, low, period, "plus_dm")
1169 }
1170
1171 pub fn minus_dm(self, high: &str, low: &str, period: usize) -> LazyFrame {
1172 self.math_operator_2_in_1_out_period::<MINUS_DM>(high, low, period, "minus_dm")
1173 }
1174
1175 pub fn t3(
1176 self,
1177 name: &str,
1178 period: usize,
1179 v_factor: f64,
1180 ) -> LazyFrame {
1181 let name_str = name.to_string();
1182 self.0.clone().with_columns([col(&name_str)
1183 .map(
1184 move |s| {
1185 let ca = s.f64()?;
1186 let mut indicator = T3::new(period, v_factor);
1187 let mut values = Vec::with_capacity(s.len());
1188 for i in 0..s.len() {
1189 let val = ca.get(i).unwrap_or(f64::NAN);
1190 values.push(indicator.next(val));
1191 }
1192 Ok(Some(Column::from(Series::new("t3".into(), values))))
1193 },
1194 GetOutput::from_type(DataType::Float64),
1195 )
1196 .alias("t3")])
1197 }
1198
1199 pub fn mama(
1200 self,
1201 name: &str,
1202 fastlimit: f64,
1203 slowlimit: f64,
1204 ) -> LazyFrame {
1205 let name_str = name.to_string();
1206 self.0.clone().with_columns([col(&name_str)
1207 .map(
1208 move |s| {
1209 let ca = s.f64()?;
1210 let mut indicator = MAMA::new(fastlimit, slowlimit);
1211 let mut mama_vals = Vec::with_capacity(s.len());
1212 let mut fama_vals = Vec::with_capacity(s.len());
1213
1214 for i in 0..s.len() {
1215 let val = ca.get(i).unwrap_or(f64::NAN);
1216 let (m, f) = indicator.next(val);
1217 mama_vals.push(m);
1218 fama_vals.push(f);
1219 }
1220
1221 let s_mama = Series::new("mama".into(), mama_vals);
1222 let s_fama = Series::new("fama".into(), fama_vals);
1223
1224 let struct_series = StructChunked::from_series(
1225 "mama_result".into(),
1226 s.len(),
1227 [s_mama, s_fama].iter(),
1228 )?;
1229 Ok(Some(Column::from(struct_series.into_series())))
1230 },
1231 GetOutput::from_type(DataType::Struct(vec![
1232 Field::new("mama".into(), DataType::Float64),
1233 Field::new("fama".into(), DataType::Float64),
1234 ])),
1235 )
1236 .alias("mama")])
1237 }
1238
1239 pub fn sar(
1240 self,
1241 high: &str,
1242 low: &str,
1243 acceleration: f64,
1244 maximum: f64,
1245 ) -> LazyFrame {
1246 let high_str = high.to_string();
1247 let low_str = low.to_string();
1248 self.0.clone().with_columns([as_struct(vec![
1249 col(&high_str),
1250 col(&low_str),
1251 ])
1252 .map(
1253 move |s| {
1254 let ca = s.struct_()?;
1255 let s_h = ca.field_by_name(&high_str)?;
1256 let s_l = ca.field_by_name(&low_str)?;
1257 let high = s_h.f64()?;
1258 let low = s_l.f64()?;
1259
1260 let mut indicator = SAR::new(acceleration, maximum);
1261 let mut values = Vec::with_capacity(s.len());
1262
1263 for i in 0..s.len() {
1264 let h = high.get(i).unwrap_or(f64::NAN);
1265 let l = low.get(i).unwrap_or(f64::NAN);
1266 values.push(indicator.next((h, l)));
1267 }
1268
1269 Ok(Some(Column::from(Series::new("sar".into(), values))))
1270 },
1271 GetOutput::from_type(DataType::Float64),
1272 )
1273 .alias("sar")])
1274 }
1275
1276 #[allow(clippy::too_many_arguments)]
1277 pub fn sarext(
1278 self,
1279 high: &str,
1280 low: &str,
1281 startvalue: f64,
1282 offsetonreverse: f64,
1283 accelerationinitlong: f64,
1284 accelerationlong: f64,
1285 accelerationmaxlong: f64,
1286 accelerationinitshort: f64,
1287 accelerationshort: f64,
1288 accelerationmaxshort: f64,
1289 ) -> LazyFrame {
1290 let high_str = high.to_string();
1291 let low_str = low.to_string();
1292 self.0.clone().with_columns([as_struct(vec![
1293 col(&high_str),
1294 col(&low_str),
1295 ])
1296 .map(
1297 move |s| {
1298 let ca = s.struct_()?;
1299 let s_h = ca.field_by_name(&high_str)?;
1300 let s_l = ca.field_by_name(&low_str)?;
1301 let high = s_h.f64()?;
1302 let low = s_l.f64()?;
1303
1304 let mut indicator = SAREXT::new(
1305 startvalue,
1306 offsetonreverse,
1307 accelerationinitlong,
1308 accelerationlong,
1309 accelerationmaxlong,
1310 accelerationinitshort,
1311 accelerationshort,
1312 accelerationmaxshort,
1313 );
1314 let mut values = Vec::with_capacity(s.len());
1315
1316 for i in 0..s.len() {
1317 let h = high.get(i).unwrap_or(f64::NAN);
1318 let l = low.get(i).unwrap_or(f64::NAN);
1319 values.push(indicator.next((h, l)));
1320 }
1321
1322 Ok(Some(Column::from(Series::new("sarext".into(), values))))
1323 },
1324 GetOutput::from_type(DataType::Float64),
1325 )
1326 .alias("sarext")])
1327 }
1328
1329 pub fn mavp(
1330 self,
1331 in1: &str,
1332 in2: &str,
1333 minperiod: usize,
1334 maxperiod: usize,
1335 matype: talib::MaType,
1336 ) -> LazyFrame {
1337 let in1_str = in1.to_string();
1338 let in2_str = in2.to_string();
1339 self.0.clone().with_columns([as_struct(vec![
1340 col(&in1_str),
1341 col(&in2_str),
1342 ])
1343 .map(
1344 move |s| {
1345 let ca = s.struct_()?;
1346 let s_1 = ca.field_by_name(&in1_str)?;
1347 let s_2 = ca.field_by_name(&in2_str)?;
1348 let in1_ca = s_1.f64()?;
1349 let in2_ca = s_2.f64()?;
1350
1351 let mut indicator = MAVP::new(minperiod, maxperiod, matype);
1352 let mut values = Vec::with_capacity(s.len());
1353
1354 for i in 0..s.len() {
1355 let i1 = in1_ca.get(i).unwrap_or(f64::NAN);
1356 let i2 = in2_ca.get(i).unwrap_or(f64::NAN);
1357 values.push(indicator.next((i1, i2)));
1358 }
1359
1360 Ok(Some(Column::from(Series::new("mavp".into(), values))))
1361 },
1362 GetOutput::from_type(DataType::Float64),
1363 )
1364 .alias("mavp")])
1365 }
1366
1367 pub fn ht_phasor(self, name: &str) -> LazyFrame {
1368 let name_str = name.to_string();
1369 self.0.clone().with_columns([col(&name_str)
1370 .map(
1371 move |s| {
1372 let ca = s.f64()?;
1373 let mut indicator = HT_PHASOR::new();
1374 let mut inphase_vals = Vec::with_capacity(s.len());
1375 let mut quadrature_vals = Vec::with_capacity(s.len());
1376
1377 for i in 0..s.len() {
1378 let val = ca.get(i).unwrap_or(f64::NAN);
1379 let (inp, q) = indicator.next(val);
1380 inphase_vals.push(inp);
1381 quadrature_vals.push(q);
1382 }
1383
1384 let s_inphase = Series::new("inphase".into(), inphase_vals);
1385 let s_quadrature = Series::new("quadrature".into(), quadrature_vals);
1386
1387 let struct_series = StructChunked::from_series(
1388 "ht_phasor_result".into(),
1389 s.len(),
1390 [s_inphase, s_quadrature].iter(),
1391 )?;
1392 Ok(Some(Column::from(struct_series.into_series())))
1393 },
1394 GetOutput::from_type(DataType::Struct(vec![
1395 Field::new("inphase".into(), DataType::Float64),
1396 Field::new("quadrature".into(), DataType::Float64),
1397 ])),
1398 )
1399 .alias("ht_phasor")])
1400 }
1401
1402 pub fn ht_sine(self, name: &str) -> LazyFrame {
1403 let name_str = name.to_string();
1404 self.0.clone().with_columns([col(&name_str)
1405 .map(
1406 move |s| {
1407 let ca = s.f64()?;
1408 let mut indicator = HT_SINE::new();
1409 let mut sine_vals = Vec::with_capacity(s.len());
1410 let mut leadsine_vals = Vec::with_capacity(s.len());
1411
1412 for i in 0..s.len() {
1413 let val = ca.get(i).unwrap_or(f64::NAN);
1414 let (si, ls) = indicator.next(val);
1415 sine_vals.push(si);
1416 leadsine_vals.push(ls);
1417 }
1418
1419 let s_sine = Series::new("sine".into(), sine_vals);
1420 let s_leadsine = Series::new("leadsine".into(), leadsine_vals);
1421
1422 let struct_series = StructChunked::from_series(
1423 "ht_sine_result".into(),
1424 s.len(),
1425 [s_sine, s_leadsine].iter(),
1426 )?;
1427 Ok(Some(Column::from(struct_series.into_series())))
1428 },
1429 GetOutput::from_type(DataType::Struct(vec![
1430 Field::new("sine".into(), DataType::Float64),
1431 Field::new("leadsine".into(), DataType::Float64),
1432 ])),
1433 )
1434 .alias("ht_sine")])
1435 }
1436
1437 fn ta_3_in_1_out_period<I>(
1438 self,
1439 in1: &str,
1440 in2: &str,
1441 in3: &str,
1442 period: usize,
1443 output_name: &str,
1444 ) -> LazyFrame
1445 where
1446 I: Next<(f64, f64, f64), Output = f64> + Send + Sync + 'static,
1447 I: From<usize>,
1448 {
1449 let in1_str = in1.to_string();
1450 let in2_str = in2.to_string();
1451 let in3_str = in3.to_string();
1452 let output_name_str = output_name.to_string();
1453 let output_name_for_closure = output_name_str.clone();
1454 self.0.clone().with_columns(
1455 [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
1456 .map(
1457 move |s| {
1458 let ca = s.struct_()?;
1459 let s1 = ca.field_by_name(&in1_str)?;
1460 let s2 = ca.field_by_name(&in2_str)?;
1461 let s3 = ca.field_by_name(&in3_str)?;
1462
1463 let ca1 = s1.f64()?;
1464 let ca2 = s2.f64()?;
1465 let ca3 = s3.f64()?;
1466
1467 let mut indicator = I::from(period);
1468 let mut values = Vec::with_capacity(s.len());
1469
1470 for i in 0..s.len() {
1471 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1472 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1473 let v3 = ca3.get(i).unwrap_or(f64::NAN);
1474 values.push(indicator.next((v1, v2, v3)));
1475 }
1476
1477 Ok(Some(Column::from(Series::new(
1478 output_name_for_closure.clone().into(),
1479 values,
1480 ))))
1481 },
1482 GetOutput::from_type(DataType::Float64),
1483 )
1484 .alias(&output_name_str)],
1485 )
1486 }
1487
1488 fn ta_3_in_1_out_default<I>(
1489 self,
1490 in1: &str,
1491 in2: &str,
1492 in3: &str,
1493 output_name: &str,
1494 ) -> LazyFrame
1495 where
1496 I: Next<(f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
1497 {
1498 let in1_str = in1.to_string();
1499 let in2_str = in2.to_string();
1500 let in3_str = in3.to_string();
1501 let output_name_str = output_name.to_string();
1502 let output_name_for_closure = output_name_str.clone();
1503 self.0.clone().with_columns(
1504 [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
1505 .map(
1506 move |s| {
1507 let ca = s.struct_()?;
1508 let s1 = ca.field_by_name(&in1_str)?;
1509 let s2 = ca.field_by_name(&in2_str)?;
1510 let s3 = ca.field_by_name(&in3_str)?;
1511
1512 let ca1 = s1.f64()?;
1513 let ca2 = s2.f64()?;
1514 let ca3 = s3.f64()?;
1515
1516 let mut indicator = I::default();
1517 let mut values = Vec::with_capacity(s.len());
1518
1519 for i in 0..s.len() {
1520 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1521 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1522 let v3 = ca3.get(i).unwrap_or(f64::NAN);
1523 values.push(indicator.next((v1, v2, v3)));
1524 }
1525
1526 Ok(Some(Column::from(Series::new(
1527 output_name_for_closure.clone().into(),
1528 values,
1529 ))))
1530 },
1531 GetOutput::from_type(DataType::Float64),
1532 )
1533 .alias(&output_name_str)],
1534 )
1535 }
1536
1537 fn ta_4_in_1_out_default<I>(
1538 self,
1539 in1: &str,
1540 in2: &str,
1541 in3: &str,
1542 in4: &str,
1543 output_name: &str,
1544 ) -> LazyFrame
1545 where
1546 I: Next<(f64, f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
1547 {
1548 let in1_str = in1.to_string();
1549 let in2_str = in2.to_string();
1550 let in3_str = in3.to_string();
1551 let in4_str = in4.to_string();
1552 let output_name_str = output_name.to_string();
1553 let output_name_for_closure = output_name_str.clone();
1554 self.0.clone().with_columns([as_struct(vec![
1555 col(&in1_str),
1556 col(&in2_str),
1557 col(&in3_str),
1558 col(&in4_str),
1559 ])
1560 .map(
1561 move |s| {
1562 let ca = s.struct_()?;
1563 let s1 = ca.field_by_name(&in1_str)?;
1564 let s2 = ca.field_by_name(&in2_str)?;
1565 let s3 = ca.field_by_name(&in3_str)?;
1566 let s4 = ca.field_by_name(&in4_str)?;
1567
1568 let ca1 = s1.f64()?;
1569 let ca2 = s2.f64()?;
1570 let ca3 = s3.f64()?;
1571 let ca4 = s4.f64()?;
1572
1573 let mut indicator = I::default();
1574 let mut values = Vec::with_capacity(s.len());
1575
1576 for i in 0..s.len() {
1577 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1578 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1579 let v3 = ca3.get(i).unwrap_or(f64::NAN);
1580 let v4 = ca4.get(i).unwrap_or(f64::NAN);
1581 values.push(indicator.next((v1, v2, v3, v4)));
1582 }
1583
1584 Ok(Some(Column::from(Series::new(
1585 output_name_for_closure.clone().into(),
1586 values,
1587 ))))
1588 },
1589 GetOutput::from_type(DataType::Float64),
1590 )
1591 .alias(&output_name_str)])
1592 }
1593
1594 fn math_transform_1_in_1_out<I>(self, name: &str, output_name: &str) -> LazyFrame
1595 where
1596 I: Next<f64, Output = f64> + Default + Send + Sync + 'static,
1597 {
1598 let name = name.to_string();
1599 let output_name_str = output_name.to_string();
1600 let output_name_for_closure = output_name_str.clone();
1601 self.0.clone().with_columns([col(&name)
1602 .map(
1603 move |s| {
1604 let ca = s.f64()?;
1605 let mut indicator = I::default();
1606 let mut values = Vec::with_capacity(s.len());
1607
1608 for i in 0..s.len() {
1609 let val = ca.get(i).unwrap_or(f64::NAN);
1610 values.push(indicator.next(val));
1611 }
1612
1613 Ok(Some(Column::from(Series::new(
1614 output_name_for_closure.clone().into(),
1615 values,
1616 ))))
1617 },
1618 GetOutput::from_type(DataType::Float64),
1619 )
1620 .alias(&output_name_str)])
1621 }
1622
1623 fn math_operator_2_in_1_out<I>(self, in1: &str, in2: &str, output_name: &str) -> LazyFrame
1624 where
1625 I: Next<(f64, f64), Output = f64> + Default + Send + Sync + 'static,
1626 {
1627 let in1_str = in1.to_string();
1628 let in2_str = in2.to_string();
1629 let output_name_str = output_name.to_string();
1630 let output_name_for_closure = output_name_str.clone();
1631 self.0
1632 .clone()
1633 .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1634 .map(
1635 move |s| {
1636 let ca = s.struct_()?;
1637 let s1 = ca.field_by_name(&in1_str)?;
1638 let s2 = ca.field_by_name(&in2_str)?;
1639
1640 let ca1 = s1.f64()?;
1641 let ca2 = s2.f64()?;
1642
1643 let mut indicator = I::default();
1644 let mut values = Vec::with_capacity(s.len());
1645
1646 for i in 0..s.len() {
1647 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1648 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1649 values.push(indicator.next((v1, v2)));
1650 }
1651
1652 Ok(Some(Column::from(Series::new(
1653 output_name_for_closure.clone().into(),
1654 values,
1655 ))))
1656 },
1657 GetOutput::from_type(DataType::Float64),
1658 )
1659 .alias(&output_name_str)])
1660 }
1661
1662 fn math_operator_1_in_1_out_period<I>(
1663 self,
1664 name: &str,
1665 period: usize,
1666 output_name: &str,
1667 ) -> LazyFrame
1668 where
1669 I: Next<f64, Output = f64> + Send + Sync + 'static,
1670 I: From<usize>,
1671 {
1672 let name = name.to_string();
1673 let output_name_str = output_name.to_string();
1674 let output_name_for_closure = output_name_str.clone();
1675 self.0.clone().with_columns([col(&name)
1676 .map(
1677 move |s| {
1678 let ca = s.f64()?;
1679 let mut indicator = I::from(period);
1680 let mut values = Vec::with_capacity(s.len());
1681
1682 for i in 0..s.len() {
1683 let val = ca.get(i).unwrap_or(f64::NAN);
1684 values.push(indicator.next(val));
1685 }
1686
1687 Ok(Some(Column::from(Series::new(
1688 output_name_for_closure.clone().into(),
1689 values,
1690 ))))
1691 },
1692 GetOutput::from_type(DataType::Float64),
1693 )
1694 .alias(&output_name_str)])
1695 }
1696
1697 fn math_operator_2_in_1_out_period<I>(
1698 self,
1699 in1: &str,
1700 in2: &str,
1701 period: usize,
1702 output_name: &str,
1703 ) -> LazyFrame
1704 where
1705 I: Next<(f64, f64), Output = f64> + Send + Sync + 'static,
1706 I: From<usize>,
1707 {
1708 let in1_str = in1.to_string();
1709 let in2_str = in2.to_string();
1710 let output_name_str = output_name.to_string();
1711 let output_name_for_closure = output_name_str.clone();
1712 self.0
1713 .clone()
1714 .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1715 .map(
1716 move |s| {
1717 let ca = s.struct_()?;
1718 let s1 = ca.field_by_name(&in1_str)?;
1719 let s2 = ca.field_by_name(&in2_str)?;
1720
1721 let ca1 = s1.f64()?;
1722 let ca2 = s2.f64()?;
1723
1724 let mut indicator = I::from(period);
1725 let mut values = Vec::with_capacity(s.len());
1726
1727 for i in 0..s.len() {
1728 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1729 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1730 values.push(indicator.next((v1, v2)));
1731 }
1732
1733 Ok(Some(Column::from(Series::new(
1734 output_name_for_closure.clone().into(),
1735 values,
1736 ))))
1737 },
1738 GetOutput::from_type(DataType::Float64),
1739 )
1740 .alias(&output_name_str)])
1741 }
1742
1743 pub fn supertrend(self, period: usize, multiplier: f64) -> LazyFrame {
1744 self.0
1745 .clone()
1746 .with_columns([as_struct(vec![col("high"), col("low"), col("close")])
1747 .map(
1748 move |s| {
1749 let ca = s.struct_()?;
1750 let s_high = ca.field_by_name("high")?;
1751 let s_low = ca.field_by_name("low")?;
1752 let s_close = ca.field_by_name("close")?;
1753
1754 let high = s_high.f64()?;
1755 let low = s_low.f64()?;
1756 let close = s_close.f64()?;
1757
1758 let mut st = SuperTrend::new(period, multiplier);
1759 let mut values = Vec::with_capacity(s.len());
1760 let mut directions = Vec::with_capacity(s.len());
1761
1762 for i in 0..s.len() {
1763 let h = high.get(i).unwrap_or(0.0);
1764 let l = low.get(i).unwrap_or(0.0);
1765 let c = close.get(i).unwrap_or(0.0);
1766 let (val, dir) = st.next((h, l, c));
1767 values.push(val);
1768 directions.push(dir as f64);
1769 }
1770
1771 let st_series = Series::new("supertrend".into(), values);
1772 let dir_series = Series::new("supertrend_direction".into(), directions);
1773
1774 let out = StructChunked::from_series(
1775 "supertrend_output".into(),
1776 s.len(),
1777 [st_series, dir_series].iter(),
1778 )?;
1779 Ok(Some(Column::from(out.into_series())))
1780 },
1781 GetOutput::from_type(DataType::Struct(vec![
1782 Field::new("supertrend".into(), DataType::Float64),
1783 Field::new("supertrend_direction".into(), DataType::Float64),
1784 ])),
1785 )
1786 .alias("supertrend_data")])
1787 }
1788
1789 pub fn anchored_vwap(self, price: &str, volume: &str, anchor: &str) -> LazyFrame {
1802 let price = price.to_string();
1803 let volume = volume.to_string();
1804 let anchor = anchor.to_string();
1805
1806 self.0
1807 .clone()
1808 .with_columns([as_struct(vec![col(&price), col(&volume), col(&anchor)])
1809 .map(
1810 move |s| {
1811 let ca = s.struct_()?;
1812 let s_price = ca.field_by_name(&price)?;
1813 let s_volume = ca.field_by_name(&volume)?;
1814 let s_anchor = ca.field_by_name(&anchor)?;
1815
1816 let price = s_price.f64()?;
1817 let volume = s_volume.f64()?;
1818 let anchor = s_anchor.bool()?;
1819
1820 let mut avwap = quantwave_core::AnchoredVWAP::new();
1821 let mut values = Vec::with_capacity(s.len());
1822
1823 for i in 0..s.len() {
1824 let p = price.get(i).unwrap_or(0.0);
1825 let v = volume.get(i).unwrap_or(0.0);
1826 let a = anchor.get(i).unwrap_or(false);
1827 values.push(avwap.next((p, v, a)));
1828 }
1829
1830 Ok(Some(Column::from(Series::new(
1831 "anchored_vwap".into(),
1832 values,
1833 ))))
1834 },
1835 GetOutput::from_type(DataType::Float64),
1836 )
1837 .alias("avwap")])
1838 }
1839
1840 pub fn hma(self, name: &str, period: usize) -> LazyFrame {
1841 let name = name.to_string();
1842 self.0.clone().with_columns([col(&name)
1843 .map(
1844 move |s| {
1845 let ca = s.f64()?;
1846 let mut hma = quantwave_core::HMA::new(period);
1847 let mut values = Vec::with_capacity(s.len());
1848
1849 for i in 0..s.len() {
1850 let val = ca.get(i).unwrap_or(0.0);
1851 values.push(hma.next(val));
1852 }
1853
1854 Ok(Some(Column::from(Series::new("hma".into(), values))))
1855 },
1856 GetOutput::from_type(DataType::Float64),
1857 )
1858 .alias("hma")])
1859 }
1860
1861 pub fn kalman(self, name: &str, q: f64, r: f64) -> LazyFrame {
1862 let name = name.to_string();
1863 self.0.clone().with_columns([col(&name)
1864 .map(
1865 move |s| {
1866 let ca = s.f64()?;
1867 let mut indicator =
1868 quantwave_core::indicators::kalman::KalmanFilter::new(q, r);
1869 let mut values = Vec::with_capacity(s.len());
1870
1871 for i in 0..s.len() {
1872 let val = ca.get(i).unwrap_or(f64::NAN);
1873 values.push(indicator.next(val));
1874 }
1875
1876 Ok(Some(Column::from(Series::new("kalman".into(), values))))
1877 },
1878 GetOutput::from_type(DataType::Float64),
1879 )
1880 .alias("kalman")])
1881 }
1882
1883 pub fn kinematic_kalman(self, name: &str, q_pos: f64, q_vel: f64, r: f64) -> LazyFrame {
1884 let name = name.to_string();
1885 self.0.clone().with_columns([col(&name)
1886 .map(
1887 move |s| {
1888 let ca = s.f64()?;
1889 let mut indicator =
1890 quantwave_core::indicators::kinematic_kalman::KinematicKalmanFilter::new(
1891 q_pos, q_vel, r,
1892 );
1893 let mut values = Vec::with_capacity(s.len());
1894
1895 for i in 0..s.len() {
1896 let val = ca.get(i).unwrap_or(f64::NAN);
1897 values.push(indicator.next(val));
1898 }
1899
1900 Ok(Some(Column::from(Series::new(
1901 "kinematic_kalman".into(),
1902 values,
1903 ))))
1904 },
1905 GetOutput::from_type(DataType::Float64),
1906 )
1907 .alias("kinematic_kalman")])
1908 }
1909
1910 pub fn vpn(
1911 self,
1912 high: &str,
1913 low: &str,
1914 close: &str,
1915 volume: &str,
1916 period: usize,
1917 smooth_period: usize,
1918 ) -> LazyFrame {
1919 let high_str = high.to_string();
1920 let low_str = low.to_string();
1921 let close_str = close.to_string();
1922 let volume_str = volume.to_string();
1923
1924 self.0.clone().with_columns([as_struct(vec![
1925 col(&high_str),
1926 col(&low_str),
1927 col(&close_str),
1928 col(&volume_str),
1929 ])
1930 .map(
1931 move |s| {
1932 let ca = s.struct_()?;
1933 let s_h = ca.field_by_name(&high_str)?;
1934 let s_l = ca.field_by_name(&low_str)?;
1935 let s_c = ca.field_by_name(&close_str)?;
1936 let s_v = ca.field_by_name(&volume_str)?;
1937
1938 let high = s_h.f64()?;
1939 let low = s_l.f64()?;
1940 let close = s_c.f64()?;
1941 let volume = s_v.f64()?;
1942
1943 let mut indicator = quantwave_core::VPNIndicator::new(period, smooth_period);
1944 let mut values = Vec::with_capacity(s.len());
1945
1946 for i in 0..s.len() {
1947 let h = high.get(i).unwrap_or(f64::NAN);
1948 let l = low.get(i).unwrap_or(f64::NAN);
1949 let c = close.get(i).unwrap_or(f64::NAN);
1950 let v = volume.get(i).unwrap_or(f64::NAN);
1951 values.push(indicator.next((h, l, c, v)));
1952 }
1953
1954 Ok(Some(Column::from(Series::new("vpn".into(), values))))
1955 },
1956 GetOutput::from_type(DataType::Float64),
1957 )
1958 .alias("vpn")])
1959 }
1960
1961 pub fn gap_momentum(
1962 self,
1963 open: &str,
1964 close: &str,
1965 period: usize,
1966 signal_period: usize,
1967 ) -> LazyFrame {
1968 let open_str = open.to_string();
1969 let close_str = close.to_string();
1970
1971 self.0.clone().with_columns([as_struct(vec![
1972 col(&open_str),
1973 col(&close_str),
1974 ])
1975 .map(
1976 move |s| {
1977 let ca = s.struct_()?;
1978 let s_o = ca.field_by_name(&open_str)?;
1979 let s_c = ca.field_by_name(&close_str)?;
1980
1981 let open = s_o.f64()?;
1982 let close = s_c.f64()?;
1983
1984 let mut indicator = quantwave_core::GapMomentum::new(period, signal_period);
1985 let mut ratio_vals = Vec::with_capacity(s.len());
1986 let mut signal_vals = Vec::with_capacity(s.len());
1987
1988 for i in 0..s.len() {
1989 let o = open.get(i).unwrap_or(f64::NAN);
1990 let c = close.get(i).unwrap_or(f64::NAN);
1991 let (ratio, signal) = indicator.next((o, c));
1992 ratio_vals.push(ratio);
1993 signal_vals.push(signal);
1994 }
1995
1996 let s_ratio = Series::new("gap_ratio".into(), ratio_vals);
1997 let s_signal = Series::new("gap_signal".into(), signal_vals);
1998 let struct_series = StructChunked::from_series(
1999 "gap_momentum_result".into(),
2000 s.len(),
2001 [s_ratio, s_signal].iter(),
2002 )?;
2003 Ok(Some(Column::from(struct_series.into_series())))
2004 },
2005 GetOutput::from_type(DataType::Struct(vec![
2006 Field::new("gap_ratio".into(), DataType::Float64),
2007 Field::new("gap_signal".into(), DataType::Float64),
2008 ])),
2009 )
2010 .alias("gap_momentum")])
2011 }
2012
2013 pub fn autotune_filter(self, name: &str, window: usize, bandwidth: f64) -> LazyFrame {
2014 let name_str = name.to_string();
2015 self.0.clone().with_columns([col(&name_str)
2016 .map(
2017 move |s| {
2018 let ca = s.f64()?;
2019 let mut indicator = quantwave_core::AutoTuneFilter::new(window, bandwidth);
2020 let mut values = Vec::with_capacity(s.len());
2021
2022 for i in 0..s.len() {
2023 let val = ca.get(i).unwrap_or(f64::NAN);
2024 values.push(indicator.next(val));
2025 }
2026
2027 Ok(Some(Column::from(Series::new("autotune".into(), values))))
2028 },
2029 GetOutput::from_type(DataType::Float64),
2030 )
2031 .alias("autotune")])
2032 }
2033
2034 pub fn adaptive_ema(
2035 self,
2036 high: &str,
2037 low: &str,
2038 close: &str,
2039 period: usize,
2040 pds: usize,
2041 ) -> LazyFrame {
2042 let h_str = high.to_string();
2043 let l_str = low.to_string();
2044 let c_str = close.to_string();
2045
2046 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2047 .map(
2048 move |s| {
2049 let ca = s.struct_()?;
2050 let f_h = ca.field_by_name(&h_str)?;
2051 let high = f_h.f64()?;
2052 let f_l = ca.field_by_name(&l_str)?;
2053 let low = f_l.f64()?;
2054 let f_c = ca.field_by_name(&c_str)?;
2055 let close = f_c.f64()?;
2056
2057 let mut indicator = quantwave_core::AdaptiveEMA::new(period, pds);
2058 let mut values = Vec::with_capacity(s.len());
2059
2060 for i in 0..s.len() {
2061 let h = high.get(i).unwrap_or(f64::NAN);
2062 let l = low.get(i).unwrap_or(f64::NAN);
2063 let c = close.get(i).unwrap_or(f64::NAN);
2064 values.push(indicator.next((h, l, c)));
2065 }
2066
2067 Ok(Some(Column::from(Series::new("adaptive_ema".into(), values))))
2068 },
2069 GetOutput::from_type(DataType::Float64),
2070 )
2071 .alias("adaptive_ema")])
2072 }
2073
2074 pub fn tradj_ema(
2075 self,
2076 high: &str,
2077 low: &str,
2078 close: &str,
2079 period: usize,
2080 pds: usize,
2081 mltp: f64,
2082 ) -> LazyFrame {
2083 let h_str = high.to_string();
2084 let l_str = low.to_string();
2085 let c_str = close.to_string();
2086
2087 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2088 .map(
2089 move |s| {
2090 let ca = s.struct_()?;
2091 let f_h = ca.field_by_name(&h_str)?;
2092 let high = f_h.f64()?;
2093 let f_l = ca.field_by_name(&l_str)?;
2094 let low = f_l.f64()?;
2095 let f_c = ca.field_by_name(&c_str)?;
2096 let close = f_c.f64()?;
2097
2098 let mut indicator = quantwave_core::TRAdjEMA::new(period, pds, mltp);
2099 let mut values = Vec::with_capacity(s.len());
2100
2101 for i in 0..s.len() {
2102 let h = high.get(i).unwrap_or(f64::NAN);
2103 let l = low.get(i).unwrap_or(f64::NAN);
2104 let c = close.get(i).unwrap_or(f64::NAN);
2105 values.push(indicator.next((h, l, c)));
2106 }
2107
2108 Ok(Some(Column::from(Series::new("tradj_ema".into(), values))))
2109 },
2110 GetOutput::from_type(DataType::Float64),
2111 )
2112 .alias("tradj_ema")])
2113 }
2114
2115 pub fn obvm(
2116 self,
2117 high: &str,
2118 low: &str,
2119 close: &str,
2120 volume: &str,
2121 obvm_period: usize,
2122 signal_period: usize,
2123 ) -> LazyFrame {
2124 let h_str = high.to_string();
2125 let l_str = low.to_string();
2126 let c_str = close.to_string();
2127 let v_str = volume.to_string();
2128
2129 self.0.clone().with_columns([as_struct(vec![
2130 col(&h_str),
2131 col(&l_str),
2132 col(&c_str),
2133 col(&v_str),
2134 ])
2135 .map(
2136 move |s| {
2137 let ca = s.struct_()?;
2138 let f_h = ca.field_by_name(&h_str)?;
2139 let high = f_h.f64()?;
2140 let f_l = ca.field_by_name(&l_str)?;
2141 let low = f_l.f64()?;
2142 let f_c = ca.field_by_name(&c_str)?;
2143 let close = f_c.f64()?;
2144 let f_v = ca.field_by_name(&v_str)?;
2145 let volume = f_v.f64()?;
2146
2147 let mut indicator = quantwave_core::Obvm::new(obvm_period, signal_period);
2148 let mut obvm_vals = Vec::with_capacity(s.len());
2149 let mut signal_vals = Vec::with_capacity(s.len());
2150
2151 for i in 0..s.len() {
2152 let h = high.get(i).unwrap_or(f64::NAN);
2153 let l = low.get(i).unwrap_or(f64::NAN);
2154 let c = close.get(i).unwrap_or(f64::NAN);
2155 let v = volume.get(i).unwrap_or(f64::NAN);
2156 let (o, sig) = indicator.next((h, l, c, v));
2157 obvm_vals.push(o);
2158 signal_vals.push(sig);
2159 }
2160
2161 let s_obvm = Series::new("obvm".into(), obvm_vals);
2162 let s_signal = Series::new("signal".into(), signal_vals);
2163 let struct_series = StructChunked::from_series(
2164 "obvm_data".into(),
2165 s.len(),
2166 [s_obvm, s_signal].iter(),
2167 )?;
2168 Ok(Some(Column::from(struct_series.into_series())))
2169 },
2170 GetOutput::from_type(DataType::Struct(vec![
2171 Field::new("obvm".into(), DataType::Float64),
2172 Field::new("signal".into(), DataType::Float64),
2173 ])),
2174 )
2175 .alias("obvm_data")])
2176 }
2177
2178 pub fn vfi(
2179 self,
2180 high: &str,
2181 low: &str,
2182 close: &str,
2183 volume: &str,
2184 period: usize,
2185 coef: f64,
2186 vcoef: f64,
2187 smoothing_period: usize,
2188 ) -> LazyFrame {
2189 let h_str = high.to_string();
2190 let l_str = low.to_string();
2191 let c_str = close.to_string();
2192 let v_str = volume.to_string();
2193
2194 self.0.clone().with_columns([as_struct(vec![
2195 col(&h_str),
2196 col(&l_str),
2197 col(&c_str),
2198 col(&v_str),
2199 ])
2200 .map(
2201 move |s| {
2202 let ca = s.struct_()?;
2203 let f_h = ca.field_by_name(&h_str)?;
2204 let high = f_h.f64()?;
2205 let f_l = ca.field_by_name(&l_str)?;
2206 let low = f_l.f64()?;
2207 let f_c = ca.field_by_name(&c_str)?;
2208 let close = f_c.f64()?;
2209 let f_v = ca.field_by_name(&v_str)?;
2210 let volume = f_v.f64()?;
2211
2212 let mut indicator = quantwave_core::Vfi::new(period, coef, vcoef, smoothing_period);
2213 let mut values = Vec::with_capacity(s.len());
2214
2215 for i in 0..s.len() {
2216 let h = high.get(i).unwrap_or(f64::NAN);
2217 let l = low.get(i).unwrap_or(f64::NAN);
2218 let c = close.get(i).unwrap_or(f64::NAN);
2219 let v = volume.get(i).unwrap_or(f64::NAN);
2220 values.push(indicator.next((h, l, c, v)));
2221 }
2222
2223 Ok(Some(Column::from(Series::new("vfi".into(), values))))
2224 },
2225 GetOutput::from_type(DataType::Float64),
2226 )
2227 .alias("vfi")])
2228 }
2229
2230 pub fn sve_volatility_bands(
2231 self,
2232 high: &str,
2233 low: &str,
2234 close: &str,
2235 bands_period: usize,
2236 bands_deviation: f64,
2237 low_band_adjust: f64,
2238 mid_line_length: usize,
2239 ) -> LazyFrame {
2240 let h_str = high.to_string();
2241 let l_str = low.to_string();
2242 let c_str = close.to_string();
2243
2244 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2245 .map(
2246 move |s| {
2247 let ca = s.struct_()?;
2248 let f_h = ca.field_by_name(&h_str)?;
2249 let high = f_h.f64()?;
2250 let f_l = ca.field_by_name(&l_str)?;
2251 let low = f_l.f64()?;
2252 let f_c = ca.field_by_name(&c_str)?;
2253 let close = f_c.f64()?;
2254
2255 let mut indicator = quantwave_core::SVEVolatilityBands::new(
2256 bands_period,
2257 bands_deviation,
2258 low_band_adjust,
2259 mid_line_length,
2260 );
2261 let mut upper_vals = Vec::with_capacity(s.len());
2262 let mut mid_vals = Vec::with_capacity(s.len());
2263 let mut lower_vals = Vec::with_capacity(s.len());
2264
2265 for i in 0..s.len() {
2266 let h = high.get(i).unwrap_or(f64::NAN);
2267 let l = low.get(i).unwrap_or(f64::NAN);
2268 let c = close.get(i).unwrap_or(f64::NAN);
2269 let (upper, mid, lower) = indicator.next((h, l, c));
2270 upper_vals.push(upper);
2271 mid_vals.push(mid);
2272 lower_vals.push(lower);
2273 }
2274
2275 let s_upper = Series::new("upper".into(), upper_vals);
2276 let s_mid = Series::new("middle".into(), mid_vals);
2277 let s_lower = Series::new("lower".into(), lower_vals);
2278 let struct_series = StructChunked::from_series(
2279 "sve_bands_data".into(),
2280 s.len(),
2281 [s_upper, s_mid, s_lower].iter(),
2282 )?;
2283 Ok(Some(Column::from(struct_series.into_series())))
2284 },
2285 GetOutput::from_type(DataType::Struct(vec![
2286 Field::new("upper".into(), DataType::Float64),
2287 Field::new("middle".into(), DataType::Float64),
2288 Field::new("lower".into(), DataType::Float64),
2289 ])),
2290 )
2291 .alias("sve_bands_data")])
2292 }
2293
2294 pub fn exp_dev_bands(
2295 self,
2296 name: &str,
2297 period: usize,
2298 multiplier: f64,
2299 use_sma: bool,
2300 ) -> LazyFrame {
2301 let name_str = name.to_string();
2302 self.0.clone().with_columns([col(&name_str)
2303 .map(
2304 move |s| {
2305 let ca = s.f64()?;
2306 let mut indicator = quantwave_core::ExpDevBands::new(period, multiplier, use_sma);
2307 let mut upper_vals = Vec::with_capacity(s.len());
2308 let mut mid_vals = Vec::with_capacity(s.len());
2309 let mut lower_vals = Vec::with_capacity(s.len());
2310
2311 for i in 0..s.len() {
2312 let val = ca.get(i).unwrap_or(f64::NAN);
2313 let (upper, mid, lower) = indicator.next(val);
2314 upper_vals.push(upper);
2315 mid_vals.push(mid);
2316 lower_vals.push(lower);
2317 }
2318
2319 let s_upper = Series::new("upper".into(), upper_vals);
2320 let s_mid = Series::new("middle".into(), mid_vals);
2321 let s_lower = Series::new("lower".into(), lower_vals);
2322 let struct_series = StructChunked::from_series(
2323 "exp_dev_bands_data".into(),
2324 s.len(),
2325 [s_upper, s_mid, s_lower].iter(),
2326 )?;
2327 Ok(Some(Column::from(struct_series.into_series())))
2328 },
2329 GetOutput::from_type(DataType::Struct(vec![
2330 Field::new("upper".into(), DataType::Float64),
2331 Field::new("middle".into(), DataType::Float64),
2332 Field::new("lower".into(), DataType::Float64),
2333 ])),
2334 )
2335 .alias("exp_dev_bands_data")])
2336 }
2337
2338 pub fn sdo(
2339 self,
2340 name: &str,
2341 lookback_period: usize,
2342 period: usize,
2343 ema_pds: usize,
2344 ) -> LazyFrame {
2345 let name_str = name.to_string();
2346 self.0.clone().with_columns([col(&name_str)
2347 .map(
2348 move |s| {
2349 let ca = s.f64()?;
2350 let mut indicator = quantwave_core::SDO::new(lookback_period, period, ema_pds);
2351 let mut values = Vec::with_capacity(s.len());
2352
2353 for i in 0..s.len() {
2354 let val = ca.get(i).unwrap_or(f64::NAN);
2355 values.push(indicator.next(val));
2356 }
2357
2358 Ok(Some(Column::from(Series::new("sdo".into(), values))))
2359 },
2360 GetOutput::from_type(DataType::Float64),
2361 )
2362 .alias("sdo")])
2363 }
2364
2365 pub fn rsmk(self, price: &str, benchmark: &str, length: usize, ema_length: usize) -> LazyFrame {
2366 let p_str = price.to_string();
2367 let b_str = benchmark.to_string();
2368
2369 self.0.clone().with_columns([as_struct(vec![col(&p_str), col(&b_str)])
2370 .map(
2371 move |s| {
2372 let ca = s.struct_()?;
2373 let f_p = ca.field_by_name(&p_str)?;
2374 let price = f_p.f64()?;
2375 let f_b = ca.field_by_name(&b_str)?;
2376 let benchmark = f_b.f64()?;
2377
2378 let mut indicator = quantwave_core::RSMK::new(length, ema_length);
2379 let mut values = Vec::with_capacity(s.len());
2380
2381 for i in 0..s.len() {
2382 let p = price.get(i).unwrap_or(f64::NAN);
2383 let b = benchmark.get(i).unwrap_or(f64::NAN);
2384 values.push(indicator.next((p, b)));
2385 }
2386
2387 Ok(Some(Column::from(Series::new("rsmk".into(), values))))
2388 },
2389 GetOutput::from_type(DataType::Float64),
2390 )
2391 .alias("rsmk")])
2392 }
2393
2394 pub fn rodc(
2395 self,
2396 name: &str,
2397 window_size: usize,
2398 threshold: f64,
2399 smooth_period: usize,
2400 ) -> LazyFrame {
2401 let name_str = name.to_string();
2402 self.0.clone().with_columns([col(&name_str)
2403 .map(
2404 move |s| {
2405 let ca = s.f64()?;
2406 let mut indicator = quantwave_core::RODC::new(window_size, threshold, smooth_period);
2407 let mut values = Vec::with_capacity(s.len());
2408
2409 for i in 0..s.len() {
2410 let val = ca.get(i).unwrap_or(f64::NAN);
2411 values.push(indicator.next(val));
2412 }
2413
2414 Ok(Some(Column::from(Series::new("rodc".into(), values))))
2415 },
2416 GetOutput::from_type(DataType::Float64),
2417 )
2418 .alias("rodc")])
2419 }
2420
2421 pub fn reverse_ema(self, name: &str, alpha: f64) -> LazyFrame {
2422 let name_str = name.to_string();
2423 self.0.clone().with_columns([col(&name_str)
2424 .map(
2425 move |s| {
2426 let ca = s.f64()?;
2427 let mut indicator = quantwave_core::ReverseEMA::new(alpha);
2428 let mut values = Vec::with_capacity(s.len());
2429
2430 for i in 0..s.len() {
2431 let val = ca.get(i).unwrap_or(f64::NAN);
2432 values.push(indicator.next(val));
2433 }
2434
2435 Ok(Some(Column::from(Series::new("reverse_ema".into(), values))))
2436 },
2437 GetOutput::from_type(DataType::Float64),
2438 )
2439 .alias("reverse_ema")])
2440 }
2441
2442 pub fn harrington_adx(
2443 self,
2444 high: &str,
2445 low: &str,
2446 close: &str,
2447 adx_length: usize,
2448 adx_smooth_length: usize,
2449 ) -> LazyFrame {
2450 let h_str = high.to_string();
2451 let l_str = low.to_string();
2452 let c_str = close.to_string();
2453
2454 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2455 .map(
2456 move |s| {
2457 let ca = s.struct_()?;
2458 let f_h = ca.field_by_name(&h_str)?;
2459 let high = f_h.f64()?;
2460 let f_l = ca.field_by_name(&l_str)?;
2461 let low = f_l.f64()?;
2462 let f_c = ca.field_by_name(&c_str)?;
2463 let close = f_c.f64()?;
2464
2465 let mut indicator = quantwave_core::HarringtonADXOscillator::new(adx_length, adx_smooth_length);
2466 let mut values = Vec::with_capacity(s.len());
2467
2468 for i in 0..s.len() {
2469 let h = high.get(i).unwrap_or(f64::NAN);
2470 let l = low.get(i).unwrap_or(f64::NAN);
2471 let c = close.get(i).unwrap_or(f64::NAN);
2472 values.push(indicator.next((h, l, c)));
2473 }
2474
2475 Ok(Some(Column::from(Series::new("harrington_adx".into(), values))))
2476 },
2477 GetOutput::from_type(DataType::Float64),
2478 )
2479 .alias("harrington_adx")])
2480 }
2481
2482 pub fn keltner_channels(
2483 self,
2484 high: &str,
2485 low: &str,
2486 close: &str,
2487 ema_period: usize,
2488 atr_period: usize,
2489 multiplier: f64,
2490 ) -> LazyFrame {
2491 let high = high.to_string();
2492 let low = low.to_string();
2493 let close = close.to_string();
2494
2495 self.0
2496 .clone()
2497 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2498 .map(
2499 move |s| {
2500 let ca = s.struct_()?;
2501 let s_high = ca.field_by_name(&high)?;
2502 let s_low = ca.field_by_name(&low)?;
2503 let s_close = ca.field_by_name(&close)?;
2504
2505 let high = s_high.f64()?;
2506 let low = s_low.f64()?;
2507 let close = s_close.f64()?;
2508
2509 let mut kc = quantwave_core::KeltnerChannels::new(
2510 ema_period, atr_period, multiplier,
2511 );
2512 let mut uppers = Vec::with_capacity(s.len());
2513 let mut middles = Vec::with_capacity(s.len());
2514 let mut lowers = Vec::with_capacity(s.len());
2515
2516 for i in 0..s.len() {
2517 let h = high.get(i).unwrap_or(0.0);
2518 let l = low.get(i).unwrap_or(0.0);
2519 let c = close.get(i).unwrap_or(0.0);
2520 let (upper, middle, lower) = kc.next((h, l, c));
2521 uppers.push(upper);
2522 middles.push(middle);
2523 lowers.push(lower);
2524 }
2525
2526 let upper_series = Series::new("upper".into(), uppers);
2527 let middle_series = Series::new("middle".into(), middles);
2528 let lower_series = Series::new("lower".into(), lowers);
2529
2530 let out = StructChunked::from_series(
2531 "keltner_output".into(),
2532 s.len(),
2533 [upper_series, middle_series, lower_series].iter(),
2534 )?;
2535 Ok(Some(Column::from(out.into_series())))
2536 },
2537 GetOutput::from_type(DataType::Struct(vec![
2538 Field::new("upper".into(), DataType::Float64),
2539 Field::new("middle".into(), DataType::Float64),
2540 Field::new("lower".into(), DataType::Float64),
2541 ])),
2542 )
2543 .alias("keltner_data")])
2544 }
2545
2546 pub fn alma(self, name: &str, period: usize, offset: f64, sigma: f64) -> LazyFrame {
2547 let name = name.to_string();
2548 self.0.clone().with_columns([col(&name)
2549 .map(
2550 move |s| {
2551 let ca = s.f64()?;
2552 let mut alma = quantwave_core::ALMA::new(period, offset, sigma);
2553 let mut values = Vec::with_capacity(s.len());
2554
2555 for i in 0..s.len() {
2556 let val = ca.get(i).unwrap_or(0.0);
2557 values.push(alma.next(val));
2558 }
2559
2560 Ok(Some(Column::from(Series::new("alma".into(), values))))
2561 },
2562 GetOutput::from_type(DataType::Float64),
2563 )
2564 .alias("alma")])
2565 }
2566
2567 pub fn donchian_channels(self, high: &str, low: &str, period: usize) -> LazyFrame {
2568 let high = high.to_string();
2569 let low = low.to_string();
2570
2571 self.0
2572 .clone()
2573 .with_columns([as_struct(vec![col(&high), col(&low)])
2574 .map(
2575 move |s| {
2576 let ca = s.struct_()?;
2577 let s_high = ca.field_by_name(&high)?;
2578 let s_low = ca.field_by_name(&low)?;
2579
2580 let high = s_high.f64()?;
2581 let low = s_low.f64()?;
2582
2583 let mut dc = quantwave_core::DonchianChannels::new(period);
2584 let mut uppers = Vec::with_capacity(s.len());
2585 let mut middles = Vec::with_capacity(s.len());
2586 let mut lowers = Vec::with_capacity(s.len());
2587
2588 for i in 0..s.len() {
2589 let h = high.get(i).unwrap_or(0.0);
2590 let l = low.get(i).unwrap_or(0.0);
2591 let (upper, middle, lower) = dc.next((h, l));
2592 uppers.push(upper);
2593 middles.push(middle);
2594 lowers.push(lower);
2595 }
2596
2597 let upper_series = Series::new("upper".into(), uppers);
2598 let middle_series = Series::new("middle".into(), middles);
2599 let lower_series = Series::new("lower".into(), lowers);
2600
2601 let out = StructChunked::from_series(
2602 "donchian_output".into(),
2603 s.len(),
2604 [upper_series, middle_series, lower_series].iter(),
2605 )?;
2606 Ok(Some(Column::from(out.into_series())))
2607 },
2608 GetOutput::from_type(DataType::Struct(vec![
2609 Field::new("upper".into(), DataType::Float64),
2610 Field::new("middle".into(), DataType::Float64),
2611 Field::new("lower".into(), DataType::Float64),
2612 ])),
2613 )
2614 .alias("donchian_data")])
2615 }
2616
2617 pub fn ttm_squeeze(
2618 self,
2619 high: &str,
2620 low: &str,
2621 close: &str,
2622 period: usize,
2623 multiplier_bb: f64,
2624 multiplier_kc: f64,
2625 ) -> LazyFrame {
2626 let high = high.to_string();
2627 let low = low.to_string();
2628 let close = close.to_string();
2629
2630 self.0
2631 .clone()
2632 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2633 .map(
2634 move |s| {
2635 let ca = s.struct_()?;
2636 let s_high = ca.field_by_name(&high)?;
2637 let s_low = ca.field_by_name(&low)?;
2638 let s_close = ca.field_by_name(&close)?;
2639
2640 let high = s_high.f64()?;
2641 let low = s_low.f64()?;
2642 let close = s_close.f64()?;
2643
2644 let mut ttm =
2645 quantwave_core::TTMSqueeze::new(period, multiplier_bb, multiplier_kc);
2646 let mut histograms = Vec::with_capacity(s.len());
2647 let mut squeezed = Vec::with_capacity(s.len());
2648
2649 for i in 0..s.len() {
2650 let h = high.get(i).unwrap_or(0.0);
2651 let l = low.get(i).unwrap_or(0.0);
2652 let c = close.get(i).unwrap_or(0.0);
2653 let (hist, is_sq) = ttm.next((h, l, c));
2654 histograms.push(hist);
2655 squeezed.push(is_sq);
2656 }
2657
2658 let hist_series = Series::new("histogram".into(), histograms);
2659 let squeezed_series = Series::new("is_squeezed".into(), squeezed);
2660
2661 let out = StructChunked::from_series(
2662 "ttm_squeeze_output".into(),
2663 s.len(),
2664 [hist_series, squeezed_series].iter(),
2665 )?;
2666 Ok(Some(Column::from(out.into_series())))
2667 },
2668 GetOutput::from_type(DataType::Struct(vec![
2669 Field::new("histogram".into(), DataType::Float64),
2670 Field::new("is_squeezed".into(), DataType::Boolean),
2671 ])),
2672 )
2673 .alias("ttm_squeeze_data")])
2674 }
2675
2676 pub fn vortex_indicator(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
2677 let high = high.to_string();
2678 let low = low.to_string();
2679 let close = close.to_string();
2680
2681 self.0
2682 .clone()
2683 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2684 .map(
2685 move |s| {
2686 let ca = s.struct_()?;
2687 let s_high = ca.field_by_name(&high)?;
2688 let s_low = ca.field_by_name(&low)?;
2689 let s_close = ca.field_by_name(&close)?;
2690
2691 let high = s_high.f64()?;
2692 let low = s_low.f64()?;
2693 let close = s_close.f64()?;
2694
2695 let mut vi = quantwave_core::VortexIndicator::new(period);
2696 let mut plus_vals = Vec::with_capacity(s.len());
2697 let mut minus_vals = Vec::with_capacity(s.len());
2698
2699 for i in 0..s.len() {
2700 let h = high.get(i).unwrap_or(0.0);
2701 let l = low.get(i).unwrap_or(0.0);
2702 let c = close.get(i).unwrap_or(0.0);
2703 let (plus, minus) = vi.next((h, l, c));
2704 plus_vals.push(plus);
2705 minus_vals.push(minus);
2706 }
2707
2708 let plus_series = Series::new("vi_plus".into(), plus_vals);
2709 let minus_series = Series::new("vi_minus".into(), minus_vals);
2710
2711 let out = StructChunked::from_series(
2712 "vortex_output".into(),
2713 s.len(),
2714 [plus_series, minus_series].iter(),
2715 )?;
2716 Ok(Some(Column::from(out.into_series())))
2717 },
2718 GetOutput::from_type(DataType::Struct(vec![
2719 Field::new("vi_plus".into(), DataType::Float64),
2720 Field::new("vi_minus".into(), DataType::Float64),
2721 ])),
2722 )
2723 .alias("vortex_data")])
2724 }
2725
2726 pub fn heikin_ashi(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
2727 let open = open.to_string();
2728 let high = high.to_string();
2729 let low = low.to_string();
2730 let close = close.to_string();
2731
2732 self.0.clone().with_columns([as_struct(vec![
2733 col(&open),
2734 col(&high),
2735 col(&low),
2736 col(&close),
2737 ])
2738 .map(
2739 move |s| {
2740 let ca = s.struct_()?;
2741 let s_open = ca.field_by_name(&open)?;
2742 let s_high = ca.field_by_name(&high)?;
2743 let s_low = ca.field_by_name(&low)?;
2744 let s_close = ca.field_by_name(&close)?;
2745
2746 let open = s_open.f64()?;
2747 let high = s_high.f64()?;
2748 let low = s_low.f64()?;
2749 let close = s_close.f64()?;
2750
2751 let mut ha = quantwave_core::HeikinAshi::new();
2752 let mut ha_opens = Vec::with_capacity(s.len());
2753 let mut ha_highs = Vec::with_capacity(s.len());
2754 let mut ha_lows = Vec::with_capacity(s.len());
2755 let mut ha_closes = Vec::with_capacity(s.len());
2756
2757 for i in 0..s.len() {
2758 let o = open.get(i).unwrap_or(0.0);
2759 let h = high.get(i).unwrap_or(0.0);
2760 let l = low.get(i).unwrap_or(0.0);
2761 let c = close.get(i).unwrap_or(0.0);
2762 let (ha_o, ha_h, ha_l, ha_c) = ha.next((o, h, l, c));
2763 ha_opens.push(ha_o);
2764 ha_highs.push(ha_h);
2765 ha_lows.push(ha_l);
2766 ha_closes.push(ha_c);
2767 }
2768
2769 let o_series = Series::new("ha_open".into(), ha_opens);
2770 let h_series = Series::new("ha_high".into(), ha_highs);
2771 let l_series = Series::new("ha_low".into(), ha_lows);
2772 let c_series = Series::new("ha_close".into(), ha_closes);
2773
2774 let out = StructChunked::from_series(
2775 "heikin_ashi_output".into(),
2776 s.len(),
2777 [o_series, h_series, l_series, c_series].iter(),
2778 )?;
2779 Ok(Some(Column::from(out.into_series())))
2780 },
2781 GetOutput::from_type(DataType::Struct(vec![
2782 Field::new("ha_open".into(), DataType::Float64),
2783 Field::new("ha_high".into(), DataType::Float64),
2784 Field::new("ha_low".into(), DataType::Float64),
2785 Field::new("ha_close".into(), DataType::Float64),
2786 ])),
2787 )
2788 .alias("heikin_ashi_data")])
2789 }
2790
2791 pub fn wavetrend(
2792 self,
2793 high: &str,
2794 low: &str,
2795 close: &str,
2796 n1: usize,
2797 n2: usize,
2798 n3: usize,
2799 ) -> LazyFrame {
2800 let high = high.to_string();
2801 let low = low.to_string();
2802 let close = close.to_string();
2803
2804 self.0
2805 .clone()
2806 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2807 .map(
2808 move |s| {
2809 let ca = s.struct_()?;
2810 let s_high = ca.field_by_name(&high)?;
2811 let s_low = ca.field_by_name(&low)?;
2812 let s_close = ca.field_by_name(&close)?;
2813
2814 let high = s_high.f64()?;
2815 let low = s_low.f64()?;
2816 let close = s_close.f64()?;
2817
2818 let mut wt = quantwave_core::WaveTrend::new(n1, n2, n3);
2819 let mut wt1_vals = Vec::with_capacity(s.len());
2820 let mut wt2_vals = Vec::with_capacity(s.len());
2821
2822 for i in 0..s.len() {
2823 let h = high.get(i).unwrap_or(0.0);
2824 let l = low.get(i).unwrap_or(0.0);
2825 let c = close.get(i).unwrap_or(0.0);
2826 let (wt1, wt2) = wt.next((h, l, c));
2827 wt1_vals.push(wt1);
2828 wt2_vals.push(wt2);
2829 }
2830
2831 let wt1_series = Series::new("wt1".into(), wt1_vals);
2832 let wt2_series = Series::new("wt2".into(), wt2_vals);
2833
2834 let out = StructChunked::from_series(
2835 "wavetrend_output".into(),
2836 s.len(),
2837 [wt1_series, wt2_series].iter(),
2838 )?;
2839 Ok(Some(Column::from(out.into_series())))
2840 },
2841 GetOutput::from_type(DataType::Struct(vec![
2842 Field::new("wt1".into(), DataType::Float64),
2843 Field::new("wt2".into(), DataType::Float64),
2844 ])),
2845 )
2846 .alias("wavetrend_data")])
2847 }
2848
2849 pub fn tema(self, name: &str, period: usize) -> LazyFrame {
2850 let name = name.to_string();
2851 self.0.clone().with_columns([col(&name)
2852 .map(
2853 move |s| {
2854 let ca = s.f64()?;
2855 let mut tema = quantwave_core::TEMA::new(period);
2856 let mut values = Vec::with_capacity(s.len());
2857
2858 for i in 0..s.len() {
2859 let val = ca.get(i).unwrap_or(0.0);
2860 values.push(tema.next(val));
2861 }
2862
2863 Ok(Some(Column::from(Series::new("tema".into(), values))))
2864 },
2865 GetOutput::from_type(DataType::Float64),
2866 )
2867 .alias("tema")])
2868 }
2869
2870 pub fn zlema(self, name: &str, period: usize) -> LazyFrame {
2871 let name = name.to_string();
2872 self.0.clone().with_columns([col(&name)
2873 .map(
2874 move |s| {
2875 let ca = s.f64()?;
2876 let mut zlema = quantwave_core::ZLEMA::new(period);
2877 let mut values = Vec::with_capacity(s.len());
2878
2879 for i in 0..s.len() {
2880 let val = ca.get(i).unwrap_or(0.0);
2881 values.push(zlema.next(val));
2882 }
2883
2884 Ok(Some(Column::from(Series::new("zlema".into(), values))))
2885 },
2886 GetOutput::from_type(DataType::Float64),
2887 )
2888 .alias("zlema")])
2889 }
2890
2891 pub fn atr_trailing_stop(
2892 self,
2893 high: &str,
2894 low: &str,
2895 close: &str,
2896 period: usize,
2897 multiplier: f64,
2898 ) -> LazyFrame {
2899 let high = high.to_string();
2900 let low = low.to_string();
2901 let close = close.to_string();
2902
2903 self.0
2904 .clone()
2905 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2906 .map(
2907 move |s| {
2908 let ca = s.struct_()?;
2909 let s_high = ca.field_by_name(&high)?;
2910 let s_low = ca.field_by_name(&low)?;
2911 let s_close = ca.field_by_name(&close)?;
2912
2913 let high = s_high.f64()?;
2914 let low = s_low.f64()?;
2915 let close = s_close.f64()?;
2916
2917 let mut atr_ts = quantwave_core::ATRTrailingStop::new(period, multiplier);
2918 let mut stops = Vec::with_capacity(s.len());
2919 let mut directions = Vec::with_capacity(s.len());
2920
2921 for i in 0..s.len() {
2922 let h = high.get(i).unwrap_or(0.0);
2923 let l = low.get(i).unwrap_or(0.0);
2924 let c = close.get(i).unwrap_or(0.0);
2925 let (stop, dir) = atr_ts.next((h, l, c));
2926 stops.push(stop);
2927 directions.push(dir as f64);
2928 }
2929
2930 let stop_series = Series::new("stop".into(), stops);
2931 let dir_series = Series::new("direction".into(), directions);
2932
2933 let out = StructChunked::from_series(
2934 "atr_ts_output".into(),
2935 s.len(),
2936 [stop_series, dir_series].iter(),
2937 )?;
2938 Ok(Some(Column::from(out.into_series())))
2939 },
2940 GetOutput::from_type(DataType::Struct(vec![
2941 Field::new("stop".into(), DataType::Float64),
2942 Field::new("direction".into(), DataType::Float64),
2943 ])),
2944 )
2945 .alias("atr_ts_data")])
2946 }
2947
2948 pub fn pivot_points(self, high: &str, low: &str, close: &str) -> LazyFrame {
2949 let high = high.to_string();
2950 let low = low.to_string();
2951 let close = close.to_string();
2952
2953 self.0
2954 .clone()
2955 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2956 .map(
2957 move |s| {
2958 let ca = s.struct_()?;
2959 let s_high = ca.field_by_name(&high)?;
2960 let s_low = ca.field_by_name(&low)?;
2961 let s_close = ca.field_by_name(&close)?;
2962
2963 let high = s_high.f64()?;
2964 let low = s_low.f64()?;
2965 let close = s_close.f64()?;
2966
2967 let mut pivot = quantwave_core::PivotPoints::new();
2968 let mut p_vals = Vec::with_capacity(s.len());
2969 let mut r1_vals = Vec::with_capacity(s.len());
2970 let mut s1_vals = Vec::with_capacity(s.len());
2971 let mut r2_vals = Vec::with_capacity(s.len());
2972 let mut s2_vals = Vec::with_capacity(s.len());
2973
2974 for i in 0..s.len() {
2975 let h = high.get(i).unwrap_or(0.0);
2976 let l = low.get(i).unwrap_or(0.0);
2977 let c = close.get(i).unwrap_or(0.0);
2978 let (p, r1, s1, r2, s2) = pivot.next((h, l, c));
2979 p_vals.push(p);
2980 r1_vals.push(r1);
2981 s1_vals.push(s1);
2982 r2_vals.push(r2);
2983 s2_vals.push(s2);
2984 }
2985
2986 let p_series = Series::new("p".into(), p_vals);
2987 let r1_series = Series::new("r1".into(), r1_vals);
2988 let s1_series = Series::new("s1".into(), s1_vals);
2989 let r2_series = Series::new("r2".into(), r2_vals);
2990 let s2_series = Series::new("s2".into(), s2_vals);
2991
2992 let out = StructChunked::from_series(
2993 "pivot_output".into(),
2994 s.len(),
2995 [p_series, r1_series, s1_series, r2_series, s2_series].iter(),
2996 )?;
2997 Ok(Some(Column::from(out.into_series())))
2998 },
2999 GetOutput::from_type(DataType::Struct(vec![
3000 Field::new("p".into(), DataType::Float64),
3001 Field::new("r1".into(), DataType::Float64),
3002 Field::new("s1".into(), DataType::Float64),
3003 Field::new("r2".into(), DataType::Float64),
3004 Field::new("s2".into(), DataType::Float64),
3005 ])),
3006 )
3007 .alias("pivot_points_data")])
3008 }
3009
3010 pub fn bill_williams_fractals(self, high: &str, low: &str) -> LazyFrame {
3011 let high = high.to_string();
3012 let low = low.to_string();
3013
3014 self.0
3015 .clone()
3016 .with_columns([as_struct(vec![col(&high), col(&low)])
3017 .map(
3018 move |s| {
3019 let ca = s.struct_()?;
3020 let s_high = ca.field_by_name(&high)?;
3021 let s_low = ca.field_by_name(&low)?;
3022
3023 let high = s_high.f64()?;
3024 let low = s_low.f64()?;
3025
3026 let mut fractals = quantwave_core::BillWilliamsFractals::new();
3027 let mut bearish_vals = Vec::with_capacity(s.len());
3028 let mut bullish_vals = Vec::with_capacity(s.len());
3029
3030 for i in 0..s.len() {
3031 let h = high.get(i).unwrap_or(0.0);
3032 let l = low.get(i).unwrap_or(0.0);
3033 let (bear, bull) = fractals.next((h, l));
3034 bearish_vals.push(bear);
3035 bullish_vals.push(bull);
3036 }
3037
3038 let bearish_series = Series::new("bearish".into(), bearish_vals);
3039 let bullish_series = Series::new("bullish".into(), bullish_vals);
3040
3041 let out = StructChunked::from_series(
3042 "fractals_output".into(),
3043 s.len(),
3044 [bearish_series, bullish_series].iter(),
3045 )?;
3046 Ok(Some(Column::from(out.into_series())))
3047 },
3048 GetOutput::from_type(DataType::Struct(vec![
3049 Field::new("bearish".into(), DataType::Boolean),
3050 Field::new("bullish".into(), DataType::Boolean),
3051 ])),
3052 )
3053 .alias("fractals_data")])
3054 }
3055
3056 pub fn market_structure(
3076 self,
3077 high: &str,
3078 low: &str,
3079 swing_strength: usize,
3080 ) -> LazyFrame {
3081 let high_str = high.to_string();
3082 let low_str = low.to_string();
3083 let strength = swing_strength;
3084
3085 self.0.clone().with_columns([as_struct(vec![col(&high_str), col(&low_str)])
3086 .map(
3087 move |s| {
3088 let ca = s.struct_()?;
3089 let s_h = ca.field_by_name(&high_str)?;
3090 let s_l = ca.field_by_name(&low_str)?;
3091 let highs = s_h.f64()?;
3092 let lows = s_l.f64()?;
3093
3094 let mut ms = quantwave_core::MarketStructure::new(strength);
3095 let n = s.len();
3096
3097 let mut bias_vals: Vec<u32> = Vec::with_capacity(n);
3098 let mut lh_p: Vec<f64> = Vec::with_capacity(n);
3099 let mut lh_b: Vec<u64> = Vec::with_capacity(n);
3100 let mut ll_p: Vec<f64> = Vec::with_capacity(n);
3101 let mut ll_b: Vec<u64> = Vec::with_capacity(n);
3102 let mut has_f: Vec<bool> = Vec::with_capacity(n);
3103 let mut f_bear: Vec<bool> = Vec::with_capacity(n);
3104 let mut f_p: Vec<f64> = Vec::with_capacity(n);
3105 let mut f_ba: Vec<u64> = Vec::with_capacity(n);
3106 let mut f_str: Vec<u32> = Vec::with_capacity(n);
3107 let mut depths: Vec<u32> = Vec::with_capacity(n);
3108 let mut bars: Vec<u64> = Vec::with_capacity(n);
3109
3110 for i in 0..n {
3111 let h = highs.get(i).unwrap_or(f64::NAN);
3112 let l = lows.get(i).unwrap_or(f64::NAN);
3113 let hh = if h.is_nan() || l.is_nan() { f64::NAN } else { h.max(l) };
3115 let ll = if h.is_nan() || l.is_nan() { f64::NAN } else { l.min(h) };
3116 let state = ms.next((hh, ll));
3117
3118 let b = match state.bias {
3119 quantwave_core::Bias::Neutral => 0u32,
3120 quantwave_core::Bias::Bullish => 1,
3121 quantwave_core::Bias::Bearish => 2,
3122 };
3123 bias_vals.push(b);
3124
3125 match &state.last_swing_high {
3126 Some(sh) => { lh_p.push(sh.price); lh_b.push(sh.bar as u64); }
3127 None => { lh_p.push(f64::NAN); lh_b.push(0); }
3128 }
3129 match &state.last_swing_low {
3130 Some(sl) => { ll_p.push(sl.price); ll_b.push(sl.bar as u64); }
3131 None => { ll_p.push(f64::NAN); ll_b.push(0); }
3132 }
3133
3134 if let Some(f) = &state.current_flip {
3135 has_f.push(true);
3136 f_bear.push(f.is_bearish);
3137 f_p.push(f.price);
3138 f_ba.push(f.bar as u64);
3139 f_str.push(f.structure_strength);
3140 } else {
3141 has_f.push(false);
3142 f_bear.push(false);
3143 f_p.push(f64::NAN);
3144 f_ba.push(0);
3145 f_str.push(0);
3146 }
3147
3148 depths.push(state.swing_depth_used as u32);
3149 bars.push(state.bar_index as u64);
3150 }
3151
3152 let s_bias = Series::new("bias".into(), bias_vals);
3153 let s_lhp = Series::new("last_high_price".into(), lh_p);
3154 let s_lhb = Series::new("last_high_bar".into(), lh_b);
3155 let s_llp = Series::new("last_low_price".into(), ll_p);
3156 let s_llb = Series::new("last_low_bar".into(), ll_b);
3157 let s_hasf = Series::new("has_flip".into(), has_f);
3158 let s_fb = Series::new("flip_bearish".into(), f_bear);
3159 let s_fp = Series::new("flip_price".into(), f_p);
3160 let s_fba = Series::new("flip_bar".into(), f_ba);
3161 let s_fstr = Series::new("flip_strength".into(), f_str);
3162 let s_dep = Series::new("swing_depth".into(), depths);
3163 let s_bar = Series::new("bar_index".into(), bars);
3164
3165 let struct_series = StructChunked::from_series(
3166 "market_structure_result".into(),
3167 s.len(),
3168 [
3169 s_bias, s_lhp, s_lhb, s_llp, s_llb, s_hasf, s_fb, s_fp, s_fba, s_fstr,
3170 s_dep, s_bar,
3171 ]
3172 .iter(),
3173 )?;
3174 Ok(Some(Column::from(struct_series.into_series())))
3175 },
3176 GetOutput::from_type(DataType::Struct(vec![
3177 Field::new("bias".into(), DataType::UInt32),
3178 Field::new("last_high_price".into(), DataType::Float64),
3179 Field::new("last_high_bar".into(), DataType::UInt64),
3180 Field::new("last_low_price".into(), DataType::Float64),
3181 Field::new("last_low_bar".into(), DataType::UInt64),
3182 Field::new("has_flip".into(), DataType::Boolean),
3183 Field::new("flip_bearish".into(), DataType::Boolean),
3184 Field::new("flip_price".into(), DataType::Float64),
3185 Field::new("flip_bar".into(), DataType::UInt64),
3186 Field::new("flip_strength".into(), DataType::UInt32),
3187 Field::new("swing_depth".into(), DataType::UInt32),
3188 Field::new("bar_index".into(), DataType::UInt64),
3189 ])),
3190 )
3191 .alias("market_structure")])
3192
3193 }
3194
3195 pub fn geometric_patterns(
3204 self,
3205 high: &str,
3206 low: &str,
3207 swing_strength: usize,
3208 ) -> LazyFrame {
3209 let high_str = high.to_string();
3210 let low_str = low.to_string();
3211 let strength = swing_strength;
3212
3213 self.0.clone().with_columns([as_struct(vec![col(&high_str), col(&low_str)])
3214 .map(
3215 move |s| {
3216 let ca = s.struct_()?;
3217 let s_h = ca.field_by_name(&high_str)?;
3218 let s_l = ca.field_by_name(&low_str)?;
3219 let highs = s_h.f64()?;
3220 let lows = s_l.f64()?;
3221
3222 let mut scanner = quantwave_core::GeometricPatternScanner::new(strength);
3223 let n = s.len();
3224
3225 let mut flag_ids: Vec<u32> = Vec::with_capacity(n);
3226 let mut flag_is_bull: Vec<bool> = Vec::with_capacity(n);
3227 let mut flag_pole_len: Vec<f64> = Vec::with_capacity(n);
3228 let mut flag_pole_atr: Vec<f64> = Vec::with_capacity(n);
3229 let mut flag_breakout: Vec<bool> = Vec::with_capacity(n);
3230 let mut flag_bp: Vec<f64> = Vec::with_capacity(n);
3231
3232 let mut hs_ids: Vec<u32> = Vec::with_capacity(n);
3233 let mut hs_bear: Vec<bool> = Vec::with_capacity(n);
3234 let mut hs_height: Vec<f64> = Vec::with_capacity(n);
3235 let mut hs_height_atr: Vec<f64> = Vec::with_capacity(n);
3236 let mut hs_score: Vec<f64> = Vec::with_capacity(n);
3237 let mut hs_breakout: Vec<bool> = Vec::with_capacity(n);
3238
3239 for i in 0..n {
3240 let h = highs.get(i).unwrap_or(f64::NAN);
3241 let l = lows.get(i).unwrap_or(f64::NAN);
3242 let hh = if h.is_nan() || l.is_nan() { f64::NAN } else { h.max(l) };
3243 let ll = if h.is_nan() || l.is_nan() { f64::NAN } else { l.min(h) };
3244 let (_state, flag, hs) = scanner.next((hh, ll));
3245
3246 if let Some(f) = flag {
3247 flag_ids.push(f.id);
3248 flag_is_bull.push(f.is_bull);
3249 flag_pole_len.push(f.pole_length);
3250 flag_pole_atr.push(f.pole_length_atr);
3251 flag_breakout.push(f.breakout_confirmed);
3252 flag_bp.push(f.breakout_price);
3253 } else {
3254 flag_ids.push(0);
3255 flag_is_bull.push(false);
3256 flag_pole_len.push(f64::NAN);
3257 flag_pole_atr.push(f64::NAN);
3258 flag_breakout.push(false);
3259 flag_bp.push(f64::NAN);
3260 }
3261
3262 if let Some(hp) = hs {
3263 hs_ids.push(hp.id);
3264 hs_bear.push(hp.is_bearish);
3265 hs_height.push(hp.height);
3266 hs_height_atr.push(hp.height_atr);
3267 hs_score.push(hp.score);
3268 hs_breakout.push(hp.breakout_confirmed);
3269 } else {
3270 hs_ids.push(0);
3271 hs_bear.push(false);
3272 hs_height.push(f64::NAN);
3273 hs_height_atr.push(f64::NAN);
3274 hs_score.push(f64::NAN);
3275 hs_breakout.push(false);
3276 }
3277 }
3278
3279 let s_fid = Series::new("id".into(), flag_ids);
3280 let s_fbull = Series::new("is_bull".into(), flag_is_bull);
3281 let s_fplen = Series::new("pole_length".into(), flag_pole_len);
3282 let s_fpatr = Series::new("pole_length_atr".into(), flag_pole_atr);
3283 let s_fbo = Series::new("breakout_confirmed".into(), flag_breakout);
3284 let s_fbp = Series::new("breakout_price".into(), flag_bp);
3285
3286 let flag_struct = StructChunked::from_series(
3287 "flag".into(),
3288 n,
3289 [s_fid, s_fbull, s_fplen, s_fpatr, s_fbo, s_fbp].iter(),
3290 )?;
3291
3292 let s_hid = Series::new("id".into(), hs_ids);
3293 let s_hbear = Series::new("is_bearish".into(), hs_bear);
3294 let s_hh = Series::new("height".into(), hs_height);
3295 let s_hhatr = Series::new("height_atr".into(), hs_height_atr);
3296 let s_hsc = Series::new("score".into(), hs_score);
3297 let s_hbo = Series::new("breakout_confirmed".into(), hs_breakout);
3298
3299 let hs_struct = StructChunked::from_series(
3300 "hs".into(),
3301 n,
3302 [s_hid, s_hbear, s_hh, s_hhatr, s_hsc, s_hbo].iter(),
3303 )?;
3304
3305 let combined = StructChunked::from_series(
3306 "geo_patterns".into(),
3307 n,
3308 [flag_struct.into_series(), hs_struct.into_series()].iter(),
3309 )?;
3310 Ok(Some(Column::from(combined.into_series())))
3311 },
3312 GetOutput::from_type(DataType::Struct(vec![
3313 Field::new("flag".into(), DataType::Struct(vec![
3314 Field::new("id".into(), DataType::UInt32),
3315 Field::new("is_bull".into(), DataType::Boolean),
3316 Field::new("pole_length".into(), DataType::Float64),
3317 Field::new("pole_length_atr".into(), DataType::Float64),
3318 Field::new("breakout_confirmed".into(), DataType::Boolean),
3319 Field::new("breakout_price".into(), DataType::Float64),
3320 ])),
3321 Field::new("hs".into(), DataType::Struct(vec![
3322 Field::new("id".into(), DataType::UInt32),
3323 Field::new("is_bearish".into(), DataType::Boolean),
3324 Field::new("height".into(), DataType::Float64),
3325 Field::new("height_atr".into(), DataType::Float64),
3326 Field::new("score".into(), DataType::Float64),
3327 Field::new("breakout_confirmed".into(), DataType::Boolean),
3328 ])),
3329 ])),
3330 )
3331 .alias("geometric_patterns")])
3332 }
3333
3334 pub fn sr_monitor(
3338 self,
3339 high: &str,
3340 low: &str,
3341 close: &str,
3342 swing_strength: usize,
3343 touch_tolerance: f64,
3344 approach_zone: f64,
3345 ) -> LazyFrame {
3346 let high_str = high.to_string();
3347 let low_str = low.to_string();
3348 let close_str = close.to_string();
3349 let strength = swing_strength;
3350
3351 self.0.clone().with_columns([as_struct(vec![
3352 col(&high_str),
3353 col(&low_str),
3354 col(&close_str),
3355 ])
3356 .map(
3357 move |s| {
3358 let ca = s.struct_()?;
3359 let s_h = ca.field_by_name(&high_str)?;
3360 let s_l = ca.field_by_name(&low_str)?;
3361 let s_c = ca.field_by_name(&close_str)?;
3362 let highs = s_h.f64()?;
3363 let lows = s_l.f64()?;
3364 let closes = s_c.f64()?;
3365
3366 let mut mon = quantwave_core::SRInteractionMonitor::new(strength, touch_tolerance, approach_zone);
3367 let n = s.len();
3368
3369 let mut bias_vals: Vec<u32> = Vec::with_capacity(n);
3370 let mut active_levels: Vec<u32> = Vec::with_capacity(n);
3371 let mut interaction_counts: Vec<u32> = Vec::with_capacity(n);
3372 let mut has_interaction: Vec<bool> = Vec::with_capacity(n);
3373 let mut interaction_types: Vec<u32> = Vec::with_capacity(n);
3374 let mut level_prices: Vec<f64> = Vec::with_capacity(n);
3375 let mut is_support_vals: Vec<bool> = Vec::with_capacity(n);
3376 let mut strengths: Vec<f64> = Vec::with_capacity(n);
3377 let mut distances: Vec<f64> = Vec::with_capacity(n);
3378 let mut atr_vals: Vec<f64> = Vec::with_capacity(n);
3379
3380 for i in 0..n {
3381 let h = highs.get(i).unwrap_or(f64::NAN);
3382 let l = lows.get(i).unwrap_or(f64::NAN);
3383 let c = closes.get(i).unwrap_or(f64::NAN);
3384 let hh = if h.is_nan() || l.is_nan() { f64::NAN } else { h.max(l) };
3385 let ll = if h.is_nan() || l.is_nan() { f64::NAN } else { l.min(h) };
3386 let cc = if c.is_nan() { (hh + ll) / 2.0 } else { c.clamp(ll, hh) };
3387
3388 let out = mon.next((hh, ll, cc));
3389
3390 let b = match out.structure.bias {
3391 quantwave_core::Bias::Neutral => 0u32,
3392 quantwave_core::Bias::Bullish => 1,
3393 quantwave_core::Bias::Bearish => 2,
3394 };
3395 bias_vals.push(b);
3396 active_levels.push(mon.active_level_count() as u32);
3397 interaction_counts.push(out.interactions.len() as u32);
3398
3399 if let Some(first) = out.interactions.first() {
3400 has_interaction.push(true);
3401 interaction_types.push(first.interaction as u32);
3402 level_prices.push(first.level_price);
3403 is_support_vals.push(first.is_support);
3404 strengths.push(first.strength);
3405 distances.push(first.distance_at_event);
3406 } else {
3407 has_interaction.push(false);
3408 interaction_types.push(0);
3409 level_prices.push(f64::NAN);
3410 is_support_vals.push(false);
3411 strengths.push(f64::NAN);
3412 distances.push(f64::NAN);
3413 }
3414 atr_vals.push(mon.current_atr());
3415 }
3416
3417 let struct_series = StructChunked::from_series(
3418 "sr_monitor_result".into(),
3419 n,
3420 [
3421 Series::new("bias".into(), bias_vals),
3422 Series::new("active_levels".into(), active_levels),
3423 Series::new("interaction_count".into(), interaction_counts),
3424 Series::new("has_interaction".into(), has_interaction),
3425 Series::new("interaction_type".into(), interaction_types),
3426 Series::new("level_price".into(), level_prices),
3427 Series::new("is_support".into(), is_support_vals),
3428 Series::new("strength".into(), strengths),
3429 Series::new("distance".into(), distances),
3430 Series::new("atr".into(), atr_vals),
3431 ]
3432 .iter(),
3433 )?;
3434 Ok(Some(Column::from(struct_series.into_series())))
3435 },
3436 GetOutput::from_type(DataType::Struct(vec![
3437 Field::new("bias".into(), DataType::UInt32),
3438 Field::new("active_levels".into(), DataType::UInt32),
3439 Field::new("interaction_count".into(), DataType::UInt32),
3440 Field::new("has_interaction".into(), DataType::Boolean),
3441 Field::new("interaction_type".into(), DataType::UInt32),
3442 Field::new("level_price".into(), DataType::Float64),
3443 Field::new("is_support".into(), DataType::Boolean),
3444 Field::new("strength".into(), DataType::Float64),
3445 Field::new("distance".into(), DataType::Float64),
3446 Field::new("atr".into(), DataType::Float64),
3447 ])),
3448 )
3449 .alias("sr_monitor")])
3450 }
3451
3452 pub fn ichimoku_cloud(
3453 self,
3454 high: &str,
3455 low: &str,
3456 p1: usize,
3457 p2: usize,
3458 p3: usize,
3459 ) -> LazyFrame {
3460 let high = high.to_string();
3461 let low = low.to_string();
3462
3463 self.0
3464 .clone()
3465 .with_columns([as_struct(vec![col(&high), col(&low)])
3466 .map(
3467 move |s| {
3468 let ca = s.struct_()?;
3469 let s_high = ca.field_by_name(&high)?;
3470 let s_low = ca.field_by_name(&low)?;
3471
3472 let high = s_high.f64()?;
3473 let low = s_low.f64()?;
3474
3475 let mut ic = quantwave_core::IchimokuCloud::new(p1, p2, p3);
3476 let mut t_vals = Vec::with_capacity(s.len());
3477 let mut k_vals = Vec::with_capacity(s.len());
3478 let mut sa_vals = Vec::with_capacity(s.len());
3479 let mut sb_vals = Vec::with_capacity(s.len());
3480
3481 for i in 0..s.len() {
3482 let h = high.get(i).unwrap_or(0.0);
3483 let l = low.get(i).unwrap_or(0.0);
3484 let (t, k, sa, sb) = ic.next((h, l));
3485 t_vals.push(t);
3486 k_vals.push(k);
3487 sa_vals.push(sa);
3488 sb_vals.push(sb);
3489 }
3490
3491 let t_series = Series::new("tenkan".into(), t_vals);
3492 let k_series = Series::new("kijun".into(), k_vals);
3493 let sa_series = Series::new("senkou_a".into(), sa_vals);
3494 let sb_series = Series::new("senkou_b".into(), sb_vals);
3495
3496 let out = StructChunked::from_series(
3497 "ichimoku_output".into(),
3498 s.len(),
3499 [t_series, k_series, sa_series, sb_series].iter(),
3500 )?;
3501 Ok(Some(Column::from(out.into_series())))
3502 },
3503 GetOutput::from_type(DataType::Struct(vec![
3504 Field::new("tenkan".into(), DataType::Float64),
3505 Field::new("kijun".into(), DataType::Float64),
3506 Field::new("senkou_a".into(), DataType::Float64),
3507 Field::new("senkou_b".into(), DataType::Float64),
3508 ])),
3509 )
3510 .alias("ichimoku_data")])
3511 }
3512
3513 pub fn volatility_clusterer(
3514 self,
3515 high: &str,
3516 low: &str,
3517 close: &str,
3518 atr_period: usize,
3519 window_size: usize,
3520 k: usize,
3521 ) -> LazyFrame {
3522 let h_str = high.to_string();
3523 let l_str = low.to_string();
3524 let c_str = close.to_string();
3525
3526 self.0.clone().with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
3527 .map(
3528 move |s| {
3529 let ca = s.struct_()?;
3530 let f_h = ca.field_by_name(&h_str)?;
3531 let high = f_h.f64()?;
3532 let f_l = ca.field_by_name(&l_str)?;
3533 let low = f_l.f64()?;
3534 let f_c = ca.field_by_name(&c_str)?;
3535 let close = f_c.f64()?;
3536
3537 let mut clusterer =
3538 quantwave_core::regimes::volatility_clustering::VolatilityClusterer::new(
3539 atr_period,
3540 window_size,
3541 k,
3542 );
3543 let mut values = Vec::with_capacity(s.len());
3544
3545 for i in 0..s.len() {
3546 let h = high.get(i).unwrap_or(f64::NAN);
3547 let l = low.get(i).unwrap_or(f64::NAN);
3548 let c = close.get(i).unwrap_or(f64::NAN);
3549 let regime = clusterer.next((h, l, c));
3550 let val = match regime {
3551 quantwave_core::regimes::MarketRegime::Steady => 0u32,
3552 quantwave_core::regimes::MarketRegime::Bull => 1,
3553 quantwave_core::regimes::MarketRegime::Bear => 2,
3554 quantwave_core::regimes::MarketRegime::Crisis => 3,
3555 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
3556 };
3557 values.push(val);
3558 }
3559
3560 Ok(Some(Column::from(Series::new("volatility_regime".into(), values))))
3561 },
3562 GetOutput::from_type(DataType::UInt32),
3563 )
3564 .alias("volatility_regime")])
3565 }
3566
3567 pub fn hmm_bull_bear(self, name: &str) -> LazyFrame {
3568 let name_str = name.to_string();
3569 self.0.clone().with_columns([col(&name_str)
3570 .map(
3571 move |s| {
3572 let ca = s.f64()?;
3573 let mut hmm = quantwave_core::regimes::hmm::HMM::bull_bear();
3574 let mut values = Vec::with_capacity(s.len());
3575
3576 for i in 0..s.len() {
3577 let val = ca.get(i).unwrap_or(f64::NAN);
3578 let regime = hmm.next(val);
3579 let out = match regime {
3580 quantwave_core::regimes::MarketRegime::Bull => 1u32,
3581 quantwave_core::regimes::MarketRegime::Bear => 2,
3582 _ => 0,
3583 };
3584 values.push(out);
3585 }
3586
3587 Ok(Some(Column::from(Series::new("hmm_regime".into(), values))))
3588 },
3589 GetOutput::from_type(DataType::UInt32),
3590 )
3591 .alias("hmm_regime")])
3592 }
3593
3594 pub fn pelt(self, name: &str, penalty: f64, min_dist: usize) -> LazyFrame {
3595 let name_str = name.to_string();
3596 self.0.clone().with_columns([col(&name_str)
3597 .map(
3598 move |s| {
3599 let ca = s.f64()?;
3600 let data: Vec<f64> = ca.into_iter().map(|v| v.unwrap_or(f64::NAN)).collect();
3601 let pelt = quantwave_core::regimes::pelt::PELT::new(penalty, min_dist);
3602 let cps = pelt.detect(&data);
3603
3604 let mut values = vec![0u32; s.len()];
3605 for cp in cps {
3606 if cp < values.len() {
3607 values[cp] = 1;
3608 }
3609 }
3610
3611 Ok(Some(Column::from(Series::new("changepoints".into(), values))))
3612 },
3613 GetOutput::from_type(DataType::UInt32),
3614 )
3615 .alias("changepoints")])
3616 }
3617
3618 pub fn gmm(self, columns: &[&str], _k: usize) -> LazyFrame {
3619 let cols: Vec<String> = columns.iter().map(|s| s.to_string()).collect();
3620 let col_exprs: Vec<Expr> = cols.iter().map(|c| col(c)).collect();
3621
3622 self.0.clone().with_columns([as_struct(col_exprs)
3623 .map(
3624 move |s| {
3625 let ca = s.struct_()?;
3626 let n_rows = s.len();
3627 let n_dims = cols.len();
3628
3629 let mut data = Vec::with_capacity(n_rows);
3630 for i in 0..n_rows {
3631 let mut row = Vec::with_capacity(n_dims);
3632 for c_name in &cols {
3633 let f = ca.field_by_name(c_name)?;
3634 let val = f.f64()?.get(i).unwrap_or(f64::NAN);
3635 row.push(val);
3636 }
3637 data.push(row);
3638 }
3639
3640 let mut values = Vec::with_capacity(n_rows);
3643 for _ in 0..n_rows {
3644 values.push(0u32);
3645 }
3646
3647 Ok(Some(Column::from(Series::new("gmm_regime".into(), values))))
3648 },
3649 GetOutput::from_type(DataType::UInt32),
3650 )
3651 .alias("gmm_regime")])
3652 }
3653
3654 pub fn regimes_duration_stats(self, regime_col: &str, num_states: usize) -> LazyFrame {
3655 let name_str = regime_col.to_string();
3656 self.0.clone().with_columns([col(&name_str)
3657 .map(
3658 move |s| {
3659 let ca = s.u32()?;
3660 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
3661 let stats = quantwave_core::RegimeAnalytics::duration_stats(&states, num_states);
3662
3663 let mut regime_ids = Vec::new();
3665 let mut means = Vec::new();
3666 let mut medians = Vec::new();
3667 let mut stds = Vec::new();
3668 let mut maxes = Vec::new();
3669 let mut totals = Vec::new();
3670
3671 for stat in stats {
3672 regime_ids.push(stat.regime_id);
3673 means.push(stat.mean_duration);
3674 medians.push(stat.median_duration);
3675 stds.push(stat.std_duration);
3676 maxes.push(stat.max_duration as u32);
3677 totals.push(stat.total_observations as u32);
3678 }
3679
3680 let s_id = Series::new("regime_id".into(), regime_ids);
3681 let s_mean = Series::new("mean_duration".into(), means);
3682 let s_median = Series::new("median_duration".into(), medians);
3683 let s_std = Series::new("std_duration".into(), stds);
3684 let s_max = Series::new("max_duration".into(), maxes);
3685 let s_total = Series::new("total_observations".into(), totals);
3686
3687 let struct_series = StructChunked::from_series(
3688 "duration_stats".into(),
3689 s_id.len(),
3690 [s_id, s_mean, s_median, s_std, s_max, s_total].iter(),
3691 )?;
3692 Ok(Some(Column::from(struct_series.into_series())))
3693 },
3694 GetOutput::from_type(DataType::Struct(vec![
3695 Field::new("regime_id".into(), DataType::UInt32),
3696 Field::new("mean_duration".into(), DataType::Float64),
3697 Field::new("median_duration".into(), DataType::Float64),
3698 Field::new("std_duration".into(), DataType::Float64),
3699 Field::new("max_duration".into(), DataType::UInt32),
3700 Field::new("total_observations".into(), DataType::UInt32),
3701 ])),
3702 )
3703 .alias("regime_duration_stats")])
3704 }
3705
3706 pub fn regimes_transition_matrix(self, regime_col: &str, num_states: usize) -> LazyFrame {
3707 let name_str = regime_col.to_string();
3708 self.0.clone().with_columns([col(&name_str)
3709 .map(
3710 move |s| {
3711 let ca = s.u32()?;
3712 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
3713 let matrix = quantwave_core::RegimeAnalytics::transition_matrix(&states, num_states);
3714
3715 let mut builders = ListPrimitiveChunkedBuilder::<Float64Type>::new(
3717 "transition_matrix".into(),
3718 matrix.len(),
3719 matrix.len() * num_states,
3720 DataType::Float64,
3721 );
3722 for row in matrix {
3723 builders.append_slice(&row);
3724 }
3725
3726 let list_ca = builders.finish();
3727 Ok(Some(Column::from(list_ca.into_series())))
3728 },
3729 GetOutput::from_type(DataType::List(Box::new(DataType::Float64))),
3730 )
3731 .alias("regime_transition_matrix")])
3732 }
3733
3734 pub fn regimes_stability_score(self, regime_col: &str) -> LazyFrame {
3735 let name_str = regime_col.to_string();
3736 self.0.clone().with_columns([col(&name_str)
3737 .map(
3738 move |s| {
3739 let ca = s.u32()?;
3740 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
3741 let score = quantwave_core::RegimeAnalytics::stability_score(&states);
3742
3743 Ok(Some(Column::from(Series::new("stability_score".into(), vec![score; s.len()]))))
3744 },
3745 GetOutput::from_type(DataType::Float64),
3746 )
3747 .alias("regime_stability_score")])
3748 }
3749
3750 pub fn regimes_next_state_prob(self, regime_col: &str, num_states: usize, steps: usize) -> LazyFrame {
3751 let name_str = regime_col.to_string();
3752 self.0.clone().with_columns([col(&name_str)
3753 .map(
3754 move |s| {
3755 let ca = s.u32()?;
3756 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
3757 let matrix = quantwave_core::RegimeAnalytics::transition_matrix(&states, num_states);
3758
3759 let mut builders = ListPrimitiveChunkedBuilder::<Float64Type>::new(
3760 "next_state_probs".into(),
3761 s.len(),
3762 s.len() * num_states,
3763 DataType::Float64,
3764 );
3765
3766 for ¤t in &states {
3767 let probs = quantwave_core::RegimeAnalytics::forecast_state(&matrix, current, steps);
3768 builders.append_slice(&probs);
3769 }
3770
3771 let list_ca = builders.finish();
3772 Ok(Some(Column::from(list_ca.into_series())))
3773 },
3774 GetOutput::from_type(DataType::List(Box::new(DataType::Float64))),
3775 )
3776 .alias("next_state_probs")])
3777 }
3778
3779 pub fn filter_by_regime(self, regime_col: &str, target_regime: u32) -> LazyFrame {
3780 self.0.clone().filter(col(regime_col).eq(lit(target_regime)))
3781 }
3782
3783 pub fn apply_regime_strategy(
3784 self,
3785 regime_col: &str,
3786 signal_col: &str,
3787 regime_weights: std::collections::HashMap<u32, f64>,
3788 ) -> LazyFrame {
3789 let mut case_expr = col(signal_col);
3790 for (regime, weight) in regime_weights {
3791 case_expr = when(col(regime_col).eq(lit(regime)))
3792 .then(col(signal_col) * lit(weight))
3793 .otherwise(case_expr);
3794 }
3795
3796 self.0.clone().with_columns([case_expr.alias("regime_adjusted_signal")])
3797 }
3798
3799 pub fn regimes_ms_garch(self, returns_col: &str) -> LazyFrame {
3800 let name_str = returns_col.to_string();
3801 self.0.clone().with_columns([col(&name_str)
3802 .map(
3803 move |s| {
3804 let ca = s.f64()?;
3805 let mut model = quantwave_core::regimes::ms_garch::MSGarch::low_high_vol();
3806 let mut regimes = Vec::with_capacity(s.len());
3807 let mut vols = Vec::with_capacity(s.len());
3808
3809 for i in 0..s.len() {
3810 let ret = ca.get(i).unwrap_or(0.0);
3811 let (regime, vol) = model.next(ret);
3812
3813 let r_val = match regime {
3814 quantwave_core::regimes::MarketRegime::Steady => 0u32,
3815 quantwave_core::regimes::MarketRegime::Crisis => 1,
3816 quantwave_core::regimes::MarketRegime::Bull => 2,
3817 quantwave_core::regimes::MarketRegime::Bear => 3,
3818 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
3819 };
3820 regimes.push(r_val);
3821 vols.push(vol);
3822 }
3823
3824 let s_regime = Series::new("regime".into(), regimes);
3825 let s_vol = Series::new("estimated_vol".into(), vols);
3826 let struct_series = StructChunked::from_series(
3827 "ms_garch_data".into(),
3828 s.len(),
3829 [s_regime, s_vol].iter(),
3830 )?;
3831 Ok(Some(Column::from(struct_series.into_series())))
3832 },
3833 GetOutput::from_type(DataType::Struct(vec![
3834 Field::new("regime".into(), DataType::UInt32),
3835 Field::new("estimated_vol".into(), DataType::Float64),
3836 ])),
3837 )
3838 .alias("ms_garch_data")])
3839 }
3840
3841 pub fn regimes_ensemble(self, columns: &[&str], weights: &[f64]) -> LazyFrame {
3842 let cols: Vec<String> = columns.iter().map(|s| s.to_string()).collect();
3843 let col_exprs: Vec<Expr> = cols.iter().map(|c| col(c)).collect();
3844 let w_vec = weights.to_vec();
3845
3846 self.0.clone().with_columns([as_struct(col_exprs)
3847 .map(
3848 move |s| {
3849 let ca = s.struct_()?;
3850 let n_rows = s.len();
3851 let n_dims = cols.len();
3852 let ensemble = quantwave_core::regimes::ensemble::RegimeEnsemble::new(w_vec.clone());
3853
3854 let mut results = Vec::with_capacity(n_rows);
3855 for i in 0..n_rows {
3856 let mut row_regimes = Vec::with_capacity(n_dims);
3857 for c_name in &cols {
3858 let f = ca.field_by_name(c_name)?;
3859 let val = f.u32()?.get(i).unwrap_or(0);
3860
3861 let regime = match val {
3862 0 => quantwave_core::regimes::MarketRegime::Steady,
3863 1 => quantwave_core::regimes::MarketRegime::Crisis,
3864 2 => quantwave_core::regimes::MarketRegime::Bull,
3865 3 => quantwave_core::regimes::MarketRegime::Bear,
3866 v if v >= 4 => quantwave_core::regimes::MarketRegime::Cluster((v - 4) as u8),
3867 _ => quantwave_core::regimes::MarketRegime::Steady,
3868 };
3869 row_regimes.push(regime);
3870 }
3871
3872 let consensus = ensemble.vote(&row_regimes);
3873 let out = match consensus {
3874 quantwave_core::regimes::MarketRegime::Steady => 0u32,
3875 quantwave_core::regimes::MarketRegime::Crisis => 1,
3876 quantwave_core::regimes::MarketRegime::Bull => 2,
3877 quantwave_core::regimes::MarketRegime::Bear => 3,
3878 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
3879 };
3880 results.push(out);
3881 }
3882
3883 Ok(Some(Column::from(Series::new("ensemble_regime".into(), results))))
3884 },
3885 GetOutput::from_type(DataType::UInt32),
3886 )
3887 .alias("ensemble_regime")])
3888 }
3889
3890 pub fn regimes_tar(self, signal_col: &str, thresholds: Vec<f64>) -> LazyFrame {
3891 let name_str = signal_col.to_string();
3892 self.0.clone().with_columns([col(&name_str)
3893 .map(
3894 move |s| {
3895 let ca = s.f64()?;
3896 let mut model = quantwave_core::regimes::tar::TAR::multi(thresholds.clone());
3897 let mut results = Vec::with_capacity(s.len());
3898
3899 for i in 0..s.len() {
3900 let val = ca.get(i).unwrap_or(f64::NAN);
3901 let regime = model.next(val);
3902 let out = match regime {
3903 quantwave_core::regimes::MarketRegime::Steady => 0u32,
3904 quantwave_core::regimes::MarketRegime::Crisis => 1,
3905 quantwave_core::regimes::MarketRegime::Bull => 2,
3906 quantwave_core::regimes::MarketRegime::Bear => 3,
3907 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
3908 };
3909 results.push(out);
3910 }
3911
3912 Ok(Some(Column::from(Series::new("tar_regime".into(), results))))
3913 },
3914 GetOutput::from_type(DataType::UInt32),
3915 )
3916 .alias("tar_regime")])
3917 }
3918
3919 pub fn regimes_hsmm(self, name: &str) -> LazyFrame {
3920 let name_str = name.to_string();
3921 self.0.clone().with_columns([col(&name_str)
3922 .map(
3923 move |s| {
3924 let ca = s.f64()?;
3925 let mut model = quantwave_core::regimes::hsmm::HSMM::new(
3927 vec![vec![0.0, 1.0], vec![1.0, 0.0]], vec![0.001, -0.002],
3929 vec![0.01, 0.02],
3930 vec![
3931 quantwave_core::regimes::hsmm::DurationDistribution::Poisson { lambda: 5.0 },
3932 quantwave_core::regimes::hsmm::DurationDistribution::Poisson { lambda: 2.0 },
3933 ],
3934 );
3935 let mut values = Vec::with_capacity(s.len());
3936
3937 for i in 0..s.len() {
3938 let val = ca.get(i).unwrap_or(f64::NAN);
3939 let regime = model.next(val);
3940 let out = match regime {
3941 quantwave_core::regimes::MarketRegime::Steady => 0u32,
3942 quantwave_core::regimes::MarketRegime::Crisis => 1,
3943 _ => 2, };
3945 values.push(out);
3946 }
3947
3948 Ok(Some(Column::from(Series::new("hsmm_regime".into(), values))))
3949 },
3950 GetOutput::from_type(DataType::UInt32),
3951 )
3952 .alias("hsmm_regime")])
3953 }
3954
3955 pub fn regimes_hmm_gas(self, name: &str) -> LazyFrame {
3956 let name_str = name.to_string();
3957 self.0.clone().with_columns([col(&name_str)
3958 .map(
3959 move |s| {
3960 let ca = s.f64()?;
3961 let mut model = quantwave_core::regimes::hmm_gas::HMMGAS::new(
3962 [0.1, 0.05, 0.9], [0.1, 0.05, 0.9], [0.001, -0.002],
3965 [0.01, 0.02],
3966 );
3967 let mut values = Vec::with_capacity(s.len());
3968
3969 for i in 0..s.len() {
3970 let val = ca.get(i).unwrap_or(f64::NAN);
3971 let regime = model.next(val);
3972 let out = match regime {
3973 quantwave_core::regimes::MarketRegime::Steady => 0u32,
3974 quantwave_core::regimes::MarketRegime::Crisis => 1,
3975 _ => 2,
3976 };
3977 values.push(out);
3978 }
3979
3980 Ok(Some(Column::from(Series::new("hmm_gas_regime".into(), values))))
3981 },
3982 GetOutput::from_type(DataType::UInt32),
3983 )
3984 .alias("hmm_gas_regime")])
3985 }
3986
3987 pub fn regimes_conditioned_metrics(
3988 self,
3989 returns_col: &str,
3990 regime_col: &str,
3991 annualization_factor: f64,
3992 ) -> LazyFrame {
3993 let ret_str = returns_col.to_string();
3994 let reg_str = regime_col.to_string();
3995
3996 self.0
3997 .clone()
3998 .group_by([col(®_str)])
3999 .agg([
4000 col(&ret_str).mean().alias("mean_return"),
4001 col(&ret_str).std(1).alias("volatility"),
4002 (col(&ret_str).mean() / col(&ret_str).std(1) * lit(annualization_factor.sqrt()))
4003 .alias("sharpe_ratio"),
4004 col(&ret_str).skew(true).alias("skewness"),
4005 col(&ret_str).kurtosis(true, true).alias("kurtosis"),
4006 (col(&ret_str).mean()
4008 / col(&ret_str).filter(col(&ret_str).lt(lit(0.0))).std(1)
4009 * lit(annualization_factor.sqrt()))
4010 .alias("sortino_ratio"),
4011 ])
4015 }
4016}
4017
4018
4019
4020
4021
4022#[cfg(test)]
4023mod tests {
4024 use super::*;
4025
4026 #[test]
4027 fn test_polars_heikin_ashi() -> PolarsResult<()> {
4028 let df = df![
4029 "open" => [10.0, 11.0],
4030 "high" => [12.0, 13.0],
4031 "low" => [8.0, 10.0],
4032 "close" => [11.0, 12.0]
4033 ]?;
4034
4035 let out = df
4036 .lazy()
4037 .ta()
4038 .heikin_ashi("open", "high", "low", "close")
4039 .collect()?;
4040
4041 let ha = out.column("heikin_ashi_data")?.struct_()?;
4042 assert_eq!(
4043 ha.field_by_name("ha_open".into())?.f64()?.get(0),
4044 Some(10.5)
4045 );
4046 assert_eq!(
4047 ha.field_by_name("ha_close".into())?.f64()?.get(0),
4048 Some(10.25)
4049 );
4050
4051 Ok(())
4052 }
4053
4054 #[test]
4055 fn test_polars_tema_zlema() -> PolarsResult<()> {
4056 let df = df![
4057 "price" => [1.0, 2.0, 3.0, 4.0, 5.0]
4058 ]?;
4059
4060 let out = df.clone().lazy().ta().tema("price", 3).collect()?;
4061
4062 let tema = out.column("tema")?.f64()?;
4063 assert!(tema.get(4).is_some());
4064
4065 let out2 = df.lazy().ta().zlema("price", 3).collect()?;
4066
4067 let zlema = out2.column("zlema")?.f64()?;
4068 assert!(zlema.get(4).is_some());
4069
4070 Ok(())
4071 }
4072
4073 #[test]
4074 fn test_polars_atr_ts() -> PolarsResult<()> {
4075 let df = df![
4076 "high" => [10.0, 12.0, 11.0],
4077 "low" => [8.0, 10.0, 9.0],
4078 "close" => [9.0, 11.0, 10.0]
4079 ]?;
4080
4081 let out = df
4082 .lazy()
4083 .ta()
4084 .atr_trailing_stop("high", "low", "close", 14, 2.5)
4085 .collect()?;
4086
4087 let atr_ts = out.column("atr_ts_data")?.struct_()?;
4088 assert!(atr_ts.field_by_name("stop".into())?.f64()?.get(0).is_some());
4089 assert!(
4090 atr_ts
4091 .field_by_name("direction".into())?
4092 .f64()?
4093 .get(0)
4094 .is_some()
4095 );
4096
4097 Ok(())
4098 }
4099
4100 #[test]
4101 fn test_polars_pivot_points() -> PolarsResult<()> {
4102 let df = df![
4103 "high" => [10.0, 12.0, 11.0],
4104 "low" => [8.0, 10.0, 9.0],
4105 "close" => [9.0, 11.0, 10.0]
4106 ]?;
4107
4108 let out = df
4109 .lazy()
4110 .ta()
4111 .pivot_points("high", "low", "close")
4112 .collect()?;
4113
4114 let pivot = out.column("pivot_points_data")?.struct_()?;
4115 assert!(pivot.field_by_name("p".into())?.f64()?.get(0).is_some());
4116 assert!(pivot.field_by_name("r1".into())?.f64()?.get(0).is_some());
4117
4118 Ok(())
4119 }
4120
4121 #[test]
4122 fn test_polars_fractals() -> PolarsResult<()> {
4123 let df = df![
4124 "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
4125 "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
4126 ]?;
4127
4128 let out = df
4129 .lazy()
4130 .ta()
4131 .bill_williams_fractals("high", "low")
4132 .collect()?;
4133
4134 let fractals = out.column("fractals_data")?.struct_()?;
4135 assert!(
4136 fractals
4137 .field_by_name("bearish".into())?
4138 .bool()?
4139 .get(4)
4140 .unwrap()
4141 );
4142 assert!(
4143 fractals
4144 .field_by_name("bullish".into())?
4145 .bool()?
4146 .get(4)
4147 .unwrap()
4148 );
4149
4150 Ok(())
4151 }
4152
4153 #[test]
4154 fn test_polars_ichimoku() -> PolarsResult<()> {
4155 let df = df![
4156 "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
4157 "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
4158 ]?;
4159
4160 let out = df
4161 .lazy()
4162 .ta()
4163 .ichimoku_cloud("high", "low", 9, 26, 52)
4164 .collect()?;
4165
4166 let ichimoku = out.column("ichimoku_data")?.struct_()?;
4167 assert!(
4168 ichimoku
4169 .field_by_name("tenkan".into())?
4170 .f64()?
4171 .get(4)
4172 .is_some()
4173 );
4174 assert!(
4175 ichimoku
4176 .field_by_name("kijun".into())?
4177 .f64()?
4178 .get(4)
4179 .is_some()
4180 );
4181
4182 Ok(())
4183 }
4184
4185 #[test]
4186 fn test_polars_wavetrend() -> PolarsResult<()> {
4187 let df = df![
4188 "high" => [10.0, 12.0, 11.0],
4189 "low" => [8.0, 10.0, 9.0],
4190 "close" => [9.0, 11.0, 10.0]
4191 ]?;
4192
4193 let out = df
4194 .lazy()
4195 .ta()
4196 .wavetrend("high", "low", "close", 10, 21, 4)
4197 .collect()?;
4198
4199 let wt = out.column("wavetrend_data")?.struct_()?;
4200 assert!(wt.field_by_name("wt1".into())?.f64()?.get(0).is_some());
4201 assert!(wt.field_by_name("wt2".into())?.f64()?.get(0).is_some());
4202
4203 Ok(())
4204 }
4205
4206 #[test]
4207 fn test_polars_vortex() -> PolarsResult<()> {
4208 let df = df![
4209 "high" => [10.0, 12.0, 11.0],
4210 "low" => [8.0, 10.0, 9.0],
4211 "close" => [9.0, 11.0, 10.0]
4212 ]?;
4213
4214 let out = df
4215 .lazy()
4216 .ta()
4217 .vortex_indicator("high", "low", "close", 14)
4218 .collect()?;
4219
4220 let vortex = out.column("vortex_data")?.struct_()?;
4221 assert!(
4222 vortex
4223 .field_by_name("vi_plus".into())?
4224 .f64()?
4225 .get(0)
4226 .is_some()
4227 );
4228 assert!(
4229 vortex
4230 .field_by_name("vi_minus".into())?
4231 .f64()?
4232 .get(0)
4233 .is_some()
4234 );
4235
4236 Ok(())
4237 }
4238
4239 #[test]
4240 fn test_polars_ttm_squeeze() -> PolarsResult<()> {
4241 let df = df![
4242 "high" => [11.0, 12.0, 13.0, 14.0],
4243 "low" => [9.0, 10.0, 11.0, 12.0],
4244 "close" => [10.0, 11.0, 12.0, 13.0]
4245 ]?;
4246
4247 let out = df
4248 .lazy()
4249 .ta()
4250 .ttm_squeeze("high", "low", "close", 20, 2.0, 1.5)
4251 .collect()?;
4252
4253 let ttm = out.column("ttm_squeeze_data")?.struct_()?;
4254 assert!(
4255 ttm.field_by_name("histogram".into())?
4256 .f64()?
4257 .get(0)
4258 .is_some()
4259 );
4260 assert!(
4261 ttm.field_by_name("is_squeezed".into())?
4262 .bool()?
4263 .get(0)
4264 .is_some()
4265 );
4266
4267 Ok(())
4268 }
4269
4270 #[test]
4271 fn test_polars_donchian() -> PolarsResult<()> {
4272 let df = df![
4273 "high" => [10.0, 12.0, 11.0, 13.0, 15.0],
4274 "low" => [8.0, 7.0, 9.0, 10.0, 12.0]
4275 ]?;
4276
4277 let out = df
4278 .lazy()
4279 .ta()
4280 .donchian_channels("high", "low", 3)
4281 .collect()?;
4282
4283 let donchian = out.column("donchian_data")?.struct_()?;
4284 assert_eq!(
4286 donchian.field_by_name("upper".into())?.f64()?.get(3),
4287 Some(13.0)
4288 );
4289 assert_eq!(
4290 donchian.field_by_name("middle".into())?.f64()?.get(3),
4291 Some(10.0)
4292 );
4293 assert_eq!(
4294 donchian.field_by_name("lower".into())?.f64()?.get(3),
4295 Some(7.0)
4296 );
4297
4298 Ok(())
4299 }
4300
4301 #[test]
4302 fn test_polars_alma() -> PolarsResult<()> {
4303 let df = df![
4304 "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
4305 ]?;
4306
4307 let out = df.lazy().ta().alma("price", 9, 0.85, 6.0).collect()?;
4308
4309 let alma = out.column("alma")?.f64()?;
4310 assert!(alma.get(9).is_some());
4311
4312 Ok(())
4313 }
4314
4315 #[test]
4316 fn test_polars_keltner() -> PolarsResult<()> {
4317 let df = df![
4318 "high" => [12.0],
4319 "low" => [8.0],
4320 "close" => [10.0]
4321 ]?;
4322
4323 let out = df
4324 .lazy()
4325 .ta()
4326 .keltner_channels("high", "low", "close", 3, 3, 2.0)
4327 .collect()?;
4328
4329 let keltner = out.column("keltner_data")?.struct_()?;
4330 assert_eq!(
4331 keltner.field_by_name("middle".into())?.f64()?.get(0),
4332 Some(10.0)
4333 );
4334 assert_eq!(
4335 keltner.field_by_name("upper".into())?.f64()?.get(0),
4336 Some(18.0)
4337 );
4338 assert_eq!(
4339 keltner.field_by_name("lower".into())?.f64()?.get(0),
4340 Some(2.0)
4341 );
4342
4343 Ok(())
4344 }
4345
4346 #[test]
4347 fn test_polars_hma() -> PolarsResult<()> {
4348 let df = df![
4349 "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
4350 ]?;
4351
4352 let out = df.lazy().ta().hma("price", 4).collect()?;
4353
4354 let hma = out.column("hma")?.f64()?;
4355 assert!(hma.get(9).is_some());
4356
4357 Ok(())
4358 }
4359
4360 #[test]
4361 fn test_polars_anchored_vwap() -> PolarsResult<()> {
4362 let df = df![
4363 "price" => [10.0, 12.0, 15.0, 16.0],
4364 "volume" => [100.0, 200.0, 100.0, 100.0],
4365 "anchor" => [false, false, true, false]
4366 ]?;
4367
4368 let out = df
4369 .lazy()
4370 .ta()
4371 .anchored_vwap("price", "volume", "anchor")
4372 .collect()?;
4373
4374 let avwap = out.column("avwap")?.f64()?;
4375 assert_eq!(avwap.get(0), Some(10.0));
4376 assert_eq!(avwap.get(1), Some(11.333333333333334));
4377 assert_eq!(avwap.get(2), Some(15.0));
4378 assert_eq!(avwap.get(3), Some(15.5));
4379
4380 Ok(())
4381 }
4382
4383 #[test]
4384 fn test_polars_math_transforms() -> PolarsResult<()> {
4385 let df = df![
4386 "val" => [0.0, 1.5707963267948966] ]?;
4388
4389 let out = df.lazy().ta().sin("val").collect()?;
4390
4391 let sin = out.column("sin")?.f64()?;
4392 assert!((sin.get(0).unwrap() - 0.0).abs() < 1e-10);
4393 assert!((sin.get(1).unwrap() - 1.0).abs() < 1e-10);
4394
4395 Ok(())
4396 }
4397
4398 #[test]
4399 fn test_polars_math_operators() -> PolarsResult<()> {
4400 let df = df![
4401 "v1" => [10.0, 20.0],
4402 "v2" => [5.0, 30.0]
4403 ]?;
4404
4405 let out = df.lazy().ta().add("v1", "v2").ta().max("v1", 2).collect()?;
4406
4407 let add = out.column("add")?.f64()?;
4408 assert_eq!(add.get(0), Some(15.0));
4409 assert_eq!(add.get(1), Some(50.0));
4410
4411 let max = out.column("max")?.f64()?;
4412 assert_eq!(max.get(1), Some(20.0));
4413
4414 Ok(())
4415 }
4416
4417 #[test]
4418 fn test_polars_vpn() -> PolarsResult<()> {
4419 let df = df![
4420 "high" => [10.0, 11.0, 12.0],
4421 "low" => [9.0, 10.0, 11.0],
4422 "close" => [9.5, 10.5, 11.5],
4423 "volume" => [1000.0, 1100.0, 1200.0]
4424 ]?;
4425
4426 let out = df.lazy().ta().vpn("high", "low", "close", "volume", 30, 3).collect()?;
4427 let vpn = out.column("vpn")?.f64()?;
4428 assert!(vpn.get(2).is_some());
4429 Ok(())
4430 }
4431
4432 #[test]
4433 fn test_polars_gap_momentum() -> PolarsResult<()> {
4434 let df = df![
4435 "open" => [10.0, 11.0, 10.0],
4436 "close" => [10.5, 10.5, 9.5]
4437 ]?;
4438
4439 let out = df.lazy().ta().gap_momentum("open", "close", 10, 5).collect()?;
4440 let gm = out.column("gap_momentum")?.struct_()?;
4441 assert!(gm.field_by_name("gap_ratio".into())?.f64()?.get(2).is_some());
4442 assert!(gm.field_by_name("gap_signal".into())?.f64()?.get(2).is_some());
4443 Ok(())
4444 }
4445
4446 #[test]
4447 fn test_polars_autotune() -> PolarsResult<()> {
4448 let df = df![
4449 "price" => [100.0; 50]
4450 ]?;
4451
4452 let out = df.lazy().ta().autotune_filter("price", 20, 0.25).collect()?;
4453 let at = out.column("autotune")?.f64()?;
4454 assert!(at.get(49).is_some());
4455 Ok(())
4456 }
4457
4458 #[test]
4459 fn test_polars_adaptive_ema() -> PolarsResult<()> {
4460 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
4461 let out = df.lazy().ta().adaptive_ema("h", "l", "c", 10, 2).collect()?;
4462 assert!(out.column("adaptive_ema")?.f64()?.get(2).is_some());
4463 Ok(())
4464 }
4465
4466 #[test]
4467 fn test_polars_obvm() -> PolarsResult<()> {
4468 let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
4469 let out = df.lazy().ta().obvm("h", "l", "c", "v", 10, 3).collect()?;
4470 let data = out.column("obvm_data")?.struct_()?;
4471 assert!(data.field_by_name("obvm".into())?.f64()?.get(1).is_some());
4472 Ok(())
4473 }
4474
4475 #[test]
4476 fn test_polars_vfi() -> PolarsResult<()> {
4477 let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
4478 let out = df.lazy().ta().vfi("h", "l", "c", "v", 10, 0.2, 2.5, 3).collect()?;
4479 assert!(out.column("vfi")?.f64()?.get(1).is_some());
4480 Ok(())
4481 }
4482
4483 #[test]
4484 fn test_polars_sdo() -> PolarsResult<()> {
4485 let df = df!["p" => [10.0, 11.0, 12.0]]?;
4486 let out = df.lazy().ta().sdo("p", 2, 5, 3).collect()?;
4487 assert!(out.column("sdo")?.f64()?.get(2).is_some());
4488 Ok(())
4489 }
4490
4491 #[test]
4492 fn test_polars_rsmk() -> PolarsResult<()> {
4493 let df = df!["p" => [10.0, 11.0], "b" => [100.0, 101.0]]?;
4494 let out = df.lazy().ta().rsmk("p", "b", 90, 3).collect()?;
4495 assert!(out.column("rsmk")?.f64()?.get(1).is_some());
4496 Ok(())
4497 }
4498
4499 #[test]
4500 fn test_polars_rodc() -> PolarsResult<()> {
4501 let df = df!["p" => [10.0, 11.0, 10.0, 11.0, 12.0]]?;
4502 let out = df.lazy().ta().rodc("p", 10, 0.5, 3).collect()?;
4503 assert!(out.column("rodc")?.f64()?.get(4).is_some());
4504 Ok(())
4505 }
4506
4507 #[test]
4508 fn test_polars_reverse_ema() -> PolarsResult<()> {
4509 let df = df!["p" => [10.0, 11.0, 12.0]]?;
4510 let out = df.lazy().ta().reverse_ema("p", 0.1).collect()?;
4511 assert!(out.column("reverse_ema")?.f64()?.get(2).is_some());
4512 Ok(())
4513 }
4514
4515 #[test]
4516 fn test_polars_harrington_adx() -> PolarsResult<()> {
4517 let df = df!["h" => [10.0, 11.0, 12.0], "l" => [9.0, 10.0, 11.0], "c" => [9.5, 10.5, 11.5]]?;
4518 let out = df.lazy().ta().harrington_adx("h", "l", "c", 10, 1).collect()?;
4519 assert!(out.column("harrington_adx")?.f64()?.get(2).is_some());
4520 Ok(())
4521 }
4522
4523 #[test]
4524 fn test_polars_tradj_ema() -> PolarsResult<()> {
4525 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
4526 let out = df.lazy().ta().tradj_ema("h", "l", "c", 10, 2, 0.5).collect()?;
4527 assert!(out.column("tradj_ema")?.f64()?.get(2).is_some());
4528 Ok(())
4529 }
4530
4531 #[test]
4532 fn test_polars_sve_volatility_bands() -> PolarsResult<()> {
4533 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
4534 let out = df.lazy().ta().sve_volatility_bands("h", "l", "c", 10, 1.5, 1.0, 3).collect()?;
4535 let data = out.column("sve_bands_data")?.struct_()?;
4536 assert!(data.field_by_name("upper".into())?.f64()?.get(2).is_some());
4537 Ok(())
4538 }
4539
4540 #[test]
4541 fn test_polars_exp_dev_bands() -> PolarsResult<()> {
4542 let df = df!["p" => [10.0, 11.0, 12.0, 11.0, 10.0]]?;
4543 let out = df.lazy().ta().exp_dev_bands("p", 10, 2.0, true).collect()?;
4544 let data = out.column("exp_dev_bands_data")?.struct_()?;
4545 assert!(data.field_by_name("upper".into())?.f64()?.get(4).is_some());
4546 Ok(())
4547 }
4548
4549 #[test]
4550 fn test_regimes_conditioned_metrics() -> PolarsResult<()> {
4551 let df = df![
4552 "returns" => [0.01, 0.02, -0.01, -0.02, 0.01],
4553 "regime" => [0u32, 0, 1, 1, 0]
4554 ]?;
4555
4556 let out = df
4557 .lazy()
4558 .ta()
4559 .regimes_conditioned_metrics("returns", "regime", 252.0)
4560 .collect()?;
4561
4562 assert_eq!(out.height(), 2);
4563 assert!(out.column("sharpe_ratio").is_ok());
4564 assert!(out.column("skewness").is_ok());
4565 assert!(out.column("kurtosis").is_ok());
4566 assert!(out.column("sortino_ratio").is_ok());
4567
4568 Ok(())
4569 }
4570
4571 #[test]
4572 fn test_polars_kalman_filters() -> PolarsResult<()> {
4573 let df = df![
4574 "price" => [100.0, 101.0, 102.0, 103.0, 104.0]
4575 ]?;
4576
4577 let out = df
4578 .clone()
4579 .lazy()
4580 .ta()
4581 .kalman("price", 0.01, 0.1)
4582 .collect()?;
4583
4584 let kalman = out.column("kalman")?.f64()?;
4585 assert!(kalman.get(0).is_some());
4586 assert_eq!(kalman.get(0).unwrap(), 100.0);
4587
4588 let out2 = df
4589 .lazy()
4590 .ta()
4591 .kinematic_kalman("price", 0.001, 0.0001, 0.1)
4592 .collect()?;
4593
4594 let kin_kalman = out2.column("kinematic_kalman")?.f64()?;
4595 assert!(kin_kalman.get(0).is_some());
4596 assert_eq!(kin_kalman.get(0).unwrap(), 100.0);
4597
4598 Ok(())
4599 }
4600
4601 #[test]
4605 fn smoke_ta_pa_foundation() -> PolarsResult<()> {
4606 let highs: Vec<f64> = (0..60).map(|i| 100.0 + (i as f64 * 0.8).sin() * 5.0 + (i as f64) * 0.3).collect();
4607 let lows: Vec<f64> = highs.iter().map(|&h| h - 1.5 - (h % 3.0) * 0.2).collect();
4608
4609 let df = df!["high" => highs, "low" => lows]?;
4610 let lf = df.lazy();
4611
4612 let out = lf
4614 .clone()
4615 .ta()
4616 .market_structure("high", "low", 3)
4617 .collect()?;
4618 let ms = out.column("market_structure")?;
4619 assert!(matches!(ms.dtype(), DataType::Struct(_)));
4620 let ca = ms.struct_()?;
4621 assert_eq!(ca.field_by_name("bias".into())?.dtype().clone(), DataType::UInt32);
4623 assert!(ca.field_by_name("has_flip".into())?.bool()?.get(59).is_some());
4624
4625 let out2 = out
4627 .lazy()
4628 .ta()
4629 .geometric_patterns("high", "low", 2)
4630 .collect()?;
4631 let gp = out2.column("geometric_patterns")?;
4632 assert!(matches!(gp.dtype(), DataType::Struct(_)));
4633 let gca = gp.struct_()?;
4634 let flag = gca.field_by_name("flag".into())?;
4635 assert!(matches!(flag.dtype(), DataType::Struct(_)));
4636 assert!(flag.struct_()?.field_by_name("pole_length_atr".into())?.f64()?.get(59).is_some());
4638
4639 Ok(())
4640 }
4641}
4642
4643impl QuantWaveExt for LazyFrame {
4644 fn ta(&self) -> QuantWaveNamespace<'_> {
4645 QuantWaveNamespace(self)
4646 }
4647}
4648
4649pub use bt::{BtNamespace, BtOptions, QuantWaveBtExt};
4650pub use quantwave_backtest::{run_param_sweep, single_param_variants, SweepVariant};