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