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 crate::features::TaFeaturesNamespace;
34 pub use crate::{QuantWaveExt, QuantWaveNamespace};
35 pub use quantwave_backtest::{SweepVariant, run_param_sweep, single_param_variants}; }
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 #[allow(clippy::too_many_arguments)]
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 =
983 StructChunked::from_series("stochf_result".into(), s.len(), [s_k, s_d].iter())?;
984 Ok(Some(Column::from(struct_series.into_series())))
985 },
986 GetOutput::from_type(DataType::Struct(vec![
987 Field::new("fastk".into(), DataType::Float64),
988 Field::new("fastd".into(), DataType::Float64),
989 ])),
990 )
991 .alias("stochf")])
992 }
993
994 pub fn stochrsi(
995 self,
996 name: &str,
997 timeperiod: usize,
998 fastk_period: usize,
999 fastd_period: usize,
1000 fastd_matype: talib::MaType,
1001 ) -> LazyFrame {
1002 let name_str = name.to_string();
1003 self.0.clone().with_columns([col(&name_str)
1004 .map(
1005 move |s| {
1006 let ca = s.f64()?;
1007 let mut indicator =
1008 STOCHRSI::new(timeperiod, fastk_period, fastd_period, fastd_matype);
1009 let mut k_vals = Vec::with_capacity(s.len());
1010 let mut d_vals = Vec::with_capacity(s.len());
1011
1012 for i in 0..s.len() {
1013 let val = ca.get(i).unwrap_or(f64::NAN);
1014 let (k, d) = indicator.next(val);
1015 k_vals.push(k);
1016 d_vals.push(d);
1017 }
1018
1019 let s_k = Series::new("fastk".into(), k_vals);
1020 let s_d = Series::new("fastd".into(), d_vals);
1021
1022 let struct_series = StructChunked::from_series(
1023 "stochrsi_result".into(),
1024 s.len(),
1025 [s_k, s_d].iter(),
1026 )?;
1027 Ok(Some(Column::from(struct_series.into_series())))
1028 },
1029 GetOutput::from_type(DataType::Struct(vec![
1030 Field::new("fastk".into(), DataType::Float64),
1031 Field::new("fastd".into(), DataType::Float64),
1032 ])),
1033 )
1034 .alias("stochrsi")])
1035 }
1036
1037 pub fn apo(
1038 self,
1039 name: &str,
1040 fastperiod: usize,
1041 slowperiod: usize,
1042 matype: talib::MaType,
1043 ) -> LazyFrame {
1044 let name_str = name.to_string();
1045 self.0.clone().with_columns([col(&name_str)
1046 .map(
1047 move |s| {
1048 let ca = s.f64()?;
1049 let mut indicator = APO::new(fastperiod, slowperiod, matype);
1050 let mut values = Vec::with_capacity(s.len());
1051 for i in 0..s.len() {
1052 let val = ca.get(i).unwrap_or(f64::NAN);
1053 values.push(indicator.next(val));
1054 }
1055 Ok(Some(Column::from(Series::new("apo".into(), values))))
1056 },
1057 GetOutput::from_type(DataType::Float64),
1058 )
1059 .alias("apo")])
1060 }
1061
1062 pub fn ppo(
1063 self,
1064 name: &str,
1065 fastperiod: usize,
1066 slowperiod: usize,
1067 matype: talib::MaType,
1068 ) -> LazyFrame {
1069 let name_str = name.to_string();
1070 self.0.clone().with_columns([col(&name_str)
1071 .map(
1072 move |s| {
1073 let ca = s.f64()?;
1074 let mut indicator = PPO::new(fastperiod, slowperiod, matype);
1075 let mut values = Vec::with_capacity(s.len());
1076 for i in 0..s.len() {
1077 let val = ca.get(i).unwrap_or(f64::NAN);
1078 values.push(indicator.next(val));
1079 }
1080 Ok(Some(Column::from(Series::new("ppo".into(), values))))
1081 },
1082 GetOutput::from_type(DataType::Float64),
1083 )
1084 .alias("ppo")])
1085 }
1086
1087 pub fn bop(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
1088 self.ta_4_in_1_out_default::<BOP>(open, high, low, close, "bop")
1089 }
1090
1091 pub fn aroonosc(self, high: &str, low: &str, period: usize) -> LazyFrame {
1092 self.math_operator_2_in_1_out_period::<AROONOSC>(high, low, period, "aroonosc")
1093 }
1094
1095 pub fn mfi(self, high: &str, low: &str, close: &str, volume: &str, period: usize) -> LazyFrame {
1096 let high_str = high.to_string();
1097 let low_str = low.to_string();
1098 let close_str = close.to_string();
1099 let volume_str = volume.to_string();
1100 self.0.clone().with_columns([as_struct(vec![
1101 col(&high_str),
1102 col(&low_str),
1103 col(&close_str),
1104 col(&volume_str),
1105 ])
1106 .map(
1107 move |s| {
1108 let ca = s.struct_()?;
1109 let s_h = ca.field_by_name(&high_str)?;
1110 let s_l = ca.field_by_name(&low_str)?;
1111 let s_c = ca.field_by_name(&close_str)?;
1112 let s_v = ca.field_by_name(&volume_str)?;
1113
1114 let high = s_h.f64()?;
1115 let low = s_l.f64()?;
1116 let close = s_c.f64()?;
1117 let volume = s_v.f64()?;
1118
1119 let mut indicator = MFI::new(period);
1120 let mut values = Vec::with_capacity(s.len());
1121
1122 for i in 0..s.len() {
1123 let h = high.get(i).unwrap_or(f64::NAN);
1124 let l = low.get(i).unwrap_or(f64::NAN);
1125 let c = close.get(i).unwrap_or(f64::NAN);
1126 let v = volume.get(i).unwrap_or(f64::NAN);
1127 values.push(indicator.next((h, l, c, v)));
1128 }
1129
1130 Ok(Some(Column::from(Series::new("mfi".into(), values))))
1131 },
1132 GetOutput::from_type(DataType::Float64),
1133 )
1134 .alias("mfi")])
1135 }
1136
1137 pub fn ultosc(
1138 self,
1139 high: &str,
1140 low: &str,
1141 close: &str,
1142 timeperiod1: usize,
1143 timeperiod2: usize,
1144 timeperiod3: usize,
1145 ) -> LazyFrame {
1146 let high_str = high.to_string();
1147 let low_str = low.to_string();
1148 let close_str = close.to_string();
1149 self.0.clone().with_columns([as_struct(vec![
1150 col(&high_str),
1151 col(&low_str),
1152 col(&close_str),
1153 ])
1154 .map(
1155 move |s| {
1156 let ca = s.struct_()?;
1157 let s_h = ca.field_by_name(&high_str)?;
1158 let s_l = ca.field_by_name(&low_str)?;
1159 let s_c = ca.field_by_name(&close_str)?;
1160 let high = s_h.f64()?;
1161 let low = s_l.f64()?;
1162 let close = s_c.f64()?;
1163
1164 let mut indicator = ULTOSC::new(timeperiod1, timeperiod2, timeperiod3);
1165 let mut values = Vec::with_capacity(s.len());
1166
1167 for i in 0..s.len() {
1168 let h = high.get(i).unwrap_or(f64::NAN);
1169 let l = low.get(i).unwrap_or(f64::NAN);
1170 let c = close.get(i).unwrap_or(f64::NAN);
1171 values.push(indicator.next((h, l, c)));
1172 }
1173
1174 Ok(Some(Column::from(Series::new("ultosc".into(), values))))
1175 },
1176 GetOutput::from_type(DataType::Float64),
1177 )
1178 .alias("ultosc")])
1179 }
1180
1181 pub fn plus_dm(self, high: &str, low: &str, period: usize) -> LazyFrame {
1182 self.math_operator_2_in_1_out_period::<PLUS_DM>(high, low, period, "plus_dm")
1183 }
1184
1185 pub fn minus_dm(self, high: &str, low: &str, period: usize) -> LazyFrame {
1186 self.math_operator_2_in_1_out_period::<MINUS_DM>(high, low, period, "minus_dm")
1187 }
1188
1189 pub fn t3(self, name: &str, period: usize, v_factor: f64) -> LazyFrame {
1190 let name_str = name.to_string();
1191 self.0.clone().with_columns([col(&name_str)
1192 .map(
1193 move |s| {
1194 let ca = s.f64()?;
1195 let mut indicator = T3::new(period, v_factor);
1196 let mut values = Vec::with_capacity(s.len());
1197 for i in 0..s.len() {
1198 let val = ca.get(i).unwrap_or(f64::NAN);
1199 values.push(indicator.next(val));
1200 }
1201 Ok(Some(Column::from(Series::new("t3".into(), values))))
1202 },
1203 GetOutput::from_type(DataType::Float64),
1204 )
1205 .alias("t3")])
1206 }
1207
1208 pub fn mama(self, name: &str, fastlimit: f64, slowlimit: f64) -> LazyFrame {
1209 let name_str = name.to_string();
1210 self.0.clone().with_columns([col(&name_str)
1211 .map(
1212 move |s| {
1213 let ca = s.f64()?;
1214 let mut indicator = MAMA::new(fastlimit, slowlimit);
1215 let mut mama_vals = Vec::with_capacity(s.len());
1216 let mut fama_vals = Vec::with_capacity(s.len());
1217
1218 for i in 0..s.len() {
1219 let val = ca.get(i).unwrap_or(f64::NAN);
1220 let (m, f) = indicator.next(val);
1221 mama_vals.push(m);
1222 fama_vals.push(f);
1223 }
1224
1225 let s_mama = Series::new("mama".into(), mama_vals);
1226 let s_fama = Series::new("fama".into(), fama_vals);
1227
1228 let struct_series = StructChunked::from_series(
1229 "mama_result".into(),
1230 s.len(),
1231 [s_mama, s_fama].iter(),
1232 )?;
1233 Ok(Some(Column::from(struct_series.into_series())))
1234 },
1235 GetOutput::from_type(DataType::Struct(vec![
1236 Field::new("mama".into(), DataType::Float64),
1237 Field::new("fama".into(), DataType::Float64),
1238 ])),
1239 )
1240 .alias("mama")])
1241 }
1242
1243 pub fn sar(self, high: &str, low: &str, acceleration: f64, maximum: f64) -> LazyFrame {
1244 let high_str = high.to_string();
1245 let low_str = low.to_string();
1246 self.0
1247 .clone()
1248 .with_columns([as_struct(vec![col(&high_str), col(&low_str)])
1249 .map(
1250 move |s| {
1251 let ca = s.struct_()?;
1252 let s_h = ca.field_by_name(&high_str)?;
1253 let s_l = ca.field_by_name(&low_str)?;
1254 let high = s_h.f64()?;
1255 let low = s_l.f64()?;
1256
1257 let mut indicator = SAR::new(acceleration, maximum);
1258 let mut values = Vec::with_capacity(s.len());
1259
1260 for i in 0..s.len() {
1261 let h = high.get(i).unwrap_or(f64::NAN);
1262 let l = low.get(i).unwrap_or(f64::NAN);
1263 values.push(indicator.next((h, l)));
1264 }
1265
1266 Ok(Some(Column::from(Series::new("sar".into(), values))))
1267 },
1268 GetOutput::from_type(DataType::Float64),
1269 )
1270 .alias("sar")])
1271 }
1272
1273 #[allow(clippy::too_many_arguments)]
1274 pub fn sarext(
1275 self,
1276 high: &str,
1277 low: &str,
1278 startvalue: f64,
1279 offsetonreverse: f64,
1280 accelerationinitlong: f64,
1281 accelerationlong: f64,
1282 accelerationmaxlong: f64,
1283 accelerationinitshort: f64,
1284 accelerationshort: f64,
1285 accelerationmaxshort: f64,
1286 ) -> LazyFrame {
1287 let high_str = high.to_string();
1288 let low_str = low.to_string();
1289 self.0
1290 .clone()
1291 .with_columns([as_struct(vec![col(&high_str), col(&low_str)])
1292 .map(
1293 move |s| {
1294 let ca = s.struct_()?;
1295 let s_h = ca.field_by_name(&high_str)?;
1296 let s_l = ca.field_by_name(&low_str)?;
1297 let high = s_h.f64()?;
1298 let low = s_l.f64()?;
1299
1300 let mut indicator = SAREXT::new(
1301 startvalue,
1302 offsetonreverse,
1303 accelerationinitlong,
1304 accelerationlong,
1305 accelerationmaxlong,
1306 accelerationinitshort,
1307 accelerationshort,
1308 accelerationmaxshort,
1309 );
1310 let mut values = Vec::with_capacity(s.len());
1311
1312 for i in 0..s.len() {
1313 let h = high.get(i).unwrap_or(f64::NAN);
1314 let l = low.get(i).unwrap_or(f64::NAN);
1315 values.push(indicator.next((h, l)));
1316 }
1317
1318 Ok(Some(Column::from(Series::new("sarext".into(), values))))
1319 },
1320 GetOutput::from_type(DataType::Float64),
1321 )
1322 .alias("sarext")])
1323 }
1324
1325 pub fn mavp(
1326 self,
1327 in1: &str,
1328 in2: &str,
1329 minperiod: usize,
1330 maxperiod: usize,
1331 matype: talib::MaType,
1332 ) -> LazyFrame {
1333 let in1_str = in1.to_string();
1334 let in2_str = in2.to_string();
1335 self.0
1336 .clone()
1337 .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1338 .map(
1339 move |s| {
1340 let ca = s.struct_()?;
1341 let s_1 = ca.field_by_name(&in1_str)?;
1342 let s_2 = ca.field_by_name(&in2_str)?;
1343 let in1_ca = s_1.f64()?;
1344 let in2_ca = s_2.f64()?;
1345
1346 let mut indicator = MAVP::new(minperiod, maxperiod, matype);
1347 let mut values = Vec::with_capacity(s.len());
1348
1349 for i in 0..s.len() {
1350 let i1 = in1_ca.get(i).unwrap_or(f64::NAN);
1351 let i2 = in2_ca.get(i).unwrap_or(f64::NAN);
1352 values.push(indicator.next((i1, i2)));
1353 }
1354
1355 Ok(Some(Column::from(Series::new("mavp".into(), values))))
1356 },
1357 GetOutput::from_type(DataType::Float64),
1358 )
1359 .alias("mavp")])
1360 }
1361
1362 pub fn ht_phasor(self, name: &str) -> LazyFrame {
1363 let name_str = name.to_string();
1364 self.0.clone().with_columns([col(&name_str)
1365 .map(
1366 move |s| {
1367 let ca = s.f64()?;
1368 let mut indicator = HT_PHASOR::new();
1369 let mut inphase_vals = Vec::with_capacity(s.len());
1370 let mut quadrature_vals = Vec::with_capacity(s.len());
1371
1372 for i in 0..s.len() {
1373 let val = ca.get(i).unwrap_or(f64::NAN);
1374 let (inp, q) = indicator.next(val);
1375 inphase_vals.push(inp);
1376 quadrature_vals.push(q);
1377 }
1378
1379 let s_inphase = Series::new("inphase".into(), inphase_vals);
1380 let s_quadrature = Series::new("quadrature".into(), quadrature_vals);
1381
1382 let struct_series = StructChunked::from_series(
1383 "ht_phasor_result".into(),
1384 s.len(),
1385 [s_inphase, s_quadrature].iter(),
1386 )?;
1387 Ok(Some(Column::from(struct_series.into_series())))
1388 },
1389 GetOutput::from_type(DataType::Struct(vec![
1390 Field::new("inphase".into(), DataType::Float64),
1391 Field::new("quadrature".into(), DataType::Float64),
1392 ])),
1393 )
1394 .alias("ht_phasor")])
1395 }
1396
1397 pub fn ht_sine(self, name: &str) -> LazyFrame {
1398 let name_str = name.to_string();
1399 self.0.clone().with_columns([col(&name_str)
1400 .map(
1401 move |s| {
1402 let ca = s.f64()?;
1403 let mut indicator = HT_SINE::new();
1404 let mut sine_vals = Vec::with_capacity(s.len());
1405 let mut leadsine_vals = Vec::with_capacity(s.len());
1406
1407 for i in 0..s.len() {
1408 let val = ca.get(i).unwrap_or(f64::NAN);
1409 let (si, ls) = indicator.next(val);
1410 sine_vals.push(si);
1411 leadsine_vals.push(ls);
1412 }
1413
1414 let s_sine = Series::new("sine".into(), sine_vals);
1415 let s_leadsine = Series::new("leadsine".into(), leadsine_vals);
1416
1417 let struct_series = StructChunked::from_series(
1418 "ht_sine_result".into(),
1419 s.len(),
1420 [s_sine, s_leadsine].iter(),
1421 )?;
1422 Ok(Some(Column::from(struct_series.into_series())))
1423 },
1424 GetOutput::from_type(DataType::Struct(vec![
1425 Field::new("sine".into(), DataType::Float64),
1426 Field::new("leadsine".into(), DataType::Float64),
1427 ])),
1428 )
1429 .alias("ht_sine")])
1430 }
1431
1432 fn ta_3_in_1_out_period<I>(
1433 self,
1434 in1: &str,
1435 in2: &str,
1436 in3: &str,
1437 period: usize,
1438 output_name: &str,
1439 ) -> LazyFrame
1440 where
1441 I: Next<(f64, f64, f64), Output = f64> + Send + Sync + 'static,
1442 I: From<usize>,
1443 {
1444 let in1_str = in1.to_string();
1445 let in2_str = in2.to_string();
1446 let in3_str = in3.to_string();
1447 let output_name_str = output_name.to_string();
1448 let output_name_for_closure = output_name_str.clone();
1449 self.0.clone().with_columns(
1450 [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
1451 .map(
1452 move |s| {
1453 let ca = s.struct_()?;
1454 let s1 = ca.field_by_name(&in1_str)?;
1455 let s2 = ca.field_by_name(&in2_str)?;
1456 let s3 = ca.field_by_name(&in3_str)?;
1457
1458 let ca1 = s1.f64()?;
1459 let ca2 = s2.f64()?;
1460 let ca3 = s3.f64()?;
1461
1462 let mut indicator = I::from(period);
1463 let mut values = Vec::with_capacity(s.len());
1464
1465 for i in 0..s.len() {
1466 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1467 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1468 let v3 = ca3.get(i).unwrap_or(f64::NAN);
1469 values.push(indicator.next((v1, v2, v3)));
1470 }
1471
1472 Ok(Some(Column::from(Series::new(
1473 output_name_for_closure.clone().into(),
1474 values,
1475 ))))
1476 },
1477 GetOutput::from_type(DataType::Float64),
1478 )
1479 .alias(&output_name_str)],
1480 )
1481 }
1482
1483 fn ta_3_in_1_out_default<I>(
1484 self,
1485 in1: &str,
1486 in2: &str,
1487 in3: &str,
1488 output_name: &str,
1489 ) -> LazyFrame
1490 where
1491 I: Next<(f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
1492 {
1493 let in1_str = in1.to_string();
1494 let in2_str = in2.to_string();
1495 let in3_str = in3.to_string();
1496 let output_name_str = output_name.to_string();
1497 let output_name_for_closure = output_name_str.clone();
1498 self.0.clone().with_columns(
1499 [as_struct(vec![col(&in1_str), col(&in2_str), col(&in3_str)])
1500 .map(
1501 move |s| {
1502 let ca = s.struct_()?;
1503 let s1 = ca.field_by_name(&in1_str)?;
1504 let s2 = ca.field_by_name(&in2_str)?;
1505 let s3 = ca.field_by_name(&in3_str)?;
1506
1507 let ca1 = s1.f64()?;
1508 let ca2 = s2.f64()?;
1509 let ca3 = s3.f64()?;
1510
1511 let mut indicator = I::default();
1512 let mut values = Vec::with_capacity(s.len());
1513
1514 for i in 0..s.len() {
1515 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1516 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1517 let v3 = ca3.get(i).unwrap_or(f64::NAN);
1518 values.push(indicator.next((v1, v2, v3)));
1519 }
1520
1521 Ok(Some(Column::from(Series::new(
1522 output_name_for_closure.clone().into(),
1523 values,
1524 ))))
1525 },
1526 GetOutput::from_type(DataType::Float64),
1527 )
1528 .alias(&output_name_str)],
1529 )
1530 }
1531
1532 fn ta_4_in_1_out_default<I>(
1533 self,
1534 in1: &str,
1535 in2: &str,
1536 in3: &str,
1537 in4: &str,
1538 output_name: &str,
1539 ) -> LazyFrame
1540 where
1541 I: Next<(f64, f64, f64, f64), Output = f64> + Default + Send + Sync + 'static,
1542 {
1543 let in1_str = in1.to_string();
1544 let in2_str = in2.to_string();
1545 let in3_str = in3.to_string();
1546 let in4_str = in4.to_string();
1547 let output_name_str = output_name.to_string();
1548 let output_name_for_closure = output_name_str.clone();
1549 self.0.clone().with_columns([as_struct(vec![
1550 col(&in1_str),
1551 col(&in2_str),
1552 col(&in3_str),
1553 col(&in4_str),
1554 ])
1555 .map(
1556 move |s| {
1557 let ca = s.struct_()?;
1558 let s1 = ca.field_by_name(&in1_str)?;
1559 let s2 = ca.field_by_name(&in2_str)?;
1560 let s3 = ca.field_by_name(&in3_str)?;
1561 let s4 = ca.field_by_name(&in4_str)?;
1562
1563 let ca1 = s1.f64()?;
1564 let ca2 = s2.f64()?;
1565 let ca3 = s3.f64()?;
1566 let ca4 = s4.f64()?;
1567
1568 let mut indicator = I::default();
1569 let mut values = Vec::with_capacity(s.len());
1570
1571 for i in 0..s.len() {
1572 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1573 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1574 let v3 = ca3.get(i).unwrap_or(f64::NAN);
1575 let v4 = ca4.get(i).unwrap_or(f64::NAN);
1576 values.push(indicator.next((v1, v2, v3, v4)));
1577 }
1578
1579 Ok(Some(Column::from(Series::new(
1580 output_name_for_closure.clone().into(),
1581 values,
1582 ))))
1583 },
1584 GetOutput::from_type(DataType::Float64),
1585 )
1586 .alias(&output_name_str)])
1587 }
1588
1589 fn math_transform_1_in_1_out<I>(self, name: &str, output_name: &str) -> LazyFrame
1590 where
1591 I: Next<f64, Output = f64> + Default + Send + Sync + 'static,
1592 {
1593 let name = name.to_string();
1594 let output_name_str = output_name.to_string();
1595 let output_name_for_closure = output_name_str.clone();
1596 self.0.clone().with_columns([col(&name)
1597 .map(
1598 move |s| {
1599 let ca = s.f64()?;
1600 let mut indicator = I::default();
1601 let mut values = Vec::with_capacity(s.len());
1602
1603 for i in 0..s.len() {
1604 let val = ca.get(i).unwrap_or(f64::NAN);
1605 values.push(indicator.next(val));
1606 }
1607
1608 Ok(Some(Column::from(Series::new(
1609 output_name_for_closure.clone().into(),
1610 values,
1611 ))))
1612 },
1613 GetOutput::from_type(DataType::Float64),
1614 )
1615 .alias(&output_name_str)])
1616 }
1617
1618 fn math_operator_2_in_1_out<I>(self, in1: &str, in2: &str, output_name: &str) -> LazyFrame
1619 where
1620 I: Next<(f64, f64), Output = f64> + Default + Send + Sync + 'static,
1621 {
1622 let in1_str = in1.to_string();
1623 let in2_str = in2.to_string();
1624 let output_name_str = output_name.to_string();
1625 let output_name_for_closure = output_name_str.clone();
1626 self.0
1627 .clone()
1628 .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1629 .map(
1630 move |s| {
1631 let ca = s.struct_()?;
1632 let s1 = ca.field_by_name(&in1_str)?;
1633 let s2 = ca.field_by_name(&in2_str)?;
1634
1635 let ca1 = s1.f64()?;
1636 let ca2 = s2.f64()?;
1637
1638 let mut indicator = I::default();
1639 let mut values = Vec::with_capacity(s.len());
1640
1641 for i in 0..s.len() {
1642 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1643 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1644 values.push(indicator.next((v1, v2)));
1645 }
1646
1647 Ok(Some(Column::from(Series::new(
1648 output_name_for_closure.clone().into(),
1649 values,
1650 ))))
1651 },
1652 GetOutput::from_type(DataType::Float64),
1653 )
1654 .alias(&output_name_str)])
1655 }
1656
1657 fn math_operator_1_in_1_out_period<I>(
1658 self,
1659 name: &str,
1660 period: usize,
1661 output_name: &str,
1662 ) -> LazyFrame
1663 where
1664 I: Next<f64, Output = f64> + Send + Sync + 'static,
1665 I: From<usize>,
1666 {
1667 let name = name.to_string();
1668 let output_name_str = output_name.to_string();
1669 let output_name_for_closure = output_name_str.clone();
1670 self.0.clone().with_columns([col(&name)
1671 .map(
1672 move |s| {
1673 let ca = s.f64()?;
1674 let mut indicator = I::from(period);
1675 let mut values = Vec::with_capacity(s.len());
1676
1677 for i in 0..s.len() {
1678 let val = ca.get(i).unwrap_or(f64::NAN);
1679 values.push(indicator.next(val));
1680 }
1681
1682 Ok(Some(Column::from(Series::new(
1683 output_name_for_closure.clone().into(),
1684 values,
1685 ))))
1686 },
1687 GetOutput::from_type(DataType::Float64),
1688 )
1689 .alias(&output_name_str)])
1690 }
1691
1692 fn math_operator_2_in_1_out_period<I>(
1693 self,
1694 in1: &str,
1695 in2: &str,
1696 period: usize,
1697 output_name: &str,
1698 ) -> LazyFrame
1699 where
1700 I: Next<(f64, f64), Output = f64> + Send + Sync + 'static,
1701 I: From<usize>,
1702 {
1703 let in1_str = in1.to_string();
1704 let in2_str = in2.to_string();
1705 let output_name_str = output_name.to_string();
1706 let output_name_for_closure = output_name_str.clone();
1707 self.0
1708 .clone()
1709 .with_columns([as_struct(vec![col(&in1_str), col(&in2_str)])
1710 .map(
1711 move |s| {
1712 let ca = s.struct_()?;
1713 let s1 = ca.field_by_name(&in1_str)?;
1714 let s2 = ca.field_by_name(&in2_str)?;
1715
1716 let ca1 = s1.f64()?;
1717 let ca2 = s2.f64()?;
1718
1719 let mut indicator = I::from(period);
1720 let mut values = Vec::with_capacity(s.len());
1721
1722 for i in 0..s.len() {
1723 let v1 = ca1.get(i).unwrap_or(f64::NAN);
1724 let v2 = ca2.get(i).unwrap_or(f64::NAN);
1725 values.push(indicator.next((v1, v2)));
1726 }
1727
1728 Ok(Some(Column::from(Series::new(
1729 output_name_for_closure.clone().into(),
1730 values,
1731 ))))
1732 },
1733 GetOutput::from_type(DataType::Float64),
1734 )
1735 .alias(&output_name_str)])
1736 }
1737
1738 pub fn supertrend(self, period: usize, multiplier: f64) -> LazyFrame {
1739 self.0
1740 .clone()
1741 .with_columns([as_struct(vec![col("high"), col("low"), col("close")])
1742 .map(
1743 move |s| {
1744 let ca = s.struct_()?;
1745 let s_high = ca.field_by_name("high")?;
1746 let s_low = ca.field_by_name("low")?;
1747 let s_close = ca.field_by_name("close")?;
1748
1749 let high = s_high.f64()?;
1750 let low = s_low.f64()?;
1751 let close = s_close.f64()?;
1752
1753 let mut st = SuperTrend::new(period, multiplier);
1754 let mut values = Vec::with_capacity(s.len());
1755 let mut directions = Vec::with_capacity(s.len());
1756
1757 for i in 0..s.len() {
1758 let h = high.get(i).unwrap_or(0.0);
1759 let l = low.get(i).unwrap_or(0.0);
1760 let c = close.get(i).unwrap_or(0.0);
1761 let (val, dir) = st.next((h, l, c));
1762 values.push(val);
1763 directions.push(dir as f64);
1764 }
1765
1766 let st_series = Series::new("supertrend".into(), values);
1767 let dir_series = Series::new("supertrend_direction".into(), directions);
1768
1769 let out = StructChunked::from_series(
1770 "supertrend_output".into(),
1771 s.len(),
1772 [st_series, dir_series].iter(),
1773 )?;
1774 Ok(Some(Column::from(out.into_series())))
1775 },
1776 GetOutput::from_type(DataType::Struct(vec![
1777 Field::new("supertrend".into(), DataType::Float64),
1778 Field::new("supertrend_direction".into(), DataType::Float64),
1779 ])),
1780 )
1781 .alias("supertrend_data")])
1782 }
1783
1784 pub fn anchored_vwap(self, price: &str, volume: &str, anchor: &str) -> LazyFrame {
1797 let price = price.to_string();
1798 let volume = volume.to_string();
1799 let anchor = anchor.to_string();
1800
1801 self.0
1802 .clone()
1803 .with_columns([as_struct(vec![col(&price), col(&volume), col(&anchor)])
1804 .map(
1805 move |s| {
1806 let ca = s.struct_()?;
1807 let s_price = ca.field_by_name(&price)?;
1808 let s_volume = ca.field_by_name(&volume)?;
1809 let s_anchor = ca.field_by_name(&anchor)?;
1810
1811 let price = s_price.f64()?;
1812 let volume = s_volume.f64()?;
1813 let anchor = s_anchor.bool()?;
1814
1815 let mut avwap = quantwave_core::AnchoredVWAP::new();
1816 let mut values = Vec::with_capacity(s.len());
1817
1818 for i in 0..s.len() {
1819 let p = price.get(i).unwrap_or(0.0);
1820 let v = volume.get(i).unwrap_or(0.0);
1821 let a = anchor.get(i).unwrap_or(false);
1822 values.push(avwap.next((p, v, a)));
1823 }
1824
1825 Ok(Some(Column::from(Series::new(
1826 "anchored_vwap".into(),
1827 values,
1828 ))))
1829 },
1830 GetOutput::from_type(DataType::Float64),
1831 )
1832 .alias("avwap")])
1833 }
1834
1835 pub fn hma(self, name: &str, period: usize) -> LazyFrame {
1836 let name = name.to_string();
1837 self.0.clone().with_columns([col(&name)
1838 .map(
1839 move |s| {
1840 let ca = s.f64()?;
1841 let mut hma = quantwave_core::HMA::new(period);
1842 let mut values = Vec::with_capacity(s.len());
1843
1844 for i in 0..s.len() {
1845 let val = ca.get(i).unwrap_or(0.0);
1846 values.push(hma.next(val));
1847 }
1848
1849 Ok(Some(Column::from(Series::new("hma".into(), values))))
1850 },
1851 GetOutput::from_type(DataType::Float64),
1852 )
1853 .alias("hma")])
1854 }
1855
1856 pub fn kalman(self, name: &str, q: f64, r: f64) -> LazyFrame {
1857 let name = name.to_string();
1858 self.0.clone().with_columns([col(&name)
1859 .map(
1860 move |s| {
1861 let ca = s.f64()?;
1862 let mut indicator = quantwave_core::indicators::kalman::KalmanFilter::new(q, r);
1863 let mut values = Vec::with_capacity(s.len());
1864
1865 for i in 0..s.len() {
1866 let val = ca.get(i).unwrap_or(f64::NAN);
1867 values.push(indicator.next(val));
1868 }
1869
1870 Ok(Some(Column::from(Series::new("kalman".into(), values))))
1871 },
1872 GetOutput::from_type(DataType::Float64),
1873 )
1874 .alias("kalman")])
1875 }
1876
1877 pub fn kinematic_kalman(self, name: &str, q_pos: f64, q_vel: f64, r: f64) -> LazyFrame {
1878 let name = name.to_string();
1879 self.0.clone().with_columns([col(&name)
1880 .map(
1881 move |s| {
1882 let ca = s.f64()?;
1883 let mut indicator =
1884 quantwave_core::indicators::kinematic_kalman::KinematicKalmanFilter::new(
1885 q_pos, q_vel, r,
1886 );
1887 let mut values = Vec::with_capacity(s.len());
1888
1889 for i in 0..s.len() {
1890 let val = ca.get(i).unwrap_or(f64::NAN);
1891 values.push(indicator.next(val));
1892 }
1893
1894 Ok(Some(Column::from(Series::new(
1895 "kinematic_kalman".into(),
1896 values,
1897 ))))
1898 },
1899 GetOutput::from_type(DataType::Float64),
1900 )
1901 .alias("kinematic_kalman")])
1902 }
1903
1904 pub fn vpn(
1905 self,
1906 high: &str,
1907 low: &str,
1908 close: &str,
1909 volume: &str,
1910 period: usize,
1911 smooth_period: usize,
1912 ) -> LazyFrame {
1913 let high_str = high.to_string();
1914 let low_str = low.to_string();
1915 let close_str = close.to_string();
1916 let volume_str = volume.to_string();
1917
1918 self.0.clone().with_columns([as_struct(vec![
1919 col(&high_str),
1920 col(&low_str),
1921 col(&close_str),
1922 col(&volume_str),
1923 ])
1924 .map(
1925 move |s| {
1926 let ca = s.struct_()?;
1927 let s_h = ca.field_by_name(&high_str)?;
1928 let s_l = ca.field_by_name(&low_str)?;
1929 let s_c = ca.field_by_name(&close_str)?;
1930 let s_v = ca.field_by_name(&volume_str)?;
1931
1932 let high = s_h.f64()?;
1933 let low = s_l.f64()?;
1934 let close = s_c.f64()?;
1935 let volume = s_v.f64()?;
1936
1937 let mut indicator = quantwave_core::VPNIndicator::new(period, smooth_period);
1938 let mut values = Vec::with_capacity(s.len());
1939
1940 for i in 0..s.len() {
1941 let h = high.get(i).unwrap_or(f64::NAN);
1942 let l = low.get(i).unwrap_or(f64::NAN);
1943 let c = close.get(i).unwrap_or(f64::NAN);
1944 let v = volume.get(i).unwrap_or(f64::NAN);
1945 values.push(indicator.next((h, l, c, v)));
1946 }
1947
1948 Ok(Some(Column::from(Series::new("vpn".into(), values))))
1949 },
1950 GetOutput::from_type(DataType::Float64),
1951 )
1952 .alias("vpn")])
1953 }
1954
1955 pub fn gap_momentum(
1956 self,
1957 open: &str,
1958 close: &str,
1959 period: usize,
1960 signal_period: usize,
1961 ) -> LazyFrame {
1962 let open_str = open.to_string();
1963 let close_str = close.to_string();
1964
1965 self.0
1966 .clone()
1967 .with_columns([as_struct(vec![col(&open_str), col(&close_str)])
1968 .map(
1969 move |s| {
1970 let ca = s.struct_()?;
1971 let s_o = ca.field_by_name(&open_str)?;
1972 let s_c = ca.field_by_name(&close_str)?;
1973
1974 let open = s_o.f64()?;
1975 let close = s_c.f64()?;
1976
1977 let mut indicator = quantwave_core::GapMomentum::new(period, signal_period);
1978 let mut ratio_vals = Vec::with_capacity(s.len());
1979 let mut signal_vals = Vec::with_capacity(s.len());
1980
1981 for i in 0..s.len() {
1982 let o = open.get(i).unwrap_or(f64::NAN);
1983 let c = close.get(i).unwrap_or(f64::NAN);
1984 let (ratio, signal) = indicator.next((o, c));
1985 ratio_vals.push(ratio);
1986 signal_vals.push(signal);
1987 }
1988
1989 let s_ratio = Series::new("gap_ratio".into(), ratio_vals);
1990 let s_signal = Series::new("gap_signal".into(), signal_vals);
1991 let struct_series = StructChunked::from_series(
1992 "gap_momentum_result".into(),
1993 s.len(),
1994 [s_ratio, s_signal].iter(),
1995 )?;
1996 Ok(Some(Column::from(struct_series.into_series())))
1997 },
1998 GetOutput::from_type(DataType::Struct(vec![
1999 Field::new("gap_ratio".into(), DataType::Float64),
2000 Field::new("gap_signal".into(), DataType::Float64),
2001 ])),
2002 )
2003 .alias("gap_momentum")])
2004 }
2005
2006 pub fn autotune_filter(self, name: &str, window: usize, bandwidth: f64) -> LazyFrame {
2007 let name_str = name.to_string();
2008 self.0.clone().with_columns([col(&name_str)
2009 .map(
2010 move |s| {
2011 let ca = s.f64()?;
2012 let mut indicator = quantwave_core::AutoTuneFilter::new(window, bandwidth);
2013 let mut values = Vec::with_capacity(s.len());
2014
2015 for i in 0..s.len() {
2016 let val = ca.get(i).unwrap_or(f64::NAN);
2017 values.push(indicator.next(val));
2018 }
2019
2020 Ok(Some(Column::from(Series::new("autotune".into(), values))))
2021 },
2022 GetOutput::from_type(DataType::Float64),
2023 )
2024 .alias("autotune")])
2025 }
2026
2027 pub fn adaptive_ema(
2028 self,
2029 high: &str,
2030 low: &str,
2031 close: &str,
2032 period: usize,
2033 pds: usize,
2034 ) -> LazyFrame {
2035 let h_str = high.to_string();
2036 let l_str = low.to_string();
2037 let c_str = close.to_string();
2038
2039 self.0
2040 .clone()
2041 .with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2042 .map(
2043 move |s| {
2044 let ca = s.struct_()?;
2045 let f_h = ca.field_by_name(&h_str)?;
2046 let high = f_h.f64()?;
2047 let f_l = ca.field_by_name(&l_str)?;
2048 let low = f_l.f64()?;
2049 let f_c = ca.field_by_name(&c_str)?;
2050 let close = f_c.f64()?;
2051
2052 let mut indicator = quantwave_core::AdaptiveEMA::new(period, pds);
2053 let mut values = Vec::with_capacity(s.len());
2054
2055 for i in 0..s.len() {
2056 let h = high.get(i).unwrap_or(f64::NAN);
2057 let l = low.get(i).unwrap_or(f64::NAN);
2058 let c = close.get(i).unwrap_or(f64::NAN);
2059 values.push(indicator.next((h, l, c)));
2060 }
2061
2062 Ok(Some(Column::from(Series::new(
2063 "adaptive_ema".into(),
2064 values,
2065 ))))
2066 },
2067 GetOutput::from_type(DataType::Float64),
2068 )
2069 .alias("adaptive_ema")])
2070 }
2071
2072 pub fn tradj_ema(
2073 self,
2074 high: &str,
2075 low: &str,
2076 close: &str,
2077 period: usize,
2078 pds: usize,
2079 mltp: f64,
2080 ) -> LazyFrame {
2081 let h_str = high.to_string();
2082 let l_str = low.to_string();
2083 let c_str = close.to_string();
2084
2085 self.0
2086 .clone()
2087 .with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2088 .map(
2089 move |s| {
2090 let ca = s.struct_()?;
2091 let f_h = ca.field_by_name(&h_str)?;
2092 let high = f_h.f64()?;
2093 let f_l = ca.field_by_name(&l_str)?;
2094 let low = f_l.f64()?;
2095 let f_c = ca.field_by_name(&c_str)?;
2096 let close = f_c.f64()?;
2097
2098 let mut indicator = quantwave_core::TRAdjEMA::new(period, pds, mltp);
2099 let mut values = Vec::with_capacity(s.len());
2100
2101 for i in 0..s.len() {
2102 let h = high.get(i).unwrap_or(f64::NAN);
2103 let l = low.get(i).unwrap_or(f64::NAN);
2104 let c = close.get(i).unwrap_or(f64::NAN);
2105 values.push(indicator.next((h, l, c)));
2106 }
2107
2108 Ok(Some(Column::from(Series::new("tradj_ema".into(), values))))
2109 },
2110 GetOutput::from_type(DataType::Float64),
2111 )
2112 .alias("tradj_ema")])
2113 }
2114
2115 pub fn obvm(
2116 self,
2117 high: &str,
2118 low: &str,
2119 close: &str,
2120 volume: &str,
2121 obvm_period: usize,
2122 signal_period: usize,
2123 ) -> LazyFrame {
2124 let h_str = high.to_string();
2125 let l_str = low.to_string();
2126 let c_str = close.to_string();
2127 let v_str = volume.to_string();
2128
2129 self.0.clone().with_columns([as_struct(vec![
2130 col(&h_str),
2131 col(&l_str),
2132 col(&c_str),
2133 col(&v_str),
2134 ])
2135 .map(
2136 move |s| {
2137 let ca = s.struct_()?;
2138 let f_h = ca.field_by_name(&h_str)?;
2139 let high = f_h.f64()?;
2140 let f_l = ca.field_by_name(&l_str)?;
2141 let low = f_l.f64()?;
2142 let f_c = ca.field_by_name(&c_str)?;
2143 let close = f_c.f64()?;
2144 let f_v = ca.field_by_name(&v_str)?;
2145 let volume = f_v.f64()?;
2146
2147 let mut indicator = quantwave_core::Obvm::new(obvm_period, signal_period);
2148 let mut obvm_vals = Vec::with_capacity(s.len());
2149 let mut signal_vals = Vec::with_capacity(s.len());
2150
2151 for i in 0..s.len() {
2152 let h = high.get(i).unwrap_or(f64::NAN);
2153 let l = low.get(i).unwrap_or(f64::NAN);
2154 let c = close.get(i).unwrap_or(f64::NAN);
2155 let v = volume.get(i).unwrap_or(f64::NAN);
2156 let (o, sig) = indicator.next((h, l, c, v));
2157 obvm_vals.push(o);
2158 signal_vals.push(sig);
2159 }
2160
2161 let s_obvm = Series::new("obvm".into(), obvm_vals);
2162 let s_signal = Series::new("signal".into(), signal_vals);
2163 let struct_series = StructChunked::from_series(
2164 "obvm_data".into(),
2165 s.len(),
2166 [s_obvm, s_signal].iter(),
2167 )?;
2168 Ok(Some(Column::from(struct_series.into_series())))
2169 },
2170 GetOutput::from_type(DataType::Struct(vec![
2171 Field::new("obvm".into(), DataType::Float64),
2172 Field::new("signal".into(), DataType::Float64),
2173 ])),
2174 )
2175 .alias("obvm_data")])
2176 }
2177
2178 #[allow(clippy::too_many_arguments)]
2179 pub fn vfi(
2180 self,
2181 high: &str,
2182 low: &str,
2183 close: &str,
2184 volume: &str,
2185 period: usize,
2186 coef: f64,
2187 vcoef: f64,
2188 smoothing_period: usize,
2189 ) -> LazyFrame {
2190 let h_str = high.to_string();
2191 let l_str = low.to_string();
2192 let c_str = close.to_string();
2193 let v_str = volume.to_string();
2194
2195 self.0.clone().with_columns([as_struct(vec![
2196 col(&h_str),
2197 col(&l_str),
2198 col(&c_str),
2199 col(&v_str),
2200 ])
2201 .map(
2202 move |s| {
2203 let ca = s.struct_()?;
2204 let f_h = ca.field_by_name(&h_str)?;
2205 let high = f_h.f64()?;
2206 let f_l = ca.field_by_name(&l_str)?;
2207 let low = f_l.f64()?;
2208 let f_c = ca.field_by_name(&c_str)?;
2209 let close = f_c.f64()?;
2210 let f_v = ca.field_by_name(&v_str)?;
2211 let volume = f_v.f64()?;
2212
2213 let mut indicator = quantwave_core::Vfi::new(period, coef, vcoef, smoothing_period);
2214 let mut values = Vec::with_capacity(s.len());
2215
2216 for i in 0..s.len() {
2217 let h = high.get(i).unwrap_or(f64::NAN);
2218 let l = low.get(i).unwrap_or(f64::NAN);
2219 let c = close.get(i).unwrap_or(f64::NAN);
2220 let v = volume.get(i).unwrap_or(f64::NAN);
2221 values.push(indicator.next((h, l, c, v)));
2222 }
2223
2224 Ok(Some(Column::from(Series::new("vfi".into(), values))))
2225 },
2226 GetOutput::from_type(DataType::Float64),
2227 )
2228 .alias("vfi")])
2229 }
2230
2231 #[allow(clippy::too_many_arguments)]
2232 pub fn sve_volatility_bands(
2233 self,
2234 high: &str,
2235 low: &str,
2236 close: &str,
2237 bands_period: usize,
2238 bands_deviation: f64,
2239 low_band_adjust: f64,
2240 mid_line_length: usize,
2241 ) -> LazyFrame {
2242 let h_str = high.to_string();
2243 let l_str = low.to_string();
2244 let c_str = close.to_string();
2245
2246 self.0
2247 .clone()
2248 .with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2249 .map(
2250 move |s| {
2251 let ca = s.struct_()?;
2252 let f_h = ca.field_by_name(&h_str)?;
2253 let high = f_h.f64()?;
2254 let f_l = ca.field_by_name(&l_str)?;
2255 let low = f_l.f64()?;
2256 let f_c = ca.field_by_name(&c_str)?;
2257 let close = f_c.f64()?;
2258
2259 let mut indicator = quantwave_core::SVEVolatilityBands::new(
2260 bands_period,
2261 bands_deviation,
2262 low_band_adjust,
2263 mid_line_length,
2264 );
2265 let mut upper_vals = Vec::with_capacity(s.len());
2266 let mut mid_vals = Vec::with_capacity(s.len());
2267 let mut lower_vals = Vec::with_capacity(s.len());
2268
2269 for i in 0..s.len() {
2270 let h = high.get(i).unwrap_or(f64::NAN);
2271 let l = low.get(i).unwrap_or(f64::NAN);
2272 let c = close.get(i).unwrap_or(f64::NAN);
2273 let (upper, mid, lower) = indicator.next((h, l, c));
2274 upper_vals.push(upper);
2275 mid_vals.push(mid);
2276 lower_vals.push(lower);
2277 }
2278
2279 let s_upper = Series::new("upper".into(), upper_vals);
2280 let s_mid = Series::new("middle".into(), mid_vals);
2281 let s_lower = Series::new("lower".into(), lower_vals);
2282 let struct_series = StructChunked::from_series(
2283 "sve_bands_data".into(),
2284 s.len(),
2285 [s_upper, s_mid, s_lower].iter(),
2286 )?;
2287 Ok(Some(Column::from(struct_series.into_series())))
2288 },
2289 GetOutput::from_type(DataType::Struct(vec![
2290 Field::new("upper".into(), DataType::Float64),
2291 Field::new("middle".into(), DataType::Float64),
2292 Field::new("lower".into(), DataType::Float64),
2293 ])),
2294 )
2295 .alias("sve_bands_data")])
2296 }
2297
2298 pub fn exp_dev_bands(
2299 self,
2300 name: &str,
2301 period: usize,
2302 multiplier: f64,
2303 use_sma: bool,
2304 ) -> LazyFrame {
2305 let name_str = name.to_string();
2306 self.0.clone().with_columns([col(&name_str)
2307 .map(
2308 move |s| {
2309 let ca = s.f64()?;
2310 let mut indicator =
2311 quantwave_core::ExpDevBands::new(period, multiplier, use_sma);
2312 let mut upper_vals = Vec::with_capacity(s.len());
2313 let mut mid_vals = Vec::with_capacity(s.len());
2314 let mut lower_vals = Vec::with_capacity(s.len());
2315
2316 for i in 0..s.len() {
2317 let val = ca.get(i).unwrap_or(f64::NAN);
2318 let (upper, mid, lower) = indicator.next(val);
2319 upper_vals.push(upper);
2320 mid_vals.push(mid);
2321 lower_vals.push(lower);
2322 }
2323
2324 let s_upper = Series::new("upper".into(), upper_vals);
2325 let s_mid = Series::new("middle".into(), mid_vals);
2326 let s_lower = Series::new("lower".into(), lower_vals);
2327 let struct_series = StructChunked::from_series(
2328 "exp_dev_bands_data".into(),
2329 s.len(),
2330 [s_upper, s_mid, s_lower].iter(),
2331 )?;
2332 Ok(Some(Column::from(struct_series.into_series())))
2333 },
2334 GetOutput::from_type(DataType::Struct(vec![
2335 Field::new("upper".into(), DataType::Float64),
2336 Field::new("middle".into(), DataType::Float64),
2337 Field::new("lower".into(), DataType::Float64),
2338 ])),
2339 )
2340 .alias("exp_dev_bands_data")])
2341 }
2342
2343 pub fn sdo(
2344 self,
2345 name: &str,
2346 lookback_period: usize,
2347 period: usize,
2348 ema_pds: usize,
2349 ) -> LazyFrame {
2350 let name_str = name.to_string();
2351 self.0.clone().with_columns([col(&name_str)
2352 .map(
2353 move |s| {
2354 let ca = s.f64()?;
2355 let mut indicator = quantwave_core::SDO::new(lookback_period, period, ema_pds);
2356 let mut values = Vec::with_capacity(s.len());
2357
2358 for i in 0..s.len() {
2359 let val = ca.get(i).unwrap_or(f64::NAN);
2360 values.push(indicator.next(val));
2361 }
2362
2363 Ok(Some(Column::from(Series::new("sdo".into(), values))))
2364 },
2365 GetOutput::from_type(DataType::Float64),
2366 )
2367 .alias("sdo")])
2368 }
2369
2370 pub fn rsmk(self, price: &str, benchmark: &str, length: usize, ema_length: usize) -> LazyFrame {
2371 let p_str = price.to_string();
2372 let b_str = benchmark.to_string();
2373
2374 self.0
2375 .clone()
2376 .with_columns([as_struct(vec![col(&p_str), col(&b_str)])
2377 .map(
2378 move |s| {
2379 let ca = s.struct_()?;
2380 let f_p = ca.field_by_name(&p_str)?;
2381 let price = f_p.f64()?;
2382 let f_b = ca.field_by_name(&b_str)?;
2383 let benchmark = f_b.f64()?;
2384
2385 let mut indicator = quantwave_core::RSMK::new(length, ema_length);
2386 let mut values = Vec::with_capacity(s.len());
2387
2388 for i in 0..s.len() {
2389 let p = price.get(i).unwrap_or(f64::NAN);
2390 let b = benchmark.get(i).unwrap_or(f64::NAN);
2391 values.push(indicator.next((p, b)));
2392 }
2393
2394 Ok(Some(Column::from(Series::new("rsmk".into(), values))))
2395 },
2396 GetOutput::from_type(DataType::Float64),
2397 )
2398 .alias("rsmk")])
2399 }
2400
2401 pub fn rodc(
2402 self,
2403 name: &str,
2404 window_size: usize,
2405 threshold: f64,
2406 smooth_period: usize,
2407 ) -> LazyFrame {
2408 let name_str = name.to_string();
2409 self.0.clone().with_columns([col(&name_str)
2410 .map(
2411 move |s| {
2412 let ca = s.f64()?;
2413 let mut indicator =
2414 quantwave_core::RODC::new(window_size, threshold, smooth_period);
2415 let mut values = Vec::with_capacity(s.len());
2416
2417 for i in 0..s.len() {
2418 let val = ca.get(i).unwrap_or(f64::NAN);
2419 values.push(indicator.next(val));
2420 }
2421
2422 Ok(Some(Column::from(Series::new("rodc".into(), values))))
2423 },
2424 GetOutput::from_type(DataType::Float64),
2425 )
2426 .alias("rodc")])
2427 }
2428
2429 pub fn reverse_ema(self, name: &str, alpha: f64) -> LazyFrame {
2430 let name_str = name.to_string();
2431 self.0.clone().with_columns([col(&name_str)
2432 .map(
2433 move |s| {
2434 let ca = s.f64()?;
2435 let mut indicator = quantwave_core::ReverseEMA::new(alpha);
2436 let mut values = Vec::with_capacity(s.len());
2437
2438 for i in 0..s.len() {
2439 let val = ca.get(i).unwrap_or(f64::NAN);
2440 values.push(indicator.next(val));
2441 }
2442
2443 Ok(Some(Column::from(Series::new(
2444 "reverse_ema".into(),
2445 values,
2446 ))))
2447 },
2448 GetOutput::from_type(DataType::Float64),
2449 )
2450 .alias("reverse_ema")])
2451 }
2452
2453 pub fn harrington_adx(
2454 self,
2455 high: &str,
2456 low: &str,
2457 close: &str,
2458 adx_length: usize,
2459 adx_smooth_length: usize,
2460 ) -> LazyFrame {
2461 let h_str = high.to_string();
2462 let l_str = low.to_string();
2463 let c_str = close.to_string();
2464
2465 self.0
2466 .clone()
2467 .with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
2468 .map(
2469 move |s| {
2470 let ca = s.struct_()?;
2471 let f_h = ca.field_by_name(&h_str)?;
2472 let high = f_h.f64()?;
2473 let f_l = ca.field_by_name(&l_str)?;
2474 let low = f_l.f64()?;
2475 let f_c = ca.field_by_name(&c_str)?;
2476 let close = f_c.f64()?;
2477
2478 let mut indicator = quantwave_core::HarringtonADXOscillator::new(
2479 adx_length,
2480 adx_smooth_length,
2481 );
2482 let mut values = Vec::with_capacity(s.len());
2483
2484 for i in 0..s.len() {
2485 let h = high.get(i).unwrap_or(f64::NAN);
2486 let l = low.get(i).unwrap_or(f64::NAN);
2487 let c = close.get(i).unwrap_or(f64::NAN);
2488 values.push(indicator.next((h, l, c)));
2489 }
2490
2491 Ok(Some(Column::from(Series::new(
2492 "harrington_adx".into(),
2493 values,
2494 ))))
2495 },
2496 GetOutput::from_type(DataType::Float64),
2497 )
2498 .alias("harrington_adx")])
2499 }
2500
2501 pub fn keltner_channels(
2502 self,
2503 high: &str,
2504 low: &str,
2505 close: &str,
2506 ema_period: usize,
2507 atr_period: usize,
2508 multiplier: f64,
2509 ) -> LazyFrame {
2510 let high = high.to_string();
2511 let low = low.to_string();
2512 let close = close.to_string();
2513
2514 self.0
2515 .clone()
2516 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2517 .map(
2518 move |s| {
2519 let ca = s.struct_()?;
2520 let s_high = ca.field_by_name(&high)?;
2521 let s_low = ca.field_by_name(&low)?;
2522 let s_close = ca.field_by_name(&close)?;
2523
2524 let high = s_high.f64()?;
2525 let low = s_low.f64()?;
2526 let close = s_close.f64()?;
2527
2528 let mut kc = quantwave_core::KeltnerChannels::new(
2529 ema_period, atr_period, multiplier,
2530 );
2531 let mut uppers = Vec::with_capacity(s.len());
2532 let mut middles = Vec::with_capacity(s.len());
2533 let mut lowers = Vec::with_capacity(s.len());
2534
2535 for i in 0..s.len() {
2536 let h = high.get(i).unwrap_or(0.0);
2537 let l = low.get(i).unwrap_or(0.0);
2538 let c = close.get(i).unwrap_or(0.0);
2539 let (upper, middle, lower) = kc.next((h, l, c));
2540 uppers.push(upper);
2541 middles.push(middle);
2542 lowers.push(lower);
2543 }
2544
2545 let upper_series = Series::new("upper".into(), uppers);
2546 let middle_series = Series::new("middle".into(), middles);
2547 let lower_series = Series::new("lower".into(), lowers);
2548
2549 let out = StructChunked::from_series(
2550 "keltner_output".into(),
2551 s.len(),
2552 [upper_series, middle_series, lower_series].iter(),
2553 )?;
2554 Ok(Some(Column::from(out.into_series())))
2555 },
2556 GetOutput::from_type(DataType::Struct(vec![
2557 Field::new("upper".into(), DataType::Float64),
2558 Field::new("middle".into(), DataType::Float64),
2559 Field::new("lower".into(), DataType::Float64),
2560 ])),
2561 )
2562 .alias("keltner_data")])
2563 }
2564
2565 pub fn alma(self, name: &str, period: usize, offset: f64, sigma: f64) -> LazyFrame {
2566 let name = name.to_string();
2567 self.0.clone().with_columns([col(&name)
2568 .map(
2569 move |s| {
2570 let ca = s.f64()?;
2571 let mut alma = quantwave_core::ALMA::new(period, offset, sigma);
2572 let mut values = Vec::with_capacity(s.len());
2573
2574 for i in 0..s.len() {
2575 let val = ca.get(i).unwrap_or(0.0);
2576 values.push(alma.next(val));
2577 }
2578
2579 Ok(Some(Column::from(Series::new("alma".into(), values))))
2580 },
2581 GetOutput::from_type(DataType::Float64),
2582 )
2583 .alias("alma")])
2584 }
2585
2586 pub fn donchian_channels(self, high: &str, low: &str, period: usize) -> LazyFrame {
2587 let high = high.to_string();
2588 let low = low.to_string();
2589
2590 self.0
2591 .clone()
2592 .with_columns([as_struct(vec![col(&high), col(&low)])
2593 .map(
2594 move |s| {
2595 let ca = s.struct_()?;
2596 let s_high = ca.field_by_name(&high)?;
2597 let s_low = ca.field_by_name(&low)?;
2598
2599 let high = s_high.f64()?;
2600 let low = s_low.f64()?;
2601
2602 let mut dc = quantwave_core::DonchianChannels::new(period);
2603 let mut uppers = Vec::with_capacity(s.len());
2604 let mut middles = Vec::with_capacity(s.len());
2605 let mut lowers = Vec::with_capacity(s.len());
2606
2607 for i in 0..s.len() {
2608 let h = high.get(i).unwrap_or(0.0);
2609 let l = low.get(i).unwrap_or(0.0);
2610 let (upper, middle, lower) = dc.next((h, l));
2611 uppers.push(upper);
2612 middles.push(middle);
2613 lowers.push(lower);
2614 }
2615
2616 let upper_series = Series::new("upper".into(), uppers);
2617 let middle_series = Series::new("middle".into(), middles);
2618 let lower_series = Series::new("lower".into(), lowers);
2619
2620 let out = StructChunked::from_series(
2621 "donchian_output".into(),
2622 s.len(),
2623 [upper_series, middle_series, lower_series].iter(),
2624 )?;
2625 Ok(Some(Column::from(out.into_series())))
2626 },
2627 GetOutput::from_type(DataType::Struct(vec![
2628 Field::new("upper".into(), DataType::Float64),
2629 Field::new("middle".into(), DataType::Float64),
2630 Field::new("lower".into(), DataType::Float64),
2631 ])),
2632 )
2633 .alias("donchian_data")])
2634 }
2635
2636 pub fn ttm_squeeze(
2637 self,
2638 high: &str,
2639 low: &str,
2640 close: &str,
2641 period: usize,
2642 multiplier_bb: f64,
2643 multiplier_kc: f64,
2644 ) -> LazyFrame {
2645 let high = high.to_string();
2646 let low = low.to_string();
2647 let close = close.to_string();
2648
2649 self.0
2650 .clone()
2651 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2652 .map(
2653 move |s| {
2654 let ca = s.struct_()?;
2655 let s_high = ca.field_by_name(&high)?;
2656 let s_low = ca.field_by_name(&low)?;
2657 let s_close = ca.field_by_name(&close)?;
2658
2659 let high = s_high.f64()?;
2660 let low = s_low.f64()?;
2661 let close = s_close.f64()?;
2662
2663 let mut ttm =
2664 quantwave_core::TTMSqueeze::new(period, multiplier_bb, multiplier_kc);
2665 let mut histograms = Vec::with_capacity(s.len());
2666 let mut squeezed = Vec::with_capacity(s.len());
2667
2668 for i in 0..s.len() {
2669 let h = high.get(i).unwrap_or(0.0);
2670 let l = low.get(i).unwrap_or(0.0);
2671 let c = close.get(i).unwrap_or(0.0);
2672 let (hist, is_sq) = ttm.next((h, l, c));
2673 histograms.push(hist);
2674 squeezed.push(is_sq);
2675 }
2676
2677 let hist_series = Series::new("histogram".into(), histograms);
2678 let squeezed_series = Series::new("is_squeezed".into(), squeezed);
2679
2680 let out = StructChunked::from_series(
2681 "ttm_squeeze_output".into(),
2682 s.len(),
2683 [hist_series, squeezed_series].iter(),
2684 )?;
2685 Ok(Some(Column::from(out.into_series())))
2686 },
2687 GetOutput::from_type(DataType::Struct(vec![
2688 Field::new("histogram".into(), DataType::Float64),
2689 Field::new("is_squeezed".into(), DataType::Boolean),
2690 ])),
2691 )
2692 .alias("ttm_squeeze_data")])
2693 }
2694
2695 pub fn vortex_indicator(self, high: &str, low: &str, close: &str, period: usize) -> LazyFrame {
2696 let high = high.to_string();
2697 let low = low.to_string();
2698 let close = close.to_string();
2699
2700 self.0
2701 .clone()
2702 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2703 .map(
2704 move |s| {
2705 let ca = s.struct_()?;
2706 let s_high = ca.field_by_name(&high)?;
2707 let s_low = ca.field_by_name(&low)?;
2708 let s_close = ca.field_by_name(&close)?;
2709
2710 let high = s_high.f64()?;
2711 let low = s_low.f64()?;
2712 let close = s_close.f64()?;
2713
2714 let mut vi = quantwave_core::VortexIndicator::new(period);
2715 let mut plus_vals = Vec::with_capacity(s.len());
2716 let mut minus_vals = Vec::with_capacity(s.len());
2717
2718 for i in 0..s.len() {
2719 let h = high.get(i).unwrap_or(0.0);
2720 let l = low.get(i).unwrap_or(0.0);
2721 let c = close.get(i).unwrap_or(0.0);
2722 let (plus, minus) = vi.next((h, l, c));
2723 plus_vals.push(plus);
2724 minus_vals.push(minus);
2725 }
2726
2727 let plus_series = Series::new("vi_plus".into(), plus_vals);
2728 let minus_series = Series::new("vi_minus".into(), minus_vals);
2729
2730 let out = StructChunked::from_series(
2731 "vortex_output".into(),
2732 s.len(),
2733 [plus_series, minus_series].iter(),
2734 )?;
2735 Ok(Some(Column::from(out.into_series())))
2736 },
2737 GetOutput::from_type(DataType::Struct(vec![
2738 Field::new("vi_plus".into(), DataType::Float64),
2739 Field::new("vi_minus".into(), DataType::Float64),
2740 ])),
2741 )
2742 .alias("vortex_data")])
2743 }
2744
2745 pub fn heikin_ashi(self, open: &str, high: &str, low: &str, close: &str) -> LazyFrame {
2746 let open = open.to_string();
2747 let high = high.to_string();
2748 let low = low.to_string();
2749 let close = close.to_string();
2750
2751 self.0.clone().with_columns([as_struct(vec![
2752 col(&open),
2753 col(&high),
2754 col(&low),
2755 col(&close),
2756 ])
2757 .map(
2758 move |s| {
2759 let ca = s.struct_()?;
2760 let s_open = ca.field_by_name(&open)?;
2761 let s_high = ca.field_by_name(&high)?;
2762 let s_low = ca.field_by_name(&low)?;
2763 let s_close = ca.field_by_name(&close)?;
2764
2765 let open = s_open.f64()?;
2766 let high = s_high.f64()?;
2767 let low = s_low.f64()?;
2768 let close = s_close.f64()?;
2769
2770 let mut ha = quantwave_core::HeikinAshi::new();
2771 let mut ha_opens = Vec::with_capacity(s.len());
2772 let mut ha_highs = Vec::with_capacity(s.len());
2773 let mut ha_lows = Vec::with_capacity(s.len());
2774 let mut ha_closes = Vec::with_capacity(s.len());
2775
2776 for i in 0..s.len() {
2777 let o = open.get(i).unwrap_or(0.0);
2778 let h = high.get(i).unwrap_or(0.0);
2779 let l = low.get(i).unwrap_or(0.0);
2780 let c = close.get(i).unwrap_or(0.0);
2781 let (ha_o, ha_h, ha_l, ha_c) = ha.next((o, h, l, c));
2782 ha_opens.push(ha_o);
2783 ha_highs.push(ha_h);
2784 ha_lows.push(ha_l);
2785 ha_closes.push(ha_c);
2786 }
2787
2788 let o_series = Series::new("ha_open".into(), ha_opens);
2789 let h_series = Series::new("ha_high".into(), ha_highs);
2790 let l_series = Series::new("ha_low".into(), ha_lows);
2791 let c_series = Series::new("ha_close".into(), ha_closes);
2792
2793 let out = StructChunked::from_series(
2794 "heikin_ashi_output".into(),
2795 s.len(),
2796 [o_series, h_series, l_series, c_series].iter(),
2797 )?;
2798 Ok(Some(Column::from(out.into_series())))
2799 },
2800 GetOutput::from_type(DataType::Struct(vec![
2801 Field::new("ha_open".into(), DataType::Float64),
2802 Field::new("ha_high".into(), DataType::Float64),
2803 Field::new("ha_low".into(), DataType::Float64),
2804 Field::new("ha_close".into(), DataType::Float64),
2805 ])),
2806 )
2807 .alias("heikin_ashi_data")])
2808 }
2809
2810 pub fn wavetrend(
2811 self,
2812 high: &str,
2813 low: &str,
2814 close: &str,
2815 n1: usize,
2816 n2: usize,
2817 n3: usize,
2818 ) -> LazyFrame {
2819 let high = high.to_string();
2820 let low = low.to_string();
2821 let close = close.to_string();
2822
2823 self.0
2824 .clone()
2825 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2826 .map(
2827 move |s| {
2828 let ca = s.struct_()?;
2829 let s_high = ca.field_by_name(&high)?;
2830 let s_low = ca.field_by_name(&low)?;
2831 let s_close = ca.field_by_name(&close)?;
2832
2833 let high = s_high.f64()?;
2834 let low = s_low.f64()?;
2835 let close = s_close.f64()?;
2836
2837 let mut wt = quantwave_core::WaveTrend::new(n1, n2, n3);
2838 let mut wt1_vals = Vec::with_capacity(s.len());
2839 let mut wt2_vals = Vec::with_capacity(s.len());
2840
2841 for i in 0..s.len() {
2842 let h = high.get(i).unwrap_or(0.0);
2843 let l = low.get(i).unwrap_or(0.0);
2844 let c = close.get(i).unwrap_or(0.0);
2845 let (wt1, wt2) = wt.next((h, l, c));
2846 wt1_vals.push(wt1);
2847 wt2_vals.push(wt2);
2848 }
2849
2850 let wt1_series = Series::new("wt1".into(), wt1_vals);
2851 let wt2_series = Series::new("wt2".into(), wt2_vals);
2852
2853 let out = StructChunked::from_series(
2854 "wavetrend_output".into(),
2855 s.len(),
2856 [wt1_series, wt2_series].iter(),
2857 )?;
2858 Ok(Some(Column::from(out.into_series())))
2859 },
2860 GetOutput::from_type(DataType::Struct(vec![
2861 Field::new("wt1".into(), DataType::Float64),
2862 Field::new("wt2".into(), DataType::Float64),
2863 ])),
2864 )
2865 .alias("wavetrend_data")])
2866 }
2867
2868 pub fn tema(self, name: &str, period: usize) -> LazyFrame {
2869 let name = name.to_string();
2870 self.0.clone().with_columns([col(&name)
2871 .map(
2872 move |s| {
2873 let ca = s.f64()?;
2874 let mut tema = quantwave_core::TEMA::new(period);
2875 let mut values = Vec::with_capacity(s.len());
2876
2877 for i in 0..s.len() {
2878 let val = ca.get(i).unwrap_or(0.0);
2879 values.push(tema.next(val));
2880 }
2881
2882 Ok(Some(Column::from(Series::new("tema".into(), values))))
2883 },
2884 GetOutput::from_type(DataType::Float64),
2885 )
2886 .alias("tema")])
2887 }
2888
2889 pub fn zlema(self, name: &str, period: usize) -> LazyFrame {
2890 let name = name.to_string();
2891 self.0.clone().with_columns([col(&name)
2892 .map(
2893 move |s| {
2894 let ca = s.f64()?;
2895 let mut zlema = quantwave_core::ZLEMA::new(period);
2896 let mut values = Vec::with_capacity(s.len());
2897
2898 for i in 0..s.len() {
2899 let val = ca.get(i).unwrap_or(0.0);
2900 values.push(zlema.next(val));
2901 }
2902
2903 Ok(Some(Column::from(Series::new("zlema".into(), values))))
2904 },
2905 GetOutput::from_type(DataType::Float64),
2906 )
2907 .alias("zlema")])
2908 }
2909
2910 pub fn atr_trailing_stop(
2911 self,
2912 high: &str,
2913 low: &str,
2914 close: &str,
2915 period: usize,
2916 multiplier: f64,
2917 ) -> LazyFrame {
2918 let high = high.to_string();
2919 let low = low.to_string();
2920 let close = close.to_string();
2921
2922 self.0
2923 .clone()
2924 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2925 .map(
2926 move |s| {
2927 let ca = s.struct_()?;
2928 let s_high = ca.field_by_name(&high)?;
2929 let s_low = ca.field_by_name(&low)?;
2930 let s_close = ca.field_by_name(&close)?;
2931
2932 let high = s_high.f64()?;
2933 let low = s_low.f64()?;
2934 let close = s_close.f64()?;
2935
2936 let mut atr_ts = quantwave_core::ATRTrailingStop::new(period, multiplier);
2937 let mut stops = Vec::with_capacity(s.len());
2938 let mut directions = Vec::with_capacity(s.len());
2939
2940 for i in 0..s.len() {
2941 let h = high.get(i).unwrap_or(0.0);
2942 let l = low.get(i).unwrap_or(0.0);
2943 let c = close.get(i).unwrap_or(0.0);
2944 let (stop, dir) = atr_ts.next((h, l, c));
2945 stops.push(stop);
2946 directions.push(dir as f64);
2947 }
2948
2949 let stop_series = Series::new("stop".into(), stops);
2950 let dir_series = Series::new("direction".into(), directions);
2951
2952 let out = StructChunked::from_series(
2953 "atr_ts_output".into(),
2954 s.len(),
2955 [stop_series, dir_series].iter(),
2956 )?;
2957 Ok(Some(Column::from(out.into_series())))
2958 },
2959 GetOutput::from_type(DataType::Struct(vec![
2960 Field::new("stop".into(), DataType::Float64),
2961 Field::new("direction".into(), DataType::Float64),
2962 ])),
2963 )
2964 .alias("atr_ts_data")])
2965 }
2966
2967 pub fn pivot_points(self, high: &str, low: &str, close: &str) -> LazyFrame {
2968 let high = high.to_string();
2969 let low = low.to_string();
2970 let close = close.to_string();
2971
2972 self.0
2973 .clone()
2974 .with_columns([as_struct(vec![col(&high), col(&low), col(&close)])
2975 .map(
2976 move |s| {
2977 let ca = s.struct_()?;
2978 let s_high = ca.field_by_name(&high)?;
2979 let s_low = ca.field_by_name(&low)?;
2980 let s_close = ca.field_by_name(&close)?;
2981
2982 let high = s_high.f64()?;
2983 let low = s_low.f64()?;
2984 let close = s_close.f64()?;
2985
2986 let mut pivot = quantwave_core::PivotPoints::new();
2987 let mut p_vals = Vec::with_capacity(s.len());
2988 let mut r1_vals = Vec::with_capacity(s.len());
2989 let mut s1_vals = Vec::with_capacity(s.len());
2990 let mut r2_vals = Vec::with_capacity(s.len());
2991 let mut s2_vals = Vec::with_capacity(s.len());
2992
2993 for i in 0..s.len() {
2994 let h = high.get(i).unwrap_or(0.0);
2995 let l = low.get(i).unwrap_or(0.0);
2996 let c = close.get(i).unwrap_or(0.0);
2997 let (p, r1, s1, r2, s2) = pivot.next((h, l, c));
2998 p_vals.push(p);
2999 r1_vals.push(r1);
3000 s1_vals.push(s1);
3001 r2_vals.push(r2);
3002 s2_vals.push(s2);
3003 }
3004
3005 let p_series = Series::new("p".into(), p_vals);
3006 let r1_series = Series::new("r1".into(), r1_vals);
3007 let s1_series = Series::new("s1".into(), s1_vals);
3008 let r2_series = Series::new("r2".into(), r2_vals);
3009 let s2_series = Series::new("s2".into(), s2_vals);
3010
3011 let out = StructChunked::from_series(
3012 "pivot_output".into(),
3013 s.len(),
3014 [p_series, r1_series, s1_series, r2_series, s2_series].iter(),
3015 )?;
3016 Ok(Some(Column::from(out.into_series())))
3017 },
3018 GetOutput::from_type(DataType::Struct(vec![
3019 Field::new("p".into(), DataType::Float64),
3020 Field::new("r1".into(), DataType::Float64),
3021 Field::new("s1".into(), DataType::Float64),
3022 Field::new("r2".into(), DataType::Float64),
3023 Field::new("s2".into(), DataType::Float64),
3024 ])),
3025 )
3026 .alias("pivot_points_data")])
3027 }
3028
3029 pub fn bill_williams_fractals(self, high: &str, low: &str) -> LazyFrame {
3030 let high = high.to_string();
3031 let low = low.to_string();
3032
3033 self.0
3034 .clone()
3035 .with_columns([as_struct(vec![col(&high), col(&low)])
3036 .map(
3037 move |s| {
3038 let ca = s.struct_()?;
3039 let s_high = ca.field_by_name(&high)?;
3040 let s_low = ca.field_by_name(&low)?;
3041
3042 let high = s_high.f64()?;
3043 let low = s_low.f64()?;
3044
3045 let mut fractals = quantwave_core::BillWilliamsFractals::new();
3046 let mut bearish_vals = Vec::with_capacity(s.len());
3047 let mut bullish_vals = Vec::with_capacity(s.len());
3048
3049 for i in 0..s.len() {
3050 let h = high.get(i).unwrap_or(0.0);
3051 let l = low.get(i).unwrap_or(0.0);
3052 let (bear, bull) = fractals.next((h, l));
3053 bearish_vals.push(bear);
3054 bullish_vals.push(bull);
3055 }
3056
3057 let bearish_series = Series::new("bearish".into(), bearish_vals);
3058 let bullish_series = Series::new("bullish".into(), bullish_vals);
3059
3060 let out = StructChunked::from_series(
3061 "fractals_output".into(),
3062 s.len(),
3063 [bearish_series, bullish_series].iter(),
3064 )?;
3065 Ok(Some(Column::from(out.into_series())))
3066 },
3067 GetOutput::from_type(DataType::Struct(vec![
3068 Field::new("bearish".into(), DataType::Boolean),
3069 Field::new("bullish".into(), DataType::Boolean),
3070 ])),
3071 )
3072 .alias("fractals_data")])
3073 }
3074
3075 pub fn market_structure(self, high: &str, low: &str, swing_strength: usize) -> LazyFrame {
3095 let high_str = high.to_string();
3096 let low_str = low.to_string();
3097 let strength = swing_strength;
3098
3099 self.0
3100 .clone()
3101 .with_columns([as_struct(vec![col(&high_str), col(&low_str)])
3102 .map(
3103 move |s| {
3104 let ca = s.struct_()?;
3105 let s_h = ca.field_by_name(&high_str)?;
3106 let s_l = ca.field_by_name(&low_str)?;
3107 let highs = s_h.f64()?;
3108 let lows = s_l.f64()?;
3109
3110 let mut ms = quantwave_core::MarketStructure::new(strength);
3111 let n = s.len();
3112
3113 let mut bias_vals: Vec<u32> = Vec::with_capacity(n);
3114 let mut lh_p: Vec<f64> = Vec::with_capacity(n);
3115 let mut lh_b: Vec<u64> = Vec::with_capacity(n);
3116 let mut ll_p: Vec<f64> = Vec::with_capacity(n);
3117 let mut ll_b: Vec<u64> = Vec::with_capacity(n);
3118 let mut has_f: Vec<bool> = Vec::with_capacity(n);
3119 let mut f_bear: Vec<bool> = Vec::with_capacity(n);
3120 let mut f_p: Vec<f64> = Vec::with_capacity(n);
3121 let mut f_ba: Vec<u64> = Vec::with_capacity(n);
3122 let mut f_str: Vec<u32> = Vec::with_capacity(n);
3123 let mut depths: Vec<u32> = Vec::with_capacity(n);
3124 let mut bars: Vec<u64> = Vec::with_capacity(n);
3125
3126 for i in 0..n {
3127 let h = highs.get(i).unwrap_or(f64::NAN);
3128 let l = lows.get(i).unwrap_or(f64::NAN);
3129 let hh = if h.is_nan() || l.is_nan() {
3131 f64::NAN
3132 } else {
3133 h.max(l)
3134 };
3135 let ll = if h.is_nan() || l.is_nan() {
3136 f64::NAN
3137 } else {
3138 l.min(h)
3139 };
3140 let state = ms.next((hh, ll));
3141
3142 let b = match state.bias {
3143 quantwave_core::Bias::Neutral => 0u32,
3144 quantwave_core::Bias::Bullish => 1,
3145 quantwave_core::Bias::Bearish => 2,
3146 };
3147 bias_vals.push(b);
3148
3149 match &state.last_swing_high {
3150 Some(sh) => {
3151 lh_p.push(sh.price);
3152 lh_b.push(sh.bar as u64);
3153 }
3154 None => {
3155 lh_p.push(f64::NAN);
3156 lh_b.push(0);
3157 }
3158 }
3159 match &state.last_swing_low {
3160 Some(sl) => {
3161 ll_p.push(sl.price);
3162 ll_b.push(sl.bar as u64);
3163 }
3164 None => {
3165 ll_p.push(f64::NAN);
3166 ll_b.push(0);
3167 }
3168 }
3169
3170 if let Some(f) = &state.current_flip {
3171 has_f.push(true);
3172 f_bear.push(f.is_bearish);
3173 f_p.push(f.price);
3174 f_ba.push(f.bar as u64);
3175 f_str.push(f.structure_strength);
3176 } else {
3177 has_f.push(false);
3178 f_bear.push(false);
3179 f_p.push(f64::NAN);
3180 f_ba.push(0);
3181 f_str.push(0);
3182 }
3183
3184 depths.push(state.swing_depth_used as u32);
3185 bars.push(state.bar_index as u64);
3186 }
3187
3188 let s_bias = Series::new("bias".into(), bias_vals);
3189 let s_lhp = Series::new("last_high_price".into(), lh_p);
3190 let s_lhb = Series::new("last_high_bar".into(), lh_b);
3191 let s_llp = Series::new("last_low_price".into(), ll_p);
3192 let s_llb = Series::new("last_low_bar".into(), ll_b);
3193 let s_hasf = Series::new("has_flip".into(), has_f);
3194 let s_fb = Series::new("flip_bearish".into(), f_bear);
3195 let s_fp = Series::new("flip_price".into(), f_p);
3196 let s_fba = Series::new("flip_bar".into(), f_ba);
3197 let s_fstr = Series::new("flip_strength".into(), f_str);
3198 let s_dep = Series::new("swing_depth".into(), depths);
3199 let s_bar = Series::new("bar_index".into(), bars);
3200
3201 let struct_series = StructChunked::from_series(
3202 "market_structure_result".into(),
3203 s.len(),
3204 [
3205 s_bias, s_lhp, s_lhb, s_llp, s_llb, s_hasf, s_fb, s_fp, s_fba,
3206 s_fstr, s_dep, s_bar,
3207 ]
3208 .iter(),
3209 )?;
3210 Ok(Some(Column::from(struct_series.into_series())))
3211 },
3212 GetOutput::from_type(DataType::Struct(vec![
3213 Field::new("bias".into(), DataType::UInt32),
3214 Field::new("last_high_price".into(), DataType::Float64),
3215 Field::new("last_high_bar".into(), DataType::UInt64),
3216 Field::new("last_low_price".into(), DataType::Float64),
3217 Field::new("last_low_bar".into(), DataType::UInt64),
3218 Field::new("has_flip".into(), DataType::Boolean),
3219 Field::new("flip_bearish".into(), DataType::Boolean),
3220 Field::new("flip_price".into(), DataType::Float64),
3221 Field::new("flip_bar".into(), DataType::UInt64),
3222 Field::new("flip_strength".into(), DataType::UInt32),
3223 Field::new("swing_depth".into(), DataType::UInt32),
3224 Field::new("bar_index".into(), DataType::UInt64),
3225 ])),
3226 )
3227 .alias("market_structure")])
3228 }
3229
3230 pub fn geometric_patterns(self, high: &str, low: &str, swing_strength: usize) -> LazyFrame {
3239 let high_str = high.to_string();
3240 let low_str = low.to_string();
3241 let strength = swing_strength;
3242
3243 self.0
3244 .clone()
3245 .with_columns([as_struct(vec![col(&high_str), col(&low_str)])
3246 .map(
3247 move |s| {
3248 let ca = s.struct_()?;
3249 let s_h = ca.field_by_name(&high_str)?;
3250 let s_l = ca.field_by_name(&low_str)?;
3251 let highs = s_h.f64()?;
3252 let lows = s_l.f64()?;
3253
3254 let mut scanner = quantwave_core::GeometricPatternScanner::new(strength);
3255 let n = s.len();
3256
3257 let mut flag_ids: Vec<u32> = Vec::with_capacity(n);
3258 let mut flag_is_bull: Vec<bool> = Vec::with_capacity(n);
3259 let mut flag_pole_len: Vec<f64> = Vec::with_capacity(n);
3260 let mut flag_pole_atr: Vec<f64> = Vec::with_capacity(n);
3261 let mut flag_breakout: Vec<bool> = Vec::with_capacity(n);
3262 let mut flag_bp: Vec<f64> = Vec::with_capacity(n);
3263
3264 let mut hs_ids: Vec<u32> = Vec::with_capacity(n);
3265 let mut hs_bear: Vec<bool> = Vec::with_capacity(n);
3266 let mut hs_height: Vec<f64> = Vec::with_capacity(n);
3267 let mut hs_height_atr: Vec<f64> = Vec::with_capacity(n);
3268 let mut hs_score: Vec<f64> = Vec::with_capacity(n);
3269 let mut hs_breakout: Vec<bool> = Vec::with_capacity(n);
3270
3271 for i in 0..n {
3272 let h = highs.get(i).unwrap_or(f64::NAN);
3273 let l = lows.get(i).unwrap_or(f64::NAN);
3274 let hh = if h.is_nan() || l.is_nan() {
3275 f64::NAN
3276 } else {
3277 h.max(l)
3278 };
3279 let ll = if h.is_nan() || l.is_nan() {
3280 f64::NAN
3281 } else {
3282 l.min(h)
3283 };
3284 let (_state, flag, hs) = scanner.next((hh, ll));
3285
3286 if let Some(f) = flag {
3287 flag_ids.push(f.id);
3288 flag_is_bull.push(f.is_bull);
3289 flag_pole_len.push(f.pole_length);
3290 flag_pole_atr.push(f.pole_length_atr);
3291 flag_breakout.push(f.breakout_confirmed);
3292 flag_bp.push(f.breakout_price);
3293 } else {
3294 flag_ids.push(0);
3295 flag_is_bull.push(false);
3296 flag_pole_len.push(f64::NAN);
3297 flag_pole_atr.push(f64::NAN);
3298 flag_breakout.push(false);
3299 flag_bp.push(f64::NAN);
3300 }
3301
3302 if let Some(hp) = hs {
3303 hs_ids.push(hp.id);
3304 hs_bear.push(hp.is_bearish);
3305 hs_height.push(hp.height);
3306 hs_height_atr.push(hp.height_atr);
3307 hs_score.push(hp.score);
3308 hs_breakout.push(hp.breakout_confirmed);
3309 } else {
3310 hs_ids.push(0);
3311 hs_bear.push(false);
3312 hs_height.push(f64::NAN);
3313 hs_height_atr.push(f64::NAN);
3314 hs_score.push(f64::NAN);
3315 hs_breakout.push(false);
3316 }
3317 }
3318
3319 let s_fid = Series::new("id".into(), flag_ids);
3320 let s_fbull = Series::new("is_bull".into(), flag_is_bull);
3321 let s_fplen = Series::new("pole_length".into(), flag_pole_len);
3322 let s_fpatr = Series::new("pole_length_atr".into(), flag_pole_atr);
3323 let s_fbo = Series::new("breakout_confirmed".into(), flag_breakout);
3324 let s_fbp = Series::new("breakout_price".into(), flag_bp);
3325
3326 let flag_struct = StructChunked::from_series(
3327 "flag".into(),
3328 n,
3329 [s_fid, s_fbull, s_fplen, s_fpatr, s_fbo, s_fbp].iter(),
3330 )?;
3331
3332 let s_hid = Series::new("id".into(), hs_ids);
3333 let s_hbear = Series::new("is_bearish".into(), hs_bear);
3334 let s_hh = Series::new("height".into(), hs_height);
3335 let s_hhatr = Series::new("height_atr".into(), hs_height_atr);
3336 let s_hsc = Series::new("score".into(), hs_score);
3337 let s_hbo = Series::new("breakout_confirmed".into(), hs_breakout);
3338
3339 let hs_struct = StructChunked::from_series(
3340 "hs".into(),
3341 n,
3342 [s_hid, s_hbear, s_hh, s_hhatr, s_hsc, s_hbo].iter(),
3343 )?;
3344
3345 let combined = StructChunked::from_series(
3346 "geo_patterns".into(),
3347 n,
3348 [flag_struct.into_series(), hs_struct.into_series()].iter(),
3349 )?;
3350 Ok(Some(Column::from(combined.into_series())))
3351 },
3352 GetOutput::from_type(DataType::Struct(vec![
3353 Field::new(
3354 "flag".into(),
3355 DataType::Struct(vec![
3356 Field::new("id".into(), DataType::UInt32),
3357 Field::new("is_bull".into(), DataType::Boolean),
3358 Field::new("pole_length".into(), DataType::Float64),
3359 Field::new("pole_length_atr".into(), DataType::Float64),
3360 Field::new("breakout_confirmed".into(), DataType::Boolean),
3361 Field::new("breakout_price".into(), DataType::Float64),
3362 ]),
3363 ),
3364 Field::new(
3365 "hs".into(),
3366 DataType::Struct(vec![
3367 Field::new("id".into(), DataType::UInt32),
3368 Field::new("is_bearish".into(), DataType::Boolean),
3369 Field::new("height".into(), DataType::Float64),
3370 Field::new("height_atr".into(), DataType::Float64),
3371 Field::new("score".into(), DataType::Float64),
3372 Field::new("breakout_confirmed".into(), DataType::Boolean),
3373 ]),
3374 ),
3375 ])),
3376 )
3377 .alias("geometric_patterns")])
3378 }
3379
3380 pub fn sr_monitor(
3384 self,
3385 high: &str,
3386 low: &str,
3387 close: &str,
3388 swing_strength: usize,
3389 touch_tolerance: f64,
3390 approach_zone: f64,
3391 ) -> LazyFrame {
3392 let high_str = high.to_string();
3393 let low_str = low.to_string();
3394 let close_str = close.to_string();
3395 let strength = swing_strength;
3396
3397 self.0.clone().with_columns([as_struct(vec![
3398 col(&high_str),
3399 col(&low_str),
3400 col(&close_str),
3401 ])
3402 .map(
3403 move |s| {
3404 let ca = s.struct_()?;
3405 let s_h = ca.field_by_name(&high_str)?;
3406 let s_l = ca.field_by_name(&low_str)?;
3407 let s_c = ca.field_by_name(&close_str)?;
3408 let highs = s_h.f64()?;
3409 let lows = s_l.f64()?;
3410 let closes = s_c.f64()?;
3411
3412 let mut mon = quantwave_core::SRInteractionMonitor::new(
3413 strength,
3414 touch_tolerance,
3415 approach_zone,
3416 );
3417 let n = s.len();
3418
3419 let mut bias_vals: Vec<u32> = Vec::with_capacity(n);
3420 let mut active_levels: Vec<u32> = Vec::with_capacity(n);
3421 let mut interaction_counts: Vec<u32> = Vec::with_capacity(n);
3422 let mut has_interaction: Vec<bool> = Vec::with_capacity(n);
3423 let mut interaction_types: Vec<u32> = Vec::with_capacity(n);
3424 let mut level_prices: Vec<f64> = Vec::with_capacity(n);
3425 let mut is_support_vals: Vec<bool> = Vec::with_capacity(n);
3426 let mut strengths: Vec<f64> = Vec::with_capacity(n);
3427 let mut distances: Vec<f64> = Vec::with_capacity(n);
3428 let mut atr_vals: Vec<f64> = Vec::with_capacity(n);
3429
3430 for i in 0..n {
3431 let h = highs.get(i).unwrap_or(f64::NAN);
3432 let l = lows.get(i).unwrap_or(f64::NAN);
3433 let c = closes.get(i).unwrap_or(f64::NAN);
3434 let hh = if h.is_nan() || l.is_nan() {
3435 f64::NAN
3436 } else {
3437 h.max(l)
3438 };
3439 let ll = if h.is_nan() || l.is_nan() {
3440 f64::NAN
3441 } else {
3442 l.min(h)
3443 };
3444 let cc = if c.is_nan() {
3445 (hh + ll) / 2.0
3446 } else {
3447 c.clamp(ll, hh)
3448 };
3449
3450 let out = mon.next((hh, ll, cc));
3451
3452 let b = match out.structure.bias {
3453 quantwave_core::Bias::Neutral => 0u32,
3454 quantwave_core::Bias::Bullish => 1,
3455 quantwave_core::Bias::Bearish => 2,
3456 };
3457 bias_vals.push(b);
3458 active_levels.push(mon.active_level_count() as u32);
3459 interaction_counts.push(out.interactions.len() as u32);
3460
3461 if let Some(first) = out.interactions.first() {
3462 has_interaction.push(true);
3463 interaction_types.push(first.interaction as u32);
3464 level_prices.push(first.level_price);
3465 is_support_vals.push(first.is_support);
3466 strengths.push(first.strength);
3467 distances.push(first.distance_at_event);
3468 } else {
3469 has_interaction.push(false);
3470 interaction_types.push(0);
3471 level_prices.push(f64::NAN);
3472 is_support_vals.push(false);
3473 strengths.push(f64::NAN);
3474 distances.push(f64::NAN);
3475 }
3476 atr_vals.push(mon.current_atr());
3477 }
3478
3479 let struct_series = StructChunked::from_series(
3480 "sr_monitor_result".into(),
3481 n,
3482 [
3483 Series::new("bias".into(), bias_vals),
3484 Series::new("active_levels".into(), active_levels),
3485 Series::new("interaction_count".into(), interaction_counts),
3486 Series::new("has_interaction".into(), has_interaction),
3487 Series::new("interaction_type".into(), interaction_types),
3488 Series::new("level_price".into(), level_prices),
3489 Series::new("is_support".into(), is_support_vals),
3490 Series::new("strength".into(), strengths),
3491 Series::new("distance".into(), distances),
3492 Series::new("atr".into(), atr_vals),
3493 ]
3494 .iter(),
3495 )?;
3496 Ok(Some(Column::from(struct_series.into_series())))
3497 },
3498 GetOutput::from_type(DataType::Struct(vec![
3499 Field::new("bias".into(), DataType::UInt32),
3500 Field::new("active_levels".into(), DataType::UInt32),
3501 Field::new("interaction_count".into(), DataType::UInt32),
3502 Field::new("has_interaction".into(), DataType::Boolean),
3503 Field::new("interaction_type".into(), DataType::UInt32),
3504 Field::new("level_price".into(), DataType::Float64),
3505 Field::new("is_support".into(), DataType::Boolean),
3506 Field::new("strength".into(), DataType::Float64),
3507 Field::new("distance".into(), DataType::Float64),
3508 Field::new("atr".into(), DataType::Float64),
3509 ])),
3510 )
3511 .alias("sr_monitor")])
3512 }
3513
3514 pub fn ichimoku_cloud(
3515 self,
3516 high: &str,
3517 low: &str,
3518 p1: usize,
3519 p2: usize,
3520 p3: usize,
3521 ) -> LazyFrame {
3522 let high = high.to_string();
3523 let low = low.to_string();
3524
3525 self.0
3526 .clone()
3527 .with_columns([as_struct(vec![col(&high), col(&low)])
3528 .map(
3529 move |s| {
3530 let ca = s.struct_()?;
3531 let s_high = ca.field_by_name(&high)?;
3532 let s_low = ca.field_by_name(&low)?;
3533
3534 let high = s_high.f64()?;
3535 let low = s_low.f64()?;
3536
3537 let mut ic = quantwave_core::IchimokuCloud::new(p1, p2, p3);
3538 let mut t_vals = Vec::with_capacity(s.len());
3539 let mut k_vals = Vec::with_capacity(s.len());
3540 let mut sa_vals = Vec::with_capacity(s.len());
3541 let mut sb_vals = Vec::with_capacity(s.len());
3542
3543 for i in 0..s.len() {
3544 let h = high.get(i).unwrap_or(0.0);
3545 let l = low.get(i).unwrap_or(0.0);
3546 let (t, k, sa, sb) = ic.next((h, l));
3547 t_vals.push(t);
3548 k_vals.push(k);
3549 sa_vals.push(sa);
3550 sb_vals.push(sb);
3551 }
3552
3553 let t_series = Series::new("tenkan".into(), t_vals);
3554 let k_series = Series::new("kijun".into(), k_vals);
3555 let sa_series = Series::new("senkou_a".into(), sa_vals);
3556 let sb_series = Series::new("senkou_b".into(), sb_vals);
3557
3558 let out = StructChunked::from_series(
3559 "ichimoku_output".into(),
3560 s.len(),
3561 [t_series, k_series, sa_series, sb_series].iter(),
3562 )?;
3563 Ok(Some(Column::from(out.into_series())))
3564 },
3565 GetOutput::from_type(DataType::Struct(vec![
3566 Field::new("tenkan".into(), DataType::Float64),
3567 Field::new("kijun".into(), DataType::Float64),
3568 Field::new("senkou_a".into(), DataType::Float64),
3569 Field::new("senkou_b".into(), DataType::Float64),
3570 ])),
3571 )
3572 .alias("ichimoku_data")])
3573 }
3574
3575 pub fn volatility_clusterer(
3576 self,
3577 high: &str,
3578 low: &str,
3579 close: &str,
3580 atr_period: usize,
3581 window_size: usize,
3582 k: usize,
3583 ) -> LazyFrame {
3584 let h_str = high.to_string();
3585 let l_str = low.to_string();
3586 let c_str = close.to_string();
3587
3588 self.0
3589 .clone()
3590 .with_columns([as_struct(vec![col(&h_str), col(&l_str), col(&c_str)])
3591 .map(
3592 move |s| {
3593 let ca = s.struct_()?;
3594 let f_h = ca.field_by_name(&h_str)?;
3595 let high = f_h.f64()?;
3596 let f_l = ca.field_by_name(&l_str)?;
3597 let low = f_l.f64()?;
3598 let f_c = ca.field_by_name(&c_str)?;
3599 let close = f_c.f64()?;
3600
3601 let mut clusterer =
3602 quantwave_core::regimes::volatility_clustering::VolatilityClusterer::new(
3603 atr_period,
3604 window_size,
3605 k,
3606 );
3607 let mut values = Vec::with_capacity(s.len());
3608
3609 for i in 0..s.len() {
3610 let h = high.get(i).unwrap_or(f64::NAN);
3611 let l = low.get(i).unwrap_or(f64::NAN);
3612 let c = close.get(i).unwrap_or(f64::NAN);
3613 let regime = clusterer.next((h, l, c));
3614 let val = match regime {
3615 quantwave_core::regimes::MarketRegime::Steady => 0u32,
3616 quantwave_core::regimes::MarketRegime::Bull => 1,
3617 quantwave_core::regimes::MarketRegime::Bear => 2,
3618 quantwave_core::regimes::MarketRegime::Crisis => 3,
3619 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
3620 };
3621 values.push(val);
3622 }
3623
3624 Ok(Some(Column::from(Series::new(
3625 "volatility_regime".into(),
3626 values,
3627 ))))
3628 },
3629 GetOutput::from_type(DataType::UInt32),
3630 )
3631 .alias("volatility_regime")])
3632 }
3633
3634 pub fn hmm_bull_bear(self, name: &str) -> LazyFrame {
3635 let name_str = name.to_string();
3636 self.0.clone().with_columns([col(&name_str)
3637 .map(
3638 move |s| {
3639 let ca = s.f64()?;
3640 let mut hmm = quantwave_core::regimes::hmm::HMM::bull_bear();
3641 let mut values = Vec::with_capacity(s.len());
3642
3643 for i in 0..s.len() {
3644 let val = ca.get(i).unwrap_or(f64::NAN);
3645 let regime = hmm.next(val);
3646 let out = match regime {
3647 quantwave_core::regimes::MarketRegime::Bull => 1u32,
3648 quantwave_core::regimes::MarketRegime::Bear => 2,
3649 _ => 0,
3650 };
3651 values.push(out);
3652 }
3653
3654 Ok(Some(Column::from(Series::new("hmm_regime".into(), values))))
3655 },
3656 GetOutput::from_type(DataType::UInt32),
3657 )
3658 .alias("hmm_regime")])
3659 }
3660
3661 pub fn hmm_fit(
3672 self,
3673 name: &str,
3674 n_states: usize,
3675 max_iter: usize,
3676 fit_lambdas: bool,
3677 ) -> LazyFrame {
3678 let name_str = name.to_string();
3679 self.0.clone().with_columns([col(&name_str)
3680 .map(
3681 move |s| {
3682 use quantwave_core::regimes::gaussian_hmm::{
3683 EmissionFamily, GaussianHmmFitConfig, fit_em,
3684 };
3685
3686 let ca = s.f64()?;
3687 let n = s.len();
3688 let mut obs = Vec::new();
3689 let mut index_map = Vec::new();
3690 for i in 0..n {
3691 if let Some(v) = ca.get(i)
3692 && v.is_finite()
3693 {
3694 obs.push(v);
3695 index_map.push(i);
3696 }
3697 }
3698
3699 let m = n_states.max(2);
3700 let uniform = 1.0 / m as f64;
3701 let mut states = vec![0u32; n];
3702 let mut probs_rows = vec![vec![uniform; m]; n];
3703
3704 if obs.len() > m {
3705 let config = GaussianHmmFitConfig {
3706 n_states: m,
3707 max_iter: max_iter.max(1),
3708 emission_family: if fit_lambdas {
3709 EmissionFamily::Lambda
3710 } else {
3711 EmissionFamily::Gaussian
3712 },
3713 fit_lambdas,
3714 ..Default::default()
3715 };
3716 if let Ok(fit) = fit_em(&obs, &config)
3717 && let Ok(decode) = fit.params.decode(&obs)
3718 {
3719 for (j, &row_idx) in index_map.iter().enumerate() {
3720 states[row_idx] = decode.viterbi_path[j] as u32;
3721 for st in 0..m {
3722 probs_rows[row_idx][st] = decode.smooth_probs[st][j];
3723 }
3724 }
3725 }
3726 }
3727
3728 let mut prob_builder = ListPrimitiveChunkedBuilder::<Float64Type>::new(
3729 "hmm_fit_smooth_probs".into(),
3730 n,
3731 n * m,
3732 DataType::Float64,
3733 );
3734 for row in &probs_rows {
3735 prob_builder.append_slice(row);
3736 }
3737
3738 let state_series = Series::new("hmm_fit_state".into(), states);
3739 let prob_series = prob_builder.finish().into_series();
3740 let out = StructChunked::from_series(
3741 "hmm_fit_data".into(),
3742 n,
3743 [state_series, prob_series].iter(),
3744 )?;
3745 Ok(Some(Column::from(out.into_series())))
3746 },
3747 GetOutput::from_type(DataType::Struct(vec![
3748 Field::new("hmm_fit_state".into(), DataType::UInt32),
3749 Field::new(
3750 "hmm_fit_smooth_probs".into(),
3751 DataType::List(Box::new(DataType::Float64)),
3752 ),
3753 ])),
3754 )
3755 .alias("hmm_fit_data")])
3756 }
3757
3758 pub fn hmm_pseudo_residuals(
3760 self,
3761 name: &str,
3762 n_states: usize,
3763 max_iter: usize,
3764 fit_lambdas: bool,
3765 ) -> LazyFrame {
3766 let name_str = name.to_string();
3767 self.0.clone().with_columns([col(&name_str)
3768 .map(
3769 move |s| {
3770 use quantwave_core::regimes::gaussian_hmm::{
3771 EmissionFamily, GaussianHmmFitConfig, fit_em,
3772 };
3773 use quantwave_core::regimes::hmm_forecast::pseudo_residuals;
3774
3775 let ca = s.f64()?;
3776 let n = s.len();
3777 let mut values = vec![f64::NAN; n];
3778 let mut obs = Vec::new();
3779 let mut index_map = Vec::new();
3780 for i in 0..n {
3781 if let Some(v) = ca.get(i)
3782 && v.is_finite()
3783 {
3784 obs.push(v);
3785 index_map.push(i);
3786 }
3787 }
3788 let m = n_states.max(2);
3789 if obs.len() > m {
3790 let config = GaussianHmmFitConfig {
3791 n_states: m,
3792 max_iter: max_iter.max(1),
3793 emission_family: if fit_lambdas {
3794 EmissionFamily::Lambda
3795 } else {
3796 EmissionFamily::Gaussian
3797 },
3798 fit_lambdas,
3799 ..Default::default()
3800 };
3801 if let Ok(fit) = fit_em(&obs, &config)
3802 && let Ok(decode) = fit.params.decode(&obs)
3803 && let Ok(residuals) =
3804 pseudo_residuals(&fit.params, &decode.forward_filter, &obs)
3805 {
3806 for (j, &row_idx) in index_map.iter().enumerate() {
3807 values[row_idx] = residuals[j];
3808 }
3809 }
3810 }
3811 Ok(Some(Column::from(Series::new(
3812 "hmm_pseudo_residual".into(),
3813 values,
3814 ))))
3815 },
3816 GetOutput::from_type(DataType::Float64),
3817 )
3818 .alias("hmm_pseudo_residual")])
3819 }
3820
3821 pub fn hmm_decode_stats(
3823 self,
3824 name: &str,
3825 n_states: usize,
3826 max_iter: usize,
3827 fit_lambdas: bool,
3828 ) -> LazyFrame {
3829 let name_str = name.to_string();
3830 self.0.clone().with_columns([col(&name_str)
3831 .map(
3832 move |s| {
3833 use quantwave_core::regimes::gaussian_hmm::{
3834 EmissionFamily, GaussianHmmFitConfig, fit_em,
3835 };
3836 use quantwave_core::regimes::hmm_forecast::decode_stats_history;
3837
3838 let ca = s.f64()?;
3839 let n = s.len();
3840 let mut means = vec![f64::NAN; n];
3841 let mut vols = vec![f64::NAN; n];
3842 let mut lambdas = vec![f64::NAN; n];
3843 let mut obs = Vec::new();
3844 let mut index_map = Vec::new();
3845 for i in 0..n {
3846 if let Some(v) = ca.get(i)
3847 && v.is_finite()
3848 {
3849 obs.push(v);
3850 index_map.push(i);
3851 }
3852 }
3853 let m = n_states.max(2);
3854 if obs.len() > m {
3855 let config = GaussianHmmFitConfig {
3856 n_states: m,
3857 max_iter: max_iter.max(1),
3858 emission_family: if fit_lambdas {
3859 EmissionFamily::Lambda
3860 } else {
3861 EmissionFamily::Gaussian
3862 },
3863 fit_lambdas,
3864 ..Default::default()
3865 };
3866 if let Ok(fit) = fit_em(&obs, &config)
3867 && let Ok(decode) = fit.params.decode(&obs)
3868 && let Ok(stats) =
3869 decode_stats_history(&fit.params, &decode.smooth_probs)
3870 {
3871 for (j, row) in stats.iter().enumerate() {
3872 let idx = index_map[j];
3873 means[idx] = row.weighted_mean;
3874 vols[idx] = row.weighted_vol;
3875 lambdas[idx] = row.weighted_lambda;
3876 }
3877 }
3878 }
3879 let out = StructChunked::from_series(
3880 "hmm_decode_stats_data".into(),
3881 n,
3882 [
3883 Series::new("hmm_decode_weighted_mean".into(), means),
3884 Series::new("hmm_decode_weighted_vol".into(), vols),
3885 Series::new("hmm_decode_weighted_lambda".into(), lambdas),
3886 ]
3887 .iter(),
3888 )?;
3889 Ok(Some(Column::from(out.into_series())))
3890 },
3891 GetOutput::from_type(DataType::Struct(vec![
3892 Field::new("hmm_decode_weighted_mean".into(), DataType::Float64),
3893 Field::new("hmm_decode_weighted_vol".into(), DataType::Float64),
3894 Field::new("hmm_decode_weighted_lambda".into(), DataType::Float64),
3895 ])),
3896 )
3897 .alias("hmm_decode_stats_data")])
3898 }
3899
3900 pub fn hmm_forecast_vol(
3902 self,
3903 name: &str,
3904 n_states: usize,
3905 max_iter: usize,
3906 fit_lambdas: bool,
3907 horizon: usize,
3908 ) -> LazyFrame {
3909 let name_str = name.to_string();
3910 let h = horizon.max(1);
3911 self.0.clone().with_columns([col(&name_str)
3912 .map(
3913 move |s| {
3914 use quantwave_core::regimes::gaussian_hmm::{
3915 EmissionFamily, GaussianHmmFitConfig, fit_em,
3916 };
3917 use quantwave_core::regimes::hmm_forecast::forecast_volatility;
3918
3919 let ca = s.f64()?;
3920 let n = s.len();
3921 let mut values = vec![f64::NAN; n];
3922 let mut obs = Vec::new();
3923 let mut index_map = Vec::new();
3924 for i in 0..n {
3925 if let Some(v) = ca.get(i)
3926 && v.is_finite()
3927 {
3928 obs.push(v);
3929 index_map.push(i);
3930 }
3931 }
3932 let m = n_states.max(2);
3933 if obs.len() > m {
3934 let config = GaussianHmmFitConfig {
3935 n_states: m,
3936 max_iter: max_iter.max(1),
3937 emission_family: if fit_lambdas {
3938 EmissionFamily::Lambda
3939 } else {
3940 EmissionFamily::Gaussian
3941 },
3942 fit_lambdas,
3943 ..Default::default()
3944 };
3945 if let Ok(fit) = fit_em(&obs, &config)
3946 && let Ok(decode) = fit.params.decode(&obs)
3947 {
3948 for (j, &row_idx) in index_map.iter().enumerate() {
3949 let probs: Vec<f64> =
3950 (0..m).map(|st| decode.forward_filter[st][j]).collect();
3951 if let Ok(vol) = forecast_volatility(&fit.params, &probs, h) {
3952 values[row_idx] = vol;
3953 }
3954 }
3955 }
3956 }
3957 Ok(Some(Column::from(Series::new(
3958 "hmm_forecast_vol".into(),
3959 values,
3960 ))))
3961 },
3962 GetOutput::from_type(DataType::Float64),
3963 )
3964 .alias("hmm_forecast_vol")])
3965 }
3966
3967 pub fn pelt(self, name: &str, penalty: f64, min_dist: usize) -> LazyFrame {
3968 let name_str = name.to_string();
3969 self.0.clone().with_columns([col(&name_str)
3970 .map(
3971 move |s| {
3972 let ca = s.f64()?;
3973 let data: Vec<f64> = ca.into_iter().map(|v| v.unwrap_or(f64::NAN)).collect();
3974 let pelt = quantwave_core::regimes::pelt::PELT::new(penalty, min_dist);
3975 let cps = pelt.detect(&data);
3976
3977 let mut values = vec![0u32; s.len()];
3978 for cp in cps {
3979 if cp < values.len() {
3980 values[cp] = 1;
3981 }
3982 }
3983
3984 Ok(Some(Column::from(Series::new(
3985 "changepoints".into(),
3986 values,
3987 ))))
3988 },
3989 GetOutput::from_type(DataType::UInt32),
3990 )
3991 .alias("changepoints")])
3992 }
3993
3994 pub fn gmm(self, columns: &[&str], _k: usize) -> LazyFrame {
3995 let cols: Vec<String> = columns.iter().map(|s| s.to_string()).collect();
3996 let col_exprs: Vec<Expr> = cols.iter().map(col).collect();
3997
3998 self.0.clone().with_columns([as_struct(col_exprs)
3999 .map(
4000 move |s| {
4001 let ca = s.struct_()?;
4002 let n_rows = s.len();
4003 let n_dims = cols.len();
4004
4005 let mut data = Vec::with_capacity(n_rows);
4006 for i in 0..n_rows {
4007 let mut row = Vec::with_capacity(n_dims);
4008 for c_name in &cols {
4009 let f = ca.field_by_name(c_name)?;
4010 let val = f.f64()?.get(i).unwrap_or(f64::NAN);
4011 row.push(val);
4012 }
4013 data.push(row);
4014 }
4015
4016 let values = vec![0u32; n_rows];
4019
4020 Ok(Some(Column::from(Series::new("gmm_regime".into(), values))))
4021 },
4022 GetOutput::from_type(DataType::UInt32),
4023 )
4024 .alias("gmm_regime")])
4025 }
4026
4027 pub fn regimes_duration_stats(self, regime_col: &str, num_states: usize) -> LazyFrame {
4028 let name_str = regime_col.to_string();
4029 self.0.clone().with_columns([col(&name_str)
4030 .map(
4031 move |s| {
4032 let ca = s.u32()?;
4033 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
4034 let stats =
4035 quantwave_core::RegimeAnalytics::duration_stats(&states, num_states);
4036
4037 let mut regime_ids = Vec::new();
4039 let mut means = Vec::new();
4040 let mut medians = Vec::new();
4041 let mut stds = Vec::new();
4042 let mut maxes = Vec::new();
4043 let mut totals = Vec::new();
4044
4045 for stat in stats {
4046 regime_ids.push(stat.regime_id);
4047 means.push(stat.mean_duration);
4048 medians.push(stat.median_duration);
4049 stds.push(stat.std_duration);
4050 maxes.push(stat.max_duration as u32);
4051 totals.push(stat.total_observations as u32);
4052 }
4053
4054 let s_id = Series::new("regime_id".into(), regime_ids);
4055 let s_mean = Series::new("mean_duration".into(), means);
4056 let s_median = Series::new("median_duration".into(), medians);
4057 let s_std = Series::new("std_duration".into(), stds);
4058 let s_max = Series::new("max_duration".into(), maxes);
4059 let s_total = Series::new("total_observations".into(), totals);
4060
4061 let struct_series = StructChunked::from_series(
4062 "duration_stats".into(),
4063 s_id.len(),
4064 [s_id, s_mean, s_median, s_std, s_max, s_total].iter(),
4065 )?;
4066 Ok(Some(Column::from(struct_series.into_series())))
4067 },
4068 GetOutput::from_type(DataType::Struct(vec![
4069 Field::new("regime_id".into(), DataType::UInt32),
4070 Field::new("mean_duration".into(), DataType::Float64),
4071 Field::new("median_duration".into(), DataType::Float64),
4072 Field::new("std_duration".into(), DataType::Float64),
4073 Field::new("max_duration".into(), DataType::UInt32),
4074 Field::new("total_observations".into(), DataType::UInt32),
4075 ])),
4076 )
4077 .alias("regime_duration_stats")])
4078 }
4079
4080 pub fn regimes_transition_matrix(self, regime_col: &str, num_states: usize) -> LazyFrame {
4081 let name_str = regime_col.to_string();
4082 self.0.clone().with_columns([col(&name_str)
4083 .map(
4084 move |s| {
4085 let ca = s.u32()?;
4086 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
4087 let matrix =
4088 quantwave_core::RegimeAnalytics::transition_matrix(&states, num_states);
4089
4090 let mut builders = ListPrimitiveChunkedBuilder::<Float64Type>::new(
4092 "transition_matrix".into(),
4093 matrix.len(),
4094 matrix.len() * num_states,
4095 DataType::Float64,
4096 );
4097 for row in matrix {
4098 builders.append_slice(&row);
4099 }
4100
4101 let list_ca = builders.finish();
4102 Ok(Some(Column::from(list_ca.into_series())))
4103 },
4104 GetOutput::from_type(DataType::List(Box::new(DataType::Float64))),
4105 )
4106 .alias("regime_transition_matrix")])
4107 }
4108
4109 pub fn regimes_stability_score(self, regime_col: &str) -> LazyFrame {
4110 let name_str = regime_col.to_string();
4111 self.0.clone().with_columns([col(&name_str)
4112 .map(
4113 move |s| {
4114 let ca = s.u32()?;
4115 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
4116 let score = quantwave_core::RegimeAnalytics::stability_score(&states);
4117
4118 Ok(Some(Column::from(Series::new(
4119 "stability_score".into(),
4120 vec![score; s.len()],
4121 ))))
4122 },
4123 GetOutput::from_type(DataType::Float64),
4124 )
4125 .alias("regime_stability_score")])
4126 }
4127
4128 pub fn regimes_next_state_prob(
4129 self,
4130 regime_col: &str,
4131 num_states: usize,
4132 steps: usize,
4133 ) -> LazyFrame {
4134 let name_str = regime_col.to_string();
4135 self.0.clone().with_columns([col(&name_str)
4136 .map(
4137 move |s| {
4138 let ca = s.u32()?;
4139 let states: Vec<u32> = ca.into_iter().map(|v| v.unwrap_or(0)).collect();
4140 let matrix =
4141 quantwave_core::RegimeAnalytics::transition_matrix(&states, num_states);
4142
4143 let mut builders = ListPrimitiveChunkedBuilder::<Float64Type>::new(
4144 "next_state_probs".into(),
4145 s.len(),
4146 s.len() * num_states,
4147 DataType::Float64,
4148 );
4149
4150 for ¤t in &states {
4151 let probs = quantwave_core::RegimeAnalytics::forecast_state(
4152 &matrix, current, steps,
4153 );
4154 builders.append_slice(&probs);
4155 }
4156
4157 let list_ca = builders.finish();
4158 Ok(Some(Column::from(list_ca.into_series())))
4159 },
4160 GetOutput::from_type(DataType::List(Box::new(DataType::Float64))),
4161 )
4162 .alias("next_state_probs")])
4163 }
4164
4165 pub fn filter_by_regime(self, regime_col: &str, target_regime: u32) -> LazyFrame {
4166 self.0
4167 .clone()
4168 .filter(col(regime_col).eq(lit(target_regime)))
4169 }
4170
4171 pub fn apply_regime_strategy(
4172 self,
4173 regime_col: &str,
4174 signal_col: &str,
4175 regime_weights: std::collections::HashMap<u32, f64>,
4176 ) -> LazyFrame {
4177 let mut case_expr = col(signal_col);
4178 for (regime, weight) in regime_weights {
4179 case_expr = when(col(regime_col).eq(lit(regime)))
4180 .then(col(signal_col) * lit(weight))
4181 .otherwise(case_expr);
4182 }
4183
4184 self.0
4185 .clone()
4186 .with_columns([case_expr.alias("regime_adjusted_signal")])
4187 }
4188
4189 pub fn regimes_ms_garch(self, returns_col: &str) -> LazyFrame {
4190 let name_str = returns_col.to_string();
4191 self.0.clone().with_columns([col(&name_str)
4192 .map(
4193 move |s| {
4194 let ca = s.f64()?;
4195 let mut model = quantwave_core::regimes::ms_garch::MSGarch::low_high_vol();
4196 let mut regimes = Vec::with_capacity(s.len());
4197 let mut vols = Vec::with_capacity(s.len());
4198
4199 for i in 0..s.len() {
4200 let ret = ca.get(i).unwrap_or(0.0);
4201 let (regime, vol) = model.next(ret);
4202
4203 let r_val = match regime {
4204 quantwave_core::regimes::MarketRegime::Steady => 0u32,
4205 quantwave_core::regimes::MarketRegime::Crisis => 1,
4206 quantwave_core::regimes::MarketRegime::Bull => 2,
4207 quantwave_core::regimes::MarketRegime::Bear => 3,
4208 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
4209 };
4210 regimes.push(r_val);
4211 vols.push(vol);
4212 }
4213
4214 let s_regime = Series::new("regime".into(), regimes);
4215 let s_vol = Series::new("estimated_vol".into(), vols);
4216 let struct_series = StructChunked::from_series(
4217 "ms_garch_data".into(),
4218 s.len(),
4219 [s_regime, s_vol].iter(),
4220 )?;
4221 Ok(Some(Column::from(struct_series.into_series())))
4222 },
4223 GetOutput::from_type(DataType::Struct(vec![
4224 Field::new("regime".into(), DataType::UInt32),
4225 Field::new("estimated_vol".into(), DataType::Float64),
4226 ])),
4227 )
4228 .alias("ms_garch_data")])
4229 }
4230
4231 pub fn regimes_ensemble(self, columns: &[&str], weights: &[f64]) -> LazyFrame {
4232 let cols: Vec<String> = columns.iter().map(|s| s.to_string()).collect();
4233 let col_exprs: Vec<Expr> = cols.iter().map(col).collect();
4234 let w_vec = weights.to_vec();
4235
4236 self.0.clone().with_columns([as_struct(col_exprs)
4237 .map(
4238 move |s| {
4239 let ca = s.struct_()?;
4240 let n_rows = s.len();
4241 let n_dims = cols.len();
4242 let ensemble =
4243 quantwave_core::regimes::ensemble::RegimeEnsemble::new(w_vec.clone());
4244
4245 let mut results = Vec::with_capacity(n_rows);
4246 for i in 0..n_rows {
4247 let mut row_regimes = Vec::with_capacity(n_dims);
4248 for c_name in &cols {
4249 let f = ca.field_by_name(c_name)?;
4250 let val = f.u32()?.get(i).unwrap_or(0);
4251
4252 let regime = match val {
4253 0 => quantwave_core::regimes::MarketRegime::Steady,
4254 1 => quantwave_core::regimes::MarketRegime::Crisis,
4255 2 => quantwave_core::regimes::MarketRegime::Bull,
4256 3 => quantwave_core::regimes::MarketRegime::Bear,
4257 v if v >= 4 => {
4258 quantwave_core::regimes::MarketRegime::Cluster((v - 4) as u8)
4259 }
4260 _ => quantwave_core::regimes::MarketRegime::Steady,
4261 };
4262 row_regimes.push(regime);
4263 }
4264
4265 let consensus = ensemble.vote(&row_regimes);
4266 let out = match consensus {
4267 quantwave_core::regimes::MarketRegime::Steady => 0u32,
4268 quantwave_core::regimes::MarketRegime::Crisis => 1,
4269 quantwave_core::regimes::MarketRegime::Bull => 2,
4270 quantwave_core::regimes::MarketRegime::Bear => 3,
4271 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
4272 };
4273 results.push(out);
4274 }
4275
4276 Ok(Some(Column::from(Series::new(
4277 "ensemble_regime".into(),
4278 results,
4279 ))))
4280 },
4281 GetOutput::from_type(DataType::UInt32),
4282 )
4283 .alias("ensemble_regime")])
4284 }
4285
4286 pub fn regimes_tar(self, signal_col: &str, thresholds: Vec<f64>) -> LazyFrame {
4287 let name_str = signal_col.to_string();
4288 self.0.clone().with_columns([col(&name_str)
4289 .map(
4290 move |s| {
4291 let ca = s.f64()?;
4292 let mut model = quantwave_core::regimes::tar::TAR::multi(thresholds.clone());
4293 let mut results = Vec::with_capacity(s.len());
4294
4295 for i in 0..s.len() {
4296 let val = ca.get(i).unwrap_or(f64::NAN);
4297 let regime = model.next(val);
4298 let out = match regime {
4299 quantwave_core::regimes::MarketRegime::Steady => 0u32,
4300 quantwave_core::regimes::MarketRegime::Crisis => 1,
4301 quantwave_core::regimes::MarketRegime::Bull => 2,
4302 quantwave_core::regimes::MarketRegime::Bear => 3,
4303 quantwave_core::regimes::MarketRegime::Cluster(c) => 4 + (c as u32),
4304 };
4305 results.push(out);
4306 }
4307
4308 Ok(Some(Column::from(Series::new(
4309 "tar_regime".into(),
4310 results,
4311 ))))
4312 },
4313 GetOutput::from_type(DataType::UInt32),
4314 )
4315 .alias("tar_regime")])
4316 }
4317
4318 pub fn regimes_hsmm(self, name: &str) -> LazyFrame {
4319 let name_str = name.to_string();
4320 self.0.clone().with_columns([col(&name_str)
4321 .map(
4322 move |s| {
4323 let ca = s.f64()?;
4324 let mut model = quantwave_core::regimes::hsmm::HSMM::new(
4326 vec![vec![0.0, 1.0], vec![1.0, 0.0]], vec![0.001, -0.002],
4328 vec![0.01, 0.02],
4329 vec![
4330 quantwave_core::regimes::hsmm::DurationDistribution::Poisson {
4331 lambda: 5.0,
4332 },
4333 quantwave_core::regimes::hsmm::DurationDistribution::Poisson {
4334 lambda: 2.0,
4335 },
4336 ],
4337 );
4338 let mut values = Vec::with_capacity(s.len());
4339
4340 for i in 0..s.len() {
4341 let val = ca.get(i).unwrap_or(f64::NAN);
4342 let regime = model.next(val);
4343 let out = match regime {
4344 quantwave_core::regimes::MarketRegime::Steady => 0u32,
4345 quantwave_core::regimes::MarketRegime::Crisis => 1,
4346 _ => 2, };
4348 values.push(out);
4349 }
4350
4351 Ok(Some(Column::from(Series::new(
4352 "hsmm_regime".into(),
4353 values,
4354 ))))
4355 },
4356 GetOutput::from_type(DataType::UInt32),
4357 )
4358 .alias("hsmm_regime")])
4359 }
4360
4361 pub fn regimes_hmm_gas(self, name: &str) -> LazyFrame {
4362 let name_str = name.to_string();
4363 self.0.clone().with_columns([col(&name_str)
4364 .map(
4365 move |s| {
4366 let ca = s.f64()?;
4367 let mut model = quantwave_core::regimes::hmm_gas::HMMGAS::new(
4368 [0.1, 0.05, 0.9], [0.1, 0.05, 0.9], [0.001, -0.002],
4371 [0.01, 0.02],
4372 );
4373 let mut values = Vec::with_capacity(s.len());
4374
4375 for i in 0..s.len() {
4376 let val = ca.get(i).unwrap_or(f64::NAN);
4377 let regime = model.next(val);
4378 let out = match regime {
4379 quantwave_core::regimes::MarketRegime::Steady => 0u32,
4380 quantwave_core::regimes::MarketRegime::Crisis => 1,
4381 _ => 2,
4382 };
4383 values.push(out);
4384 }
4385
4386 Ok(Some(Column::from(Series::new(
4387 "hmm_gas_regime".into(),
4388 values,
4389 ))))
4390 },
4391 GetOutput::from_type(DataType::UInt32),
4392 )
4393 .alias("hmm_gas_regime")])
4394 }
4395
4396 pub fn regimes_conditioned_metrics(
4397 self,
4398 returns_col: &str,
4399 regime_col: &str,
4400 annualization_factor: f64,
4401 ) -> LazyFrame {
4402 let ret_str = returns_col.to_string();
4403 let reg_str = regime_col.to_string();
4404
4405 self.0.clone().group_by([col(®_str)]).agg([
4406 col(&ret_str).mean().alias("mean_return"),
4407 col(&ret_str).std(1).alias("volatility"),
4408 (col(&ret_str).mean() / col(&ret_str).std(1) * lit(annualization_factor.sqrt()))
4409 .alias("sharpe_ratio"),
4410 col(&ret_str).skew(true).alias("skewness"),
4411 col(&ret_str).kurtosis(true, true).alias("kurtosis"),
4412 (col(&ret_str).mean() / col(&ret_str).filter(col(&ret_str).lt(lit(0.0))).std(1)
4414 * lit(annualization_factor.sqrt()))
4415 .alias("sortino_ratio"),
4416 ])
4420 }
4421}
4422
4423#[cfg(test)]
4424mod tests {
4425 use super::*;
4426
4427 #[test]
4428 fn test_polars_heikin_ashi() -> PolarsResult<()> {
4429 let df = df![
4430 "open" => [10.0, 11.0],
4431 "high" => [12.0, 13.0],
4432 "low" => [8.0, 10.0],
4433 "close" => [11.0, 12.0]
4434 ]?;
4435
4436 let out = df
4437 .lazy()
4438 .ta()
4439 .heikin_ashi("open", "high", "low", "close")
4440 .collect()?;
4441
4442 let ha = out.column("heikin_ashi_data")?.struct_()?;
4443 assert_eq!(ha.field_by_name("ha_open")?.f64()?.get(0), Some(10.5));
4444 assert_eq!(ha.field_by_name("ha_close")?.f64()?.get(0), Some(10.25));
4445
4446 Ok(())
4447 }
4448
4449 #[test]
4450 fn test_polars_tema_zlema() -> PolarsResult<()> {
4451 let df = df![
4452 "price" => [1.0, 2.0, 3.0, 4.0, 5.0]
4453 ]?;
4454
4455 let out = df.clone().lazy().ta().tema("price", 3).collect()?;
4456
4457 let tema = out.column("tema")?.f64()?;
4458 assert!(tema.get(4).is_some());
4459
4460 let out2 = df.lazy().ta().zlema("price", 3).collect()?;
4461
4462 let zlema = out2.column("zlema")?.f64()?;
4463 assert!(zlema.get(4).is_some());
4464
4465 Ok(())
4466 }
4467
4468 #[test]
4469 fn test_polars_atr_ts() -> PolarsResult<()> {
4470 let df = df![
4471 "high" => [10.0, 12.0, 11.0],
4472 "low" => [8.0, 10.0, 9.0],
4473 "close" => [9.0, 11.0, 10.0]
4474 ]?;
4475
4476 let out = df
4477 .lazy()
4478 .ta()
4479 .atr_trailing_stop("high", "low", "close", 14, 2.5)
4480 .collect()?;
4481
4482 let atr_ts = out.column("atr_ts_data")?.struct_()?;
4483 assert!(atr_ts.field_by_name("stop")?.f64()?.get(0).is_some());
4484 assert!(atr_ts.field_by_name("direction")?.f64()?.get(0).is_some());
4485
4486 Ok(())
4487 }
4488
4489 #[test]
4490 fn test_polars_pivot_points() -> PolarsResult<()> {
4491 let df = df![
4492 "high" => [10.0, 12.0, 11.0],
4493 "low" => [8.0, 10.0, 9.0],
4494 "close" => [9.0, 11.0, 10.0]
4495 ]?;
4496
4497 let out = df
4498 .lazy()
4499 .ta()
4500 .pivot_points("high", "low", "close")
4501 .collect()?;
4502
4503 let pivot = out.column("pivot_points_data")?.struct_()?;
4504 assert!(pivot.field_by_name("p")?.f64()?.get(0).is_some());
4505 assert!(pivot.field_by_name("r1")?.f64()?.get(0).is_some());
4506
4507 Ok(())
4508 }
4509
4510 #[test]
4511 fn test_polars_fractals() -> PolarsResult<()> {
4512 let df = df![
4513 "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
4514 "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
4515 ]?;
4516
4517 let out = df
4518 .lazy()
4519 .ta()
4520 .bill_williams_fractals("high", "low")
4521 .collect()?;
4522
4523 let fractals = out.column("fractals_data")?.struct_()?;
4524 assert!(fractals.field_by_name("bearish")?.bool()?.get(4).unwrap());
4525 assert!(fractals.field_by_name("bullish")?.bool()?.get(4).unwrap());
4526
4527 Ok(())
4528 }
4529
4530 #[test]
4531 fn test_polars_ichimoku() -> PolarsResult<()> {
4532 let df = df![
4533 "high" => [10.0, 11.0, 15.0, 12.0, 10.0],
4534 "low" => [5.0, 6.0, 2.0, 6.0, 7.0]
4535 ]?;
4536
4537 let out = df
4538 .lazy()
4539 .ta()
4540 .ichimoku_cloud("high", "low", 9, 26, 52)
4541 .collect()?;
4542
4543 let ichimoku = out.column("ichimoku_data")?.struct_()?;
4544 assert!(ichimoku.field_by_name("tenkan")?.f64()?.get(4).is_some());
4545 assert!(ichimoku.field_by_name("kijun")?.f64()?.get(4).is_some());
4546
4547 Ok(())
4548 }
4549
4550 #[test]
4551 fn test_polars_wavetrend() -> PolarsResult<()> {
4552 let df = df![
4553 "high" => [10.0, 12.0, 11.0],
4554 "low" => [8.0, 10.0, 9.0],
4555 "close" => [9.0, 11.0, 10.0]
4556 ]?;
4557
4558 let out = df
4559 .lazy()
4560 .ta()
4561 .wavetrend("high", "low", "close", 10, 21, 4)
4562 .collect()?;
4563
4564 let wt = out.column("wavetrend_data")?.struct_()?;
4565 assert!(wt.field_by_name("wt1")?.f64()?.get(0).is_some());
4566 assert!(wt.field_by_name("wt2")?.f64()?.get(0).is_some());
4567
4568 Ok(())
4569 }
4570
4571 #[test]
4572 fn test_polars_vortex() -> PolarsResult<()> {
4573 let df = df![
4574 "high" => [10.0, 12.0, 11.0],
4575 "low" => [8.0, 10.0, 9.0],
4576 "close" => [9.0, 11.0, 10.0]
4577 ]?;
4578
4579 let out = df
4580 .lazy()
4581 .ta()
4582 .vortex_indicator("high", "low", "close", 14)
4583 .collect()?;
4584
4585 let vortex = out.column("vortex_data")?.struct_()?;
4586 assert!(vortex.field_by_name("vi_plus")?.f64()?.get(0).is_some());
4587 assert!(vortex.field_by_name("vi_minus")?.f64()?.get(0).is_some());
4588
4589 Ok(())
4590 }
4591
4592 #[test]
4593 fn test_polars_ttm_squeeze() -> PolarsResult<()> {
4594 let df = df![
4595 "high" => [11.0, 12.0, 13.0, 14.0],
4596 "low" => [9.0, 10.0, 11.0, 12.0],
4597 "close" => [10.0, 11.0, 12.0, 13.0]
4598 ]?;
4599
4600 let out = df
4601 .lazy()
4602 .ta()
4603 .ttm_squeeze("high", "low", "close", 20, 2.0, 1.5)
4604 .collect()?;
4605
4606 let ttm = out.column("ttm_squeeze_data")?.struct_()?;
4607 assert!(ttm.field_by_name("histogram")?.f64()?.get(0).is_some());
4608 assert!(ttm.field_by_name("is_squeezed")?.bool()?.get(0).is_some());
4609
4610 Ok(())
4611 }
4612
4613 #[test]
4614 fn test_polars_donchian() -> PolarsResult<()> {
4615 let df = df![
4616 "high" => [10.0, 12.0, 11.0, 13.0, 15.0],
4617 "low" => [8.0, 7.0, 9.0, 10.0, 12.0]
4618 ]?;
4619
4620 let out = df
4621 .lazy()
4622 .ta()
4623 .donchian_channels("high", "low", 3)
4624 .collect()?;
4625
4626 let donchian = out.column("donchian_data")?.struct_()?;
4627 assert_eq!(donchian.field_by_name("upper")?.f64()?.get(3), Some(13.0));
4629 assert_eq!(donchian.field_by_name("middle")?.f64()?.get(3), Some(10.0));
4630 assert_eq!(donchian.field_by_name("lower")?.f64()?.get(3), Some(7.0));
4631
4632 Ok(())
4633 }
4634
4635 #[test]
4636 fn test_polars_alma() -> PolarsResult<()> {
4637 let df = df![
4638 "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
4639 ]?;
4640
4641 let out = df.lazy().ta().alma("price", 9, 0.85, 6.0).collect()?;
4642
4643 let alma = out.column("alma")?.f64()?;
4644 assert!(alma.get(9).is_some());
4645
4646 Ok(())
4647 }
4648
4649 #[test]
4650 fn test_polars_keltner() -> PolarsResult<()> {
4651 let df = df![
4652 "high" => [12.0],
4653 "low" => [8.0],
4654 "close" => [10.0]
4655 ]?;
4656
4657 let out = df
4658 .lazy()
4659 .ta()
4660 .keltner_channels("high", "low", "close", 3, 3, 2.0)
4661 .collect()?;
4662
4663 let keltner = out.column("keltner_data")?.struct_()?;
4664 assert_eq!(keltner.field_by_name("middle")?.f64()?.get(0), Some(10.0));
4665 assert_eq!(keltner.field_by_name("upper")?.f64()?.get(0), Some(18.0));
4666 assert_eq!(keltner.field_by_name("lower")?.f64()?.get(0), Some(2.0));
4667
4668 Ok(())
4669 }
4670
4671 #[test]
4672 fn test_polars_hma() -> PolarsResult<()> {
4673 let df = df![
4674 "price" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
4675 ]?;
4676
4677 let out = df.lazy().ta().hma("price", 4).collect()?;
4678
4679 let hma = out.column("hma")?.f64()?;
4680 assert!(hma.get(9).is_some());
4681
4682 Ok(())
4683 }
4684
4685 #[test]
4686 fn test_polars_anchored_vwap() -> PolarsResult<()> {
4687 let df = df![
4688 "price" => [10.0, 12.0, 15.0, 16.0],
4689 "volume" => [100.0, 200.0, 100.0, 100.0],
4690 "anchor" => [false, false, true, false]
4691 ]?;
4692
4693 let out = df
4694 .lazy()
4695 .ta()
4696 .anchored_vwap("price", "volume", "anchor")
4697 .collect()?;
4698
4699 let avwap = out.column("avwap")?.f64()?;
4700 assert_eq!(avwap.get(0), Some(10.0));
4701 assert_eq!(avwap.get(1), Some(11.333333333333334));
4702 assert_eq!(avwap.get(2), Some(15.0));
4703 assert_eq!(avwap.get(3), Some(15.5));
4704
4705 Ok(())
4706 }
4707
4708 #[test]
4709 fn test_polars_math_transforms() -> PolarsResult<()> {
4710 let df = df![
4711 "val" => [0.0, 1.5707963267948966] ]?;
4713
4714 let out = df.lazy().ta().sin("val").collect()?;
4715
4716 let sin = out.column("sin")?.f64()?;
4717 assert!((sin.get(0).unwrap() - 0.0).abs() < 1e-10);
4718 assert!((sin.get(1).unwrap() - 1.0).abs() < 1e-10);
4719
4720 Ok(())
4721 }
4722
4723 #[test]
4724 fn test_polars_math_operators() -> PolarsResult<()> {
4725 let df = df![
4726 "v1" => [10.0, 20.0],
4727 "v2" => [5.0, 30.0]
4728 ]?;
4729
4730 let out = df.lazy().ta().add("v1", "v2").ta().max("v1", 2).collect()?;
4731
4732 let add = out.column("add")?.f64()?;
4733 assert_eq!(add.get(0), Some(15.0));
4734 assert_eq!(add.get(1), Some(50.0));
4735
4736 let max = out.column("max")?.f64()?;
4737 assert_eq!(max.get(1), Some(20.0));
4738
4739 Ok(())
4740 }
4741
4742 #[test]
4743 fn test_polars_vpn() -> PolarsResult<()> {
4744 let df = df![
4745 "high" => [10.0, 11.0, 12.0],
4746 "low" => [9.0, 10.0, 11.0],
4747 "close" => [9.5, 10.5, 11.5],
4748 "volume" => [1000.0, 1100.0, 1200.0]
4749 ]?;
4750
4751 let out = df
4752 .lazy()
4753 .ta()
4754 .vpn("high", "low", "close", "volume", 30, 3)
4755 .collect()?;
4756 let vpn = out.column("vpn")?.f64()?;
4757 assert!(vpn.get(2).is_some());
4758 Ok(())
4759 }
4760
4761 #[test]
4762 fn test_polars_gap_momentum() -> PolarsResult<()> {
4763 let df = df![
4764 "open" => [10.0, 11.0, 10.0],
4765 "close" => [10.5, 10.5, 9.5]
4766 ]?;
4767
4768 let out = df
4769 .lazy()
4770 .ta()
4771 .gap_momentum("open", "close", 10, 5)
4772 .collect()?;
4773 let gm = out.column("gap_momentum")?.struct_()?;
4774 assert!(gm.field_by_name("gap_ratio")?.f64()?.get(2).is_some());
4775 assert!(gm.field_by_name("gap_signal")?.f64()?.get(2).is_some());
4776 Ok(())
4777 }
4778
4779 #[test]
4780 fn test_polars_autotune() -> PolarsResult<()> {
4781 let df = df![
4782 "price" => [100.0; 50]
4783 ]?;
4784
4785 let out = df
4786 .lazy()
4787 .ta()
4788 .autotune_filter("price", 20, 0.25)
4789 .collect()?;
4790 let at = out.column("autotune")?.f64()?;
4791 assert!(at.get(49).is_some());
4792 Ok(())
4793 }
4794
4795 #[test]
4796 fn test_polars_adaptive_ema() -> PolarsResult<()> {
4797 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
4798 let out = df
4799 .lazy()
4800 .ta()
4801 .adaptive_ema("h", "l", "c", 10, 2)
4802 .collect()?;
4803 assert!(out.column("adaptive_ema")?.f64()?.get(2).is_some());
4804 Ok(())
4805 }
4806
4807 #[test]
4808 fn test_polars_obvm() -> PolarsResult<()> {
4809 let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
4810 let out = df.lazy().ta().obvm("h", "l", "c", "v", 10, 3).collect()?;
4811 let data = out.column("obvm_data")?.struct_()?;
4812 assert!(data.field_by_name("obvm")?.f64()?.get(1).is_some());
4813 Ok(())
4814 }
4815
4816 #[test]
4817 fn test_polars_vfi() -> PolarsResult<()> {
4818 let df = df!["h" => [10.0, 11.0], "l" => [9.0, 10.0], "c" => [9.5, 10.5], "v" => [100.0, 200.0]]?;
4819 let out = df
4820 .lazy()
4821 .ta()
4822 .vfi("h", "l", "c", "v", 10, 0.2, 2.5, 3)
4823 .collect()?;
4824 assert!(out.column("vfi")?.f64()?.get(1).is_some());
4825 Ok(())
4826 }
4827
4828 #[test]
4829 fn test_polars_sdo() -> PolarsResult<()> {
4830 let df = df!["p" => [10.0, 11.0, 12.0]]?;
4831 let out = df.lazy().ta().sdo("p", 2, 5, 3).collect()?;
4832 assert!(out.column("sdo")?.f64()?.get(2).is_some());
4833 Ok(())
4834 }
4835
4836 #[test]
4837 fn test_polars_rsmk() -> PolarsResult<()> {
4838 let df = df!["p" => [10.0, 11.0], "b" => [100.0, 101.0]]?;
4839 let out = df.lazy().ta().rsmk("p", "b", 90, 3).collect()?;
4840 assert!(out.column("rsmk")?.f64()?.get(1).is_some());
4841 Ok(())
4842 }
4843
4844 #[test]
4845 fn test_polars_rodc() -> PolarsResult<()> {
4846 let df = df!["p" => [10.0, 11.0, 10.0, 11.0, 12.0]]?;
4847 let out = df.lazy().ta().rodc("p", 10, 0.5, 3).collect()?;
4848 assert!(out.column("rodc")?.f64()?.get(4).is_some());
4849 Ok(())
4850 }
4851
4852 #[test]
4853 fn test_polars_reverse_ema() -> PolarsResult<()> {
4854 let df = df!["p" => [10.0, 11.0, 12.0]]?;
4855 let out = df.lazy().ta().reverse_ema("p", 0.1).collect()?;
4856 assert!(out.column("reverse_ema")?.f64()?.get(2).is_some());
4857 Ok(())
4858 }
4859
4860 #[test]
4861 fn test_polars_harrington_adx() -> PolarsResult<()> {
4862 let df =
4863 df!["h" => [10.0, 11.0, 12.0], "l" => [9.0, 10.0, 11.0], "c" => [9.5, 10.5, 11.5]]?;
4864 let out = df
4865 .lazy()
4866 .ta()
4867 .harrington_adx("h", "l", "c", 10, 1)
4868 .collect()?;
4869 assert!(out.column("harrington_adx")?.f64()?.get(2).is_some());
4870 Ok(())
4871 }
4872
4873 #[test]
4874 fn test_polars_tradj_ema() -> PolarsResult<()> {
4875 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
4876 let out = df
4877 .lazy()
4878 .ta()
4879 .tradj_ema("h", "l", "c", 10, 2, 0.5)
4880 .collect()?;
4881 assert!(out.column("tradj_ema")?.f64()?.get(2).is_some());
4882 Ok(())
4883 }
4884
4885 #[test]
4886 fn test_polars_sve_volatility_bands() -> PolarsResult<()> {
4887 let df = df!["h" => [10.0, 11.0, 10.5], "l" => [9.0, 10.0, 9.5], "c" => [9.5, 10.5, 10.0]]?;
4888 let out = df
4889 .lazy()
4890 .ta()
4891 .sve_volatility_bands("h", "l", "c", 10, 1.5, 1.0, 3)
4892 .collect()?;
4893 let data = out.column("sve_bands_data")?.struct_()?;
4894 assert!(data.field_by_name("upper")?.f64()?.get(2).is_some());
4895 Ok(())
4896 }
4897
4898 #[test]
4899 fn test_polars_exp_dev_bands() -> PolarsResult<()> {
4900 let df = df!["p" => [10.0, 11.0, 12.0, 11.0, 10.0]]?;
4901 let out = df.lazy().ta().exp_dev_bands("p", 10, 2.0, true).collect()?;
4902 let data = out.column("exp_dev_bands_data")?.struct_()?;
4903 assert!(data.field_by_name("upper")?.f64()?.get(4).is_some());
4904 Ok(())
4905 }
4906
4907 #[test]
4908 fn test_polars_hmm_fit() -> PolarsResult<()> {
4909 let obs: Vec<f64> = vec![
4910 0.01, -0.008, 0.012, -0.015, 0.009, -0.011, 0.007, -0.013, 0.011, -0.009, 0.008,
4911 -0.014, 0.006, -0.01, 0.013, -0.012, 0.005, -0.007, 0.01, -0.016,
4912 ];
4913 let df = df!["returns" => obs.as_slice()]?;
4914 let out = df.lazy().ta().hmm_fit("returns", 2, 40, false).collect()?;
4915 let data = out.column("hmm_fit_data")?.struct_()?;
4916 let state_col = data.field_by_name("hmm_fit_state")?;
4917 let prob_col = data.field_by_name("hmm_fit_smooth_probs")?;
4918 let states = state_col.u32()?;
4919 let probs = prob_col.list()?;
4920 assert_eq!(states.len(), obs.len());
4921 assert_eq!(probs.len(), obs.len());
4922 assert!(states.get(0).is_some());
4923 Ok(())
4924 }
4925
4926 #[test]
4927 fn test_regimes_conditioned_metrics() -> PolarsResult<()> {
4928 let df = df![
4929 "returns" => [0.01, 0.02, -0.01, -0.02, 0.01],
4930 "regime" => [0u32, 0, 1, 1, 0]
4931 ]?;
4932
4933 let out = df
4934 .lazy()
4935 .ta()
4936 .regimes_conditioned_metrics("returns", "regime", 252.0)
4937 .collect()?;
4938
4939 assert_eq!(out.height(), 2);
4940 assert!(out.column("sharpe_ratio").is_ok());
4941 assert!(out.column("skewness").is_ok());
4942 assert!(out.column("kurtosis").is_ok());
4943 assert!(out.column("sortino_ratio").is_ok());
4944
4945 Ok(())
4946 }
4947
4948 #[test]
4949 fn test_polars_kalman_filters() -> PolarsResult<()> {
4950 let df = df![
4951 "price" => [100.0, 101.0, 102.0, 103.0, 104.0]
4952 ]?;
4953
4954 let out = df
4955 .clone()
4956 .lazy()
4957 .ta()
4958 .kalman("price", 0.01, 0.1)
4959 .collect()?;
4960
4961 let kalman = out.column("kalman")?.f64()?;
4962 assert!(kalman.get(0).is_some());
4963 assert_eq!(kalman.get(0).unwrap(), 100.0);
4964
4965 let out2 = df
4966 .lazy()
4967 .ta()
4968 .kinematic_kalman("price", 0.001, 0.0001, 0.1)
4969 .collect()?;
4970
4971 let kin_kalman = out2.column("kinematic_kalman")?.f64()?;
4972 assert!(kin_kalman.get(0).is_some());
4973 assert_eq!(kin_kalman.get(0).unwrap(), 100.0);
4974
4975 Ok(())
4976 }
4977
4978 #[test]
4982 fn smoke_ta_pa_foundation() -> PolarsResult<()> {
4983 let highs: Vec<f64> = (0..60)
4984 .map(|i| 100.0 + (i as f64 * 0.8).sin() * 5.0 + (i as f64) * 0.3)
4985 .collect();
4986 let lows: Vec<f64> = highs.iter().map(|&h| h - 1.5 - (h % 3.0) * 0.2).collect();
4987
4988 let df = df!["high" => highs, "low" => lows]?;
4989 let lf = df.lazy();
4990
4991 let out = lf
4993 .clone()
4994 .ta()
4995 .market_structure("high", "low", 3)
4996 .collect()?;
4997 let ms = out.column("market_structure")?;
4998 assert!(matches!(ms.dtype(), DataType::Struct(_)));
4999 let ca = ms.struct_()?;
5000 assert_eq!(ca.field_by_name("bias")?.dtype().clone(), DataType::UInt32);
5002 assert!(ca.field_by_name("has_flip")?.bool()?.get(59).is_some());
5003
5004 let out2 = out
5006 .lazy()
5007 .ta()
5008 .geometric_patterns("high", "low", 2)
5009 .collect()?;
5010 let gp = out2.column("geometric_patterns")?;
5011 assert!(matches!(gp.dtype(), DataType::Struct(_)));
5012 let gca = gp.struct_()?;
5013 let flag = gca.field_by_name("flag")?;
5014 assert!(matches!(flag.dtype(), DataType::Struct(_)));
5015 assert!(
5017 flag.struct_()?
5018 .field_by_name("pole_length_atr")?
5019 .f64()?
5020 .get(59)
5021 .is_some()
5022 );
5023
5024 Ok(())
5025 }
5026}
5027
5028impl QuantWaveExt for LazyFrame {
5029 fn ta(&self) -> QuantWaveNamespace<'_> {
5030 QuantWaveNamespace(self)
5031 }
5032}
5033
5034pub use bt::{BtNamespace, BtOptions, QuantWaveBtExt};
5035pub use quantwave_backtest::{SweepVariant, run_param_sweep, single_param_variants};