1#[cfg(feature = "python")]
2use numpy::{IntoPyArray, PyArray1, PyArrayMethods, PyReadonlyArray1};
3#[cfg(feature = "python")]
4use pyo3::exceptions::PyValueError;
5#[cfg(feature = "python")]
6use pyo3::prelude::*;
7#[cfg(feature = "python")]
8use pyo3::types::PyDict;
9#[cfg(feature = "python")]
10use pyo3::wrap_pyfunction;
11
12#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
13use serde::{Deserialize, Serialize};
14#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
15use wasm_bindgen::prelude::*;
16
17use crate::utilities::data_loader::Candles;
18use crate::utilities::enums::Kernel;
19use crate::utilities::helpers::{
20 alloc_with_nan_prefix, detect_best_batch_kernel, detect_best_kernel, init_matrix_prefixes,
21 make_uninit_matrix,
22};
23#[cfg(feature = "python")]
24use crate::utilities::kernel_validation::validate_kernel;
25#[cfg(not(target_arch = "wasm32"))]
26use rayon::prelude::*;
27use std::mem::{ManuallyDrop, MaybeUninit};
28use thiserror::Error;
29
30const DEFAULT_SOURCE: &str = "hlc3";
31const DEFAULT_LENGTH: usize = 21;
32const DEFAULT_ATR_LENGTH: usize = 14;
33const DEFAULT_USE_ATR: bool = true;
34const DEFAULT_TP_AGGRESSIVENESS: &str = "low";
35const MULT1: f64 = 0.618;
36const MULT2: f64 = 1.0;
37const MULT3: f64 = 1.618;
38const MULT4: f64 = 2.618;
39const FLOAT_TOL: f64 = 1e-12;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42enum SourceKind {
43 Open,
44 High,
45 Low,
46 Close,
47 Hl2,
48 Hlc3,
49 Ohlc4,
50 Hlcc4,
51}
52
53impl SourceKind {
54 #[inline(always)]
55 fn parse(value: &str) -> Option<Self> {
56 if value.eq_ignore_ascii_case("open") {
57 Some(Self::Open)
58 } else if value.eq_ignore_ascii_case("high") {
59 Some(Self::High)
60 } else if value.eq_ignore_ascii_case("low") {
61 Some(Self::Low)
62 } else if value.eq_ignore_ascii_case("close") {
63 Some(Self::Close)
64 } else if value.eq_ignore_ascii_case("hl2") {
65 Some(Self::Hl2)
66 } else if value.eq_ignore_ascii_case("hlc3") {
67 Some(Self::Hlc3)
68 } else if value.eq_ignore_ascii_case("ohlc4") {
69 Some(Self::Ohlc4)
70 } else if value.eq_ignore_ascii_case("hlcc4") || value.eq_ignore_ascii_case("hlcc") {
71 Some(Self::Hlcc4)
72 } else {
73 None
74 }
75 }
76
77 #[inline(always)]
78 fn as_str(self) -> &'static str {
79 match self {
80 Self::Open => "open",
81 Self::High => "high",
82 Self::Low => "low",
83 Self::Close => "close",
84 Self::Hl2 => "hl2",
85 Self::Hlc3 => "hlc3",
86 Self::Ohlc4 => "ohlc4",
87 Self::Hlcc4 => "hlcc4",
88 }
89 }
90
91 #[inline(always)]
92 fn needs_open(self) -> bool {
93 matches!(self, Self::Open | Self::Ohlc4)
94 }
95
96 #[inline(always)]
97 fn value(self, open: f64, high: f64, low: f64, close: f64) -> f64 {
98 match self {
99 Self::Open => open,
100 Self::High => high,
101 Self::Low => low,
102 Self::Close => close,
103 Self::Hl2 => 0.5 * (high + low),
104 Self::Hlc3 => (high + low + close) / 3.0,
105 Self::Ohlc4 => (open + high + low + close) * 0.25,
106 Self::Hlcc4 => (high + low + close + close) * 0.25,
107 }
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112enum TpAggressiveness {
113 Low,
114 Medium,
115 High,
116}
117
118impl TpAggressiveness {
119 #[inline(always)]
120 fn parse(value: &str) -> Option<Self> {
121 if value.eq_ignore_ascii_case("low") {
122 Some(Self::Low)
123 } else if value.eq_ignore_ascii_case("medium") {
124 Some(Self::Medium)
125 } else if value.eq_ignore_ascii_case("high") {
126 Some(Self::High)
127 } else {
128 None
129 }
130 }
131
132 #[inline(always)]
133 fn as_str(self) -> &'static str {
134 match self {
135 Self::Low => "low",
136 Self::Medium => "medium",
137 Self::High => "high",
138 }
139 }
140}
141
142#[derive(Debug, Clone)]
143pub enum FibonacciEntryBandsData<'a> {
144 Candles(&'a Candles),
145 Slices {
146 open: &'a [f64],
147 high: &'a [f64],
148 low: &'a [f64],
149 close: &'a [f64],
150 },
151}
152
153#[derive(Debug, Clone)]
154pub struct FibonacciEntryBandsOutput {
155 pub basis: Vec<f64>,
156 pub trend: Vec<f64>,
157 pub upper_0618: Vec<f64>,
158 pub upper_1000: Vec<f64>,
159 pub upper_1618: Vec<f64>,
160 pub upper_2618: Vec<f64>,
161 pub lower_0618: Vec<f64>,
162 pub lower_1000: Vec<f64>,
163 pub lower_1618: Vec<f64>,
164 pub lower_2618: Vec<f64>,
165 pub tp_long_band: Vec<f64>,
166 pub tp_short_band: Vec<f64>,
167 pub long_entry: Vec<f64>,
168 pub short_entry: Vec<f64>,
169 pub rejection_long: Vec<f64>,
170 pub rejection_short: Vec<f64>,
171 pub long_bounce: Vec<f64>,
172 pub short_bounce: Vec<f64>,
173}
174
175#[derive(Debug, Clone, Copy)]
176pub struct FibonacciEntryBandsPoint {
177 pub basis: f64,
178 pub trend: f64,
179 pub upper_0618: f64,
180 pub upper_1000: f64,
181 pub upper_1618: f64,
182 pub upper_2618: f64,
183 pub lower_0618: f64,
184 pub lower_1000: f64,
185 pub lower_1618: f64,
186 pub lower_2618: f64,
187 pub tp_long_band: f64,
188 pub tp_short_band: f64,
189 pub long_entry: f64,
190 pub short_entry: f64,
191 pub rejection_long: f64,
192 pub rejection_short: f64,
193 pub long_bounce: f64,
194 pub short_bounce: f64,
195}
196
197impl FibonacciEntryBandsPoint {
198 #[inline(always)]
199 fn nan() -> Self {
200 Self {
201 basis: f64::NAN,
202 trend: f64::NAN,
203 upper_0618: f64::NAN,
204 upper_1000: f64::NAN,
205 upper_1618: f64::NAN,
206 upper_2618: f64::NAN,
207 lower_0618: f64::NAN,
208 lower_1000: f64::NAN,
209 lower_1618: f64::NAN,
210 lower_2618: f64::NAN,
211 tp_long_band: f64::NAN,
212 tp_short_band: f64::NAN,
213 long_entry: f64::NAN,
214 short_entry: f64::NAN,
215 rejection_long: f64::NAN,
216 rejection_short: f64::NAN,
217 long_bounce: f64::NAN,
218 short_bounce: f64::NAN,
219 }
220 }
221}
222
223#[derive(Debug, Clone, PartialEq)]
224#[cfg_attr(
225 all(target_arch = "wasm32", feature = "wasm"),
226 derive(Serialize, Deserialize)
227)]
228pub struct FibonacciEntryBandsParams {
229 pub source: Option<String>,
230 pub length: Option<usize>,
231 pub atr_length: Option<usize>,
232 pub use_atr: Option<bool>,
233 pub tp_aggressiveness: Option<String>,
234}
235
236impl Default for FibonacciEntryBandsParams {
237 fn default() -> Self {
238 Self {
239 source: Some(DEFAULT_SOURCE.to_string()),
240 length: Some(DEFAULT_LENGTH),
241 atr_length: Some(DEFAULT_ATR_LENGTH),
242 use_atr: Some(DEFAULT_USE_ATR),
243 tp_aggressiveness: Some(DEFAULT_TP_AGGRESSIVENESS.to_string()),
244 }
245 }
246}
247
248#[derive(Debug, Clone)]
249pub struct FibonacciEntryBandsInput<'a> {
250 pub data: FibonacciEntryBandsData<'a>,
251 pub params: FibonacciEntryBandsParams,
252}
253
254impl<'a> FibonacciEntryBandsInput<'a> {
255 #[inline]
256 pub fn from_candles(candles: &'a Candles, params: FibonacciEntryBandsParams) -> Self {
257 Self {
258 data: FibonacciEntryBandsData::Candles(candles),
259 params,
260 }
261 }
262
263 #[inline]
264 pub fn from_slices(
265 open: &'a [f64],
266 high: &'a [f64],
267 low: &'a [f64],
268 close: &'a [f64],
269 params: FibonacciEntryBandsParams,
270 ) -> Self {
271 Self {
272 data: FibonacciEntryBandsData::Slices {
273 open,
274 high,
275 low,
276 close,
277 },
278 params,
279 }
280 }
281
282 #[inline]
283 pub fn with_default_candles(candles: &'a Candles) -> Self {
284 Self::from_candles(candles, FibonacciEntryBandsParams::default())
285 }
286
287 #[inline]
288 pub fn as_slices(&self) -> (&'a [f64], &'a [f64], &'a [f64], &'a [f64]) {
289 match &self.data {
290 FibonacciEntryBandsData::Candles(candles) => {
291 (&candles.open, &candles.high, &candles.low, &candles.close)
292 }
293 FibonacciEntryBandsData::Slices {
294 open,
295 high,
296 low,
297 close,
298 } => (open, high, low, close),
299 }
300 }
301}
302
303#[derive(Clone, Debug)]
304pub struct FibonacciEntryBandsBuilder {
305 source: Option<SourceKind>,
306 length: Option<usize>,
307 atr_length: Option<usize>,
308 use_atr: Option<bool>,
309 tp_aggressiveness: Option<TpAggressiveness>,
310 kernel: Kernel,
311}
312
313impl Default for FibonacciEntryBandsBuilder {
314 fn default() -> Self {
315 Self {
316 source: None,
317 length: None,
318 atr_length: None,
319 use_atr: None,
320 tp_aggressiveness: None,
321 kernel: Kernel::Auto,
322 }
323 }
324}
325
326#[derive(Debug, Error)]
327pub enum FibonacciEntryBandsError {
328 #[error("fibonacci_entry_bands: Input data slice is empty.")]
329 EmptyInputData,
330 #[error("fibonacci_entry_bands: All values are NaN.")]
331 AllValuesNaN,
332 #[error(
333 "fibonacci_entry_bands: Inconsistent slice lengths: open={open_len}, high={high_len}, low={low_len}, close={close_len}"
334 )]
335 InconsistentSliceLengths {
336 open_len: usize,
337 high_len: usize,
338 low_len: usize,
339 close_len: usize,
340 },
341 #[error("fibonacci_entry_bands: Invalid length: length = {length}, data length = {data_len}")]
342 InvalidLength { length: usize, data_len: usize },
343 #[error(
344 "fibonacci_entry_bands: Invalid atr_length: atr_length = {atr_length}, data length = {data_len}"
345 )]
346 InvalidAtrLength { atr_length: usize, data_len: usize },
347 #[error(
348 "fibonacci_entry_bands: Invalid source: {source_name}. Supported: open, high, low, close, hl2, hlc3, ohlc4, hlcc4"
349 )]
350 InvalidSource { source_name: String },
351 #[error(
352 "fibonacci_entry_bands: Invalid TP aggressiveness: {tp_aggressiveness}. Supported: low, medium, high"
353 )]
354 InvalidTpAggressiveness { tp_aggressiveness: String },
355 #[error("fibonacci_entry_bands: Not enough valid data: needed = {needed}, valid = {valid}")]
356 NotEnoughValidData { needed: usize, valid: usize },
357 #[error("fibonacci_entry_bands: Output length mismatch: expected = {expected}")]
358 OutputLengthMismatch { expected: usize },
359 #[error("fibonacci_entry_bands: Invalid range: start={start}, end={end}, step={step}")]
360 InvalidRange {
361 start: String,
362 end: String,
363 step: String,
364 },
365 #[error("fibonacci_entry_bands: Invalid kernel for batch: {0:?}")]
366 InvalidKernelForBatch(Kernel),
367}
368
369#[derive(Debug, Clone, Copy)]
370struct ResolvedParams {
371 source: SourceKind,
372 length: usize,
373 atr_length: usize,
374 use_atr: bool,
375 tp_aggressiveness: TpAggressiveness,
376 ema_alpha: f64,
377}
378
379#[derive(Debug, Clone)]
380struct RollingStdev {
381 values: Vec<f64>,
382 head: usize,
383 count: usize,
384 sum: f64,
385 sum_sq: f64,
386}
387
388impl RollingStdev {
389 #[inline]
390 fn new(length: usize) -> Self {
391 Self {
392 values: vec![0.0; length.max(1)],
393 head: 0,
394 count: 0,
395 sum: 0.0,
396 sum_sq: 0.0,
397 }
398 }
399
400 #[inline]
401 fn reset(&mut self) {
402 self.head = 0;
403 self.count = 0;
404 self.sum = 0.0;
405 self.sum_sq = 0.0;
406 }
407
408 #[inline]
409 fn update(&mut self, value: f64) -> Option<f64> {
410 let len = self.values.len();
411 if self.count == len {
412 let old = self.values[self.head];
413 self.sum -= old;
414 self.sum_sq -= old * old;
415 } else {
416 self.count += 1;
417 }
418 self.values[self.head] = value;
419 self.head += 1;
420 if self.head == len {
421 self.head = 0;
422 }
423 self.sum += value;
424 self.sum_sq += value * value;
425 if self.count < len {
426 return None;
427 }
428 let mean = self.sum / len as f64;
429 Some((self.sum_sq / len as f64 - mean * mean).max(0.0).sqrt())
430 }
431}
432
433#[derive(Debug, Clone)]
434pub struct FibonacciEntryBandsStream {
435 params: ResolvedParams,
436 ema1: Option<f64>,
437 ema2: Option<f64>,
438 prev_basis: Option<f64>,
439 prev_prev_basis: Option<f64>,
440 trend: f64,
441 prev_close: Option<f64>,
442 atr_sum: f64,
443 atr_count: usize,
444 atr_value: Option<f64>,
445 stdev: RollingStdev,
446 prev_tp_long_band: Option<f64>,
447 prev_tp_short_band: Option<f64>,
448}
449
450impl FibonacciEntryBandsBuilder {
451 #[inline]
452 pub fn new() -> Self {
453 Self::default()
454 }
455
456 #[inline]
457 pub fn source(mut self, value: &str) -> Result<Self, FibonacciEntryBandsError> {
458 self.source = Some(parse_source(value)?);
459 Ok(self)
460 }
461
462 #[inline]
463 pub fn length(mut self, value: usize) -> Self {
464 self.length = Some(value);
465 self
466 }
467
468 #[inline]
469 pub fn atr_length(mut self, value: usize) -> Self {
470 self.atr_length = Some(value);
471 self
472 }
473
474 #[inline]
475 pub fn use_atr(mut self, value: bool) -> Self {
476 self.use_atr = Some(value);
477 self
478 }
479
480 #[inline]
481 pub fn tp_aggressiveness(mut self, value: &str) -> Result<Self, FibonacciEntryBandsError> {
482 self.tp_aggressiveness = Some(parse_tp_aggressiveness(value)?);
483 Ok(self)
484 }
485
486 #[inline]
487 pub fn kernel(mut self, kernel: Kernel) -> Self {
488 self.kernel = kernel;
489 self
490 }
491
492 #[inline]
493 pub fn apply(
494 self,
495 candles: &Candles,
496 ) -> Result<FibonacciEntryBandsOutput, FibonacciEntryBandsError> {
497 let input = FibonacciEntryBandsInput::from_candles(
498 candles,
499 FibonacciEntryBandsParams {
500 source: Some(self.source.unwrap_or(SourceKind::Hlc3).as_str().to_string()),
501 length: self.length,
502 atr_length: self.atr_length,
503 use_atr: self.use_atr,
504 tp_aggressiveness: Some(
505 self.tp_aggressiveness
506 .unwrap_or(TpAggressiveness::Low)
507 .as_str()
508 .to_string(),
509 ),
510 },
511 );
512 fibonacci_entry_bands_with_kernel(&input, self.kernel)
513 }
514
515 #[inline]
516 pub fn apply_slices(
517 self,
518 open: &[f64],
519 high: &[f64],
520 low: &[f64],
521 close: &[f64],
522 ) -> Result<FibonacciEntryBandsOutput, FibonacciEntryBandsError> {
523 let input = FibonacciEntryBandsInput::from_slices(
524 open,
525 high,
526 low,
527 close,
528 FibonacciEntryBandsParams {
529 source: Some(self.source.unwrap_or(SourceKind::Hlc3).as_str().to_string()),
530 length: self.length,
531 atr_length: self.atr_length,
532 use_atr: self.use_atr,
533 tp_aggressiveness: Some(
534 self.tp_aggressiveness
535 .unwrap_or(TpAggressiveness::Low)
536 .as_str()
537 .to_string(),
538 ),
539 },
540 );
541 fibonacci_entry_bands_with_kernel(&input, self.kernel)
542 }
543
544 #[inline]
545 pub fn into_stream(self) -> Result<FibonacciEntryBandsStream, FibonacciEntryBandsError> {
546 FibonacciEntryBandsStream::try_new(FibonacciEntryBandsParams {
547 source: Some(self.source.unwrap_or(SourceKind::Hlc3).as_str().to_string()),
548 length: self.length,
549 atr_length: self.atr_length,
550 use_atr: self.use_atr,
551 tp_aggressiveness: Some(
552 self.tp_aggressiveness
553 .unwrap_or(TpAggressiveness::Low)
554 .as_str()
555 .to_string(),
556 ),
557 })
558 }
559}
560
561impl FibonacciEntryBandsStream {
562 #[inline]
563 pub fn try_new(params: FibonacciEntryBandsParams) -> Result<Self, FibonacciEntryBandsError> {
564 let resolved = resolve_params(¶ms, None)?;
565 Ok(Self::new_resolved(resolved))
566 }
567
568 #[inline]
569 fn new_resolved(params: ResolvedParams) -> Self {
570 Self {
571 params,
572 ema1: None,
573 ema2: None,
574 prev_basis: None,
575 prev_prev_basis: None,
576 trend: 0.0,
577 prev_close: None,
578 atr_sum: 0.0,
579 atr_count: 0,
580 atr_value: None,
581 stdev: RollingStdev::new(params.length),
582 prev_tp_long_band: None,
583 prev_tp_short_band: None,
584 }
585 }
586
587 #[inline]
588 pub fn reset(&mut self) {
589 self.stdev.reset();
590 *self = Self::new_resolved(self.params);
591 }
592
593 #[inline]
594 pub fn get_warmup_period(&self) -> usize {
595 let vol_warmup = if self.params.use_atr {
596 self.params.atr_length.saturating_sub(1)
597 } else {
598 self.params.length.saturating_sub(1)
599 };
600 vol_warmup.max(2)
601 }
602
603 #[inline]
604 pub fn update(
605 &mut self,
606 open: f64,
607 high: f64,
608 low: f64,
609 close: f64,
610 ) -> Option<FibonacciEntryBandsPoint> {
611 if !valid_bar(self.params.source, open, high, low, close) {
612 self.reset();
613 return None;
614 }
615
616 let source = self.params.source.value(open, high, low, close);
617 let ema1 = match self.ema1 {
618 Some(prev) => prev + self.params.ema_alpha * (source - prev),
619 None => source,
620 };
621 self.ema1 = Some(ema1);
622 let basis = match self.ema2 {
623 Some(prev) => prev + self.params.ema_alpha * (ema1 - prev),
624 None => ema1,
625 };
626 self.ema2 = Some(basis);
627
628 let long_entry = if let (Some(prev_basis), Some(prev_prev_basis)) =
629 (self.prev_basis, self.prev_prev_basis)
630 {
631 let curr_delta = basis - prev_basis;
632 let prev_delta = prev_basis - prev_prev_basis;
633 if curr_delta > FLOAT_TOL && prev_delta <= FLOAT_TOL {
634 1.0
635 } else {
636 0.0
637 }
638 } else {
639 f64::NAN
640 };
641
642 let short_entry = if let (Some(prev_basis), Some(prev_prev_basis)) =
643 (self.prev_basis, self.prev_prev_basis)
644 {
645 let curr_delta = basis - prev_basis;
646 let prev_delta = prev_basis - prev_prev_basis;
647 if curr_delta < -FLOAT_TOL && prev_delta >= -FLOAT_TOL {
648 1.0
649 } else {
650 0.0
651 }
652 } else {
653 f64::NAN
654 };
655
656 if let Some(prev_basis) = self.prev_basis {
657 if basis > prev_basis + FLOAT_TOL {
658 self.trend = 1.0;
659 } else if basis < prev_basis - FLOAT_TOL {
660 self.trend = -1.0;
661 }
662 }
663
664 let vol = if self.params.use_atr {
665 self.update_atr(high, low, close)
666 } else {
667 self.stdev.update(source)
668 };
669
670 let mut point = FibonacciEntryBandsPoint {
671 basis,
672 trend: self.trend,
673 upper_0618: f64::NAN,
674 upper_1000: f64::NAN,
675 upper_1618: f64::NAN,
676 upper_2618: f64::NAN,
677 lower_0618: f64::NAN,
678 lower_1000: f64::NAN,
679 lower_1618: f64::NAN,
680 lower_2618: f64::NAN,
681 tp_long_band: f64::NAN,
682 tp_short_band: f64::NAN,
683 long_entry,
684 short_entry,
685 rejection_long: f64::NAN,
686 rejection_short: f64::NAN,
687 long_bounce: if self.trend > 0.0 {
688 if low < basis - FLOAT_TOL && close > basis + FLOAT_TOL && !matches_true(long_entry)
689 {
690 1.0
691 } else {
692 0.0
693 }
694 } else {
695 0.0
696 },
697 short_bounce: if self.trend < 0.0 {
698 if high > basis + FLOAT_TOL
699 && close < basis - FLOAT_TOL
700 && !matches_true(short_entry)
701 {
702 1.0
703 } else {
704 0.0
705 }
706 } else {
707 0.0
708 },
709 };
710
711 if let Some(volatility) = vol.filter(|v| v.is_finite()) {
712 point.upper_0618 = basis + volatility * MULT1;
713 point.upper_1000 = basis + volatility * MULT2;
714 point.upper_1618 = basis + volatility * MULT3;
715 point.upper_2618 = basis + volatility * MULT4;
716 point.lower_0618 = basis - volatility * MULT1;
717 point.lower_1000 = basis - volatility * MULT2;
718 point.lower_1618 = basis - volatility * MULT3;
719 point.lower_2618 = basis - volatility * MULT4;
720
721 let (tp_long_band, tp_short_band) = match self.params.tp_aggressiveness {
722 TpAggressiveness::Low => (point.lower_2618, point.upper_2618),
723 TpAggressiveness::Medium => (point.lower_1000, point.upper_1000),
724 TpAggressiveness::High => (point.lower_0618, point.upper_0618),
725 };
726 point.tp_long_band = tp_long_band;
727 point.tp_short_band = tp_short_band;
728
729 point.rejection_long = if self.trend < 0.0
730 && crossunder(close, tp_long_band, self.prev_close, self.prev_tp_long_band)
731 {
732 1.0
733 } else {
734 0.0
735 };
736
737 point.rejection_short = if self.trend > 0.0
738 && crossover(
739 close,
740 tp_short_band,
741 self.prev_close,
742 self.prev_tp_short_band,
743 ) {
744 1.0
745 } else {
746 0.0
747 };
748 }
749
750 self.prev_prev_basis = self.prev_basis;
751 self.prev_basis = Some(basis);
752 self.prev_close = Some(close);
753 self.prev_tp_long_band = finite_option(point.tp_long_band);
754 self.prev_tp_short_band = finite_option(point.tp_short_band);
755
756 Some(point)
757 }
758
759 #[inline]
760 fn update_atr(&mut self, high: f64, low: f64, close: f64) -> Option<f64> {
761 let tr = match self.prev_close {
762 Some(prev_close) => {
763 let hl = high - low;
764 let hc = (high - prev_close).abs();
765 let lc = (low - prev_close).abs();
766 hl.max(hc).max(lc)
767 }
768 None => high - low,
769 };
770 if self.atr_count < self.params.atr_length {
771 self.atr_count += 1;
772 self.atr_sum += tr;
773 if self.atr_count == self.params.atr_length {
774 self.atr_value = Some(self.atr_sum / self.params.atr_length as f64);
775 }
776 } else if let Some(prev) = self.atr_value {
777 self.atr_value = Some(
778 ((self.params.atr_length - 1) as f64 * prev + tr) / self.params.atr_length as f64,
779 );
780 }
781 self.atr_value
782 }
783}
784
785#[inline(always)]
786fn finite_option(value: f64) -> Option<f64> {
787 if value.is_finite() {
788 Some(value)
789 } else {
790 None
791 }
792}
793
794#[inline(always)]
795fn matches_true(value: f64) -> bool {
796 value.is_finite() && value > 0.5
797}
798
799#[inline(always)]
800fn crossover(current_a: f64, current_b: f64, prev_a: Option<f64>, prev_b: Option<f64>) -> bool {
801 current_a.is_finite()
802 && current_b.is_finite()
803 && current_a > current_b + FLOAT_TOL
804 && prev_a
805 .zip(prev_b)
806 .map_or(false, |(a, b)| a <= b + FLOAT_TOL)
807}
808
809#[inline(always)]
810fn crossunder(current_a: f64, current_b: f64, prev_a: Option<f64>, prev_b: Option<f64>) -> bool {
811 current_a.is_finite()
812 && current_b.is_finite()
813 && current_a < current_b - FLOAT_TOL
814 && prev_a
815 .zip(prev_b)
816 .map_or(false, |(a, b)| a >= b - FLOAT_TOL)
817}
818
819#[inline(always)]
820fn parse_source(value: &str) -> Result<SourceKind, FibonacciEntryBandsError> {
821 SourceKind::parse(value).ok_or_else(|| FibonacciEntryBandsError::InvalidSource {
822 source_name: value.to_string(),
823 })
824}
825
826#[inline(always)]
827fn parse_tp_aggressiveness(value: &str) -> Result<TpAggressiveness, FibonacciEntryBandsError> {
828 TpAggressiveness::parse(value).ok_or_else(|| {
829 FibonacciEntryBandsError::InvalidTpAggressiveness {
830 tp_aggressiveness: value.to_string(),
831 }
832 })
833}
834
835#[inline(always)]
836fn valid_bar(source: SourceKind, open: f64, high: f64, low: f64, close: f64) -> bool {
837 high.is_finite()
838 && low.is_finite()
839 && close.is_finite()
840 && (!source.needs_open() || open.is_finite())
841}
842
843#[inline(always)]
844fn first_valid_ohlc(
845 source: SourceKind,
846 open: &[f64],
847 high: &[f64],
848 low: &[f64],
849 close: &[f64],
850) -> usize {
851 let len = close.len();
852 let mut i = 0usize;
853 while i < len {
854 if valid_bar(source, open[i], high[i], low[i], close[i]) {
855 return i;
856 }
857 i += 1;
858 }
859 len
860}
861
862#[inline(always)]
863fn max_consecutive_valid_ohlc(
864 source: SourceKind,
865 open: &[f64],
866 high: &[f64],
867 low: &[f64],
868 close: &[f64],
869) -> usize {
870 let len = close.len();
871 let mut best = 0usize;
872 let mut run = 0usize;
873 let mut i = 0usize;
874 while i < len {
875 if valid_bar(source, open[i], high[i], low[i], close[i]) {
876 run += 1;
877 if run > best {
878 best = run;
879 }
880 } else {
881 run = 0;
882 }
883 i += 1;
884 }
885 best
886}
887
888#[inline]
889fn resolve_params(
890 params: &FibonacciEntryBandsParams,
891 data_len: Option<usize>,
892) -> Result<ResolvedParams, FibonacciEntryBandsError> {
893 let source = parse_source(params.source.as_deref().unwrap_or(DEFAULT_SOURCE))?;
894 let length = params.length.unwrap_or(DEFAULT_LENGTH);
895 let atr_length = params.atr_length.unwrap_or(DEFAULT_ATR_LENGTH);
896 let use_atr = params.use_atr.unwrap_or(DEFAULT_USE_ATR);
897 let tp_aggressiveness = parse_tp_aggressiveness(
898 params
899 .tp_aggressiveness
900 .as_deref()
901 .unwrap_or(DEFAULT_TP_AGGRESSIVENESS),
902 )?;
903 let data_len = data_len.unwrap_or(0);
904
905 if length == 0 || (data_len != 0 && length > data_len) {
906 return Err(FibonacciEntryBandsError::InvalidLength { length, data_len });
907 }
908 if atr_length == 0 || (data_len != 0 && atr_length > data_len) {
909 return Err(FibonacciEntryBandsError::InvalidAtrLength {
910 atr_length,
911 data_len,
912 });
913 }
914
915 Ok(ResolvedParams {
916 source,
917 length,
918 atr_length,
919 use_atr,
920 tp_aggressiveness,
921 ema_alpha: 2.0 / (length as f64 + 1.0),
922 })
923}
924
925#[inline(always)]
926fn full_nan_vec(len: usize) -> Vec<f64> {
927 alloc_with_nan_prefix(len, len)
928}
929
930#[inline(always)]
931#[allow(clippy::too_many_arguments)]
932fn write_point(
933 point: FibonacciEntryBandsPoint,
934 idx: usize,
935 basis: &mut [f64],
936 trend: &mut [f64],
937 upper_0618: &mut [f64],
938 upper_1000: &mut [f64],
939 upper_1618: &mut [f64],
940 upper_2618: &mut [f64],
941 lower_0618: &mut [f64],
942 lower_1000: &mut [f64],
943 lower_1618: &mut [f64],
944 lower_2618: &mut [f64],
945 tp_long_band: &mut [f64],
946 tp_short_band: &mut [f64],
947 long_entry: &mut [f64],
948 short_entry: &mut [f64],
949 rejection_long: &mut [f64],
950 rejection_short: &mut [f64],
951 long_bounce: &mut [f64],
952 short_bounce: &mut [f64],
953) {
954 basis[idx] = point.basis;
955 trend[idx] = point.trend;
956 upper_0618[idx] = point.upper_0618;
957 upper_1000[idx] = point.upper_1000;
958 upper_1618[idx] = point.upper_1618;
959 upper_2618[idx] = point.upper_2618;
960 lower_0618[idx] = point.lower_0618;
961 lower_1000[idx] = point.lower_1000;
962 lower_1618[idx] = point.lower_1618;
963 lower_2618[idx] = point.lower_2618;
964 tp_long_band[idx] = point.tp_long_band;
965 tp_short_band[idx] = point.tp_short_band;
966 long_entry[idx] = point.long_entry;
967 short_entry[idx] = point.short_entry;
968 rejection_long[idx] = point.rejection_long;
969 rejection_short[idx] = point.rejection_short;
970 long_bounce[idx] = point.long_bounce;
971 short_bounce[idx] = point.short_bounce;
972}
973
974#[inline]
975#[allow(clippy::too_many_arguments)]
976fn fibonacci_entry_bands_row_from_slices(
977 open: &[f64],
978 high: &[f64],
979 low: &[f64],
980 close: &[f64],
981 params: ResolvedParams,
982 basis: &mut [f64],
983 trend: &mut [f64],
984 upper_0618: &mut [f64],
985 upper_1000: &mut [f64],
986 upper_1618: &mut [f64],
987 upper_2618: &mut [f64],
988 lower_0618: &mut [f64],
989 lower_1000: &mut [f64],
990 lower_1618: &mut [f64],
991 lower_2618: &mut [f64],
992 tp_long_band: &mut [f64],
993 tp_short_band: &mut [f64],
994 long_entry: &mut [f64],
995 short_entry: &mut [f64],
996 rejection_long: &mut [f64],
997 rejection_short: &mut [f64],
998 long_bounce: &mut [f64],
999 short_bounce: &mut [f64],
1000) {
1001 let mut stream = FibonacciEntryBandsStream::new_resolved(params);
1002 for i in 0..close.len() {
1003 let point = stream
1004 .update(open[i], high[i], low[i], close[i])
1005 .unwrap_or_else(FibonacciEntryBandsPoint::nan);
1006 write_point(
1007 point,
1008 i,
1009 basis,
1010 trend,
1011 upper_0618,
1012 upper_1000,
1013 upper_1618,
1014 upper_2618,
1015 lower_0618,
1016 lower_1000,
1017 lower_1618,
1018 lower_2618,
1019 tp_long_band,
1020 tp_short_band,
1021 long_entry,
1022 short_entry,
1023 rejection_long,
1024 rejection_short,
1025 long_bounce,
1026 short_bounce,
1027 );
1028 }
1029}
1030
1031#[inline]
1032fn fibonacci_entry_bands_prepare<'a>(
1033 input: &'a FibonacciEntryBandsInput,
1034 kernel: Kernel,
1035) -> Result<
1036 (
1037 &'a [f64],
1038 &'a [f64],
1039 &'a [f64],
1040 &'a [f64],
1041 ResolvedParams,
1042 Kernel,
1043 ),
1044 FibonacciEntryBandsError,
1045> {
1046 let (open, high, low, close) = input.as_slices();
1047 if open.is_empty() || high.is_empty() || low.is_empty() || close.is_empty() {
1048 return Err(FibonacciEntryBandsError::EmptyInputData);
1049 }
1050 if open.len() != high.len() || open.len() != low.len() || open.len() != close.len() {
1051 return Err(FibonacciEntryBandsError::InconsistentSliceLengths {
1052 open_len: open.len(),
1053 high_len: high.len(),
1054 low_len: low.len(),
1055 close_len: close.len(),
1056 });
1057 }
1058
1059 let params = resolve_params(&input.params, Some(close.len()))?;
1060 let first = first_valid_ohlc(params.source, open, high, low, close);
1061 if first >= close.len() {
1062 return Err(FibonacciEntryBandsError::AllValuesNaN);
1063 }
1064
1065 let valid = max_consecutive_valid_ohlc(params.source, open, high, low, close);
1066 let needed = if params.use_atr {
1067 params.atr_length
1068 } else {
1069 params.length
1070 };
1071 if valid < needed {
1072 return Err(FibonacciEntryBandsError::NotEnoughValidData { needed, valid });
1073 }
1074
1075 let chosen = match kernel {
1076 Kernel::Auto => detect_best_kernel(),
1077 other => other.to_non_batch(),
1078 };
1079 Ok((open, high, low, close, params, chosen))
1080}
1081
1082#[inline]
1083pub fn fibonacci_entry_bands(
1084 input: &FibonacciEntryBandsInput,
1085) -> Result<FibonacciEntryBandsOutput, FibonacciEntryBandsError> {
1086 fibonacci_entry_bands_with_kernel(input, Kernel::Auto)
1087}
1088
1089#[inline]
1090pub fn fibonacci_entry_bands_with_kernel(
1091 input: &FibonacciEntryBandsInput,
1092 kernel: Kernel,
1093) -> Result<FibonacciEntryBandsOutput, FibonacciEntryBandsError> {
1094 let (open, high, low, close, params, _chosen) = fibonacci_entry_bands_prepare(input, kernel)?;
1095 let len = close.len();
1096 let mut out = FibonacciEntryBandsOutput {
1097 basis: full_nan_vec(len),
1098 trend: full_nan_vec(len),
1099 upper_0618: full_nan_vec(len),
1100 upper_1000: full_nan_vec(len),
1101 upper_1618: full_nan_vec(len),
1102 upper_2618: full_nan_vec(len),
1103 lower_0618: full_nan_vec(len),
1104 lower_1000: full_nan_vec(len),
1105 lower_1618: full_nan_vec(len),
1106 lower_2618: full_nan_vec(len),
1107 tp_long_band: full_nan_vec(len),
1108 tp_short_band: full_nan_vec(len),
1109 long_entry: full_nan_vec(len),
1110 short_entry: full_nan_vec(len),
1111 rejection_long: full_nan_vec(len),
1112 rejection_short: full_nan_vec(len),
1113 long_bounce: full_nan_vec(len),
1114 short_bounce: full_nan_vec(len),
1115 };
1116 fibonacci_entry_bands_row_from_slices(
1117 open,
1118 high,
1119 low,
1120 close,
1121 params,
1122 &mut out.basis,
1123 &mut out.trend,
1124 &mut out.upper_0618,
1125 &mut out.upper_1000,
1126 &mut out.upper_1618,
1127 &mut out.upper_2618,
1128 &mut out.lower_0618,
1129 &mut out.lower_1000,
1130 &mut out.lower_1618,
1131 &mut out.lower_2618,
1132 &mut out.tp_long_band,
1133 &mut out.tp_short_band,
1134 &mut out.long_entry,
1135 &mut out.short_entry,
1136 &mut out.rejection_long,
1137 &mut out.rejection_short,
1138 &mut out.long_bounce,
1139 &mut out.short_bounce,
1140 );
1141 Ok(out)
1142}
1143
1144#[inline]
1145#[allow(clippy::too_many_arguments)]
1146pub fn fibonacci_entry_bands_into_slices(
1147 basis: &mut [f64],
1148 trend: &mut [f64],
1149 upper_0618: &mut [f64],
1150 upper_1000: &mut [f64],
1151 upper_1618: &mut [f64],
1152 upper_2618: &mut [f64],
1153 lower_0618: &mut [f64],
1154 lower_1000: &mut [f64],
1155 lower_1618: &mut [f64],
1156 lower_2618: &mut [f64],
1157 tp_long_band: &mut [f64],
1158 tp_short_band: &mut [f64],
1159 long_entry: &mut [f64],
1160 short_entry: &mut [f64],
1161 rejection_long: &mut [f64],
1162 rejection_short: &mut [f64],
1163 long_bounce: &mut [f64],
1164 short_bounce: &mut [f64],
1165 input: &FibonacciEntryBandsInput,
1166 kernel: Kernel,
1167) -> Result<(), FibonacciEntryBandsError> {
1168 let expected = input.as_slices().3.len();
1169 if basis.len() != expected
1170 || trend.len() != expected
1171 || upper_0618.len() != expected
1172 || upper_1000.len() != expected
1173 || upper_1618.len() != expected
1174 || upper_2618.len() != expected
1175 || lower_0618.len() != expected
1176 || lower_1000.len() != expected
1177 || lower_1618.len() != expected
1178 || lower_2618.len() != expected
1179 || tp_long_band.len() != expected
1180 || tp_short_band.len() != expected
1181 || long_entry.len() != expected
1182 || short_entry.len() != expected
1183 || rejection_long.len() != expected
1184 || rejection_short.len() != expected
1185 || long_bounce.len() != expected
1186 || short_bounce.len() != expected
1187 {
1188 return Err(FibonacciEntryBandsError::OutputLengthMismatch { expected });
1189 }
1190
1191 let (open, high, low, close, params, _chosen) = fibonacci_entry_bands_prepare(input, kernel)?;
1192 fibonacci_entry_bands_row_from_slices(
1193 open,
1194 high,
1195 low,
1196 close,
1197 params,
1198 basis,
1199 trend,
1200 upper_0618,
1201 upper_1000,
1202 upper_1618,
1203 upper_2618,
1204 lower_0618,
1205 lower_1000,
1206 lower_1618,
1207 lower_2618,
1208 tp_long_band,
1209 tp_short_band,
1210 long_entry,
1211 short_entry,
1212 rejection_long,
1213 rejection_short,
1214 long_bounce,
1215 short_bounce,
1216 );
1217 Ok(())
1218}
1219
1220#[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
1221#[inline]
1222#[allow(clippy::too_many_arguments)]
1223pub fn fibonacci_entry_bands_into(
1224 input: &FibonacciEntryBandsInput,
1225 basis: &mut [f64],
1226 trend: &mut [f64],
1227 upper_0618: &mut [f64],
1228 upper_1000: &mut [f64],
1229 upper_1618: &mut [f64],
1230 upper_2618: &mut [f64],
1231 lower_0618: &mut [f64],
1232 lower_1000: &mut [f64],
1233 lower_1618: &mut [f64],
1234 lower_2618: &mut [f64],
1235 tp_long_band: &mut [f64],
1236 tp_short_band: &mut [f64],
1237 long_entry: &mut [f64],
1238 short_entry: &mut [f64],
1239 rejection_long: &mut [f64],
1240 rejection_short: &mut [f64],
1241 long_bounce: &mut [f64],
1242 short_bounce: &mut [f64],
1243) -> Result<(), FibonacciEntryBandsError> {
1244 fibonacci_entry_bands_into_slices(
1245 basis,
1246 trend,
1247 upper_0618,
1248 upper_1000,
1249 upper_1618,
1250 upper_2618,
1251 lower_0618,
1252 lower_1000,
1253 lower_1618,
1254 lower_2618,
1255 tp_long_band,
1256 tp_short_band,
1257 long_entry,
1258 short_entry,
1259 rejection_long,
1260 rejection_short,
1261 long_bounce,
1262 short_bounce,
1263 input,
1264 Kernel::Auto,
1265 )
1266}
1267
1268#[derive(Debug, Clone, PartialEq)]
1269#[cfg_attr(
1270 all(target_arch = "wasm32", feature = "wasm"),
1271 derive(Serialize, Deserialize)
1272)]
1273pub struct FibonacciEntryBandsBatchRange {
1274 pub length: (usize, usize, usize),
1275 pub atr_length: (usize, usize, usize),
1276 pub source: String,
1277 pub use_atr: bool,
1278 pub tp_aggressiveness: String,
1279}
1280
1281impl Default for FibonacciEntryBandsBatchRange {
1282 fn default() -> Self {
1283 Self {
1284 length: (DEFAULT_LENGTH, DEFAULT_LENGTH, 0),
1285 atr_length: (DEFAULT_ATR_LENGTH, DEFAULT_ATR_LENGTH, 0),
1286 source: DEFAULT_SOURCE.to_string(),
1287 use_atr: DEFAULT_USE_ATR,
1288 tp_aggressiveness: DEFAULT_TP_AGGRESSIVENESS.to_string(),
1289 }
1290 }
1291}
1292
1293#[derive(Debug, Clone)]
1294pub struct FibonacciEntryBandsBatchOutput {
1295 pub basis: Vec<f64>,
1296 pub trend: Vec<f64>,
1297 pub upper_0618: Vec<f64>,
1298 pub upper_1000: Vec<f64>,
1299 pub upper_1618: Vec<f64>,
1300 pub upper_2618: Vec<f64>,
1301 pub lower_0618: Vec<f64>,
1302 pub lower_1000: Vec<f64>,
1303 pub lower_1618: Vec<f64>,
1304 pub lower_2618: Vec<f64>,
1305 pub tp_long_band: Vec<f64>,
1306 pub tp_short_band: Vec<f64>,
1307 pub long_entry: Vec<f64>,
1308 pub short_entry: Vec<f64>,
1309 pub rejection_long: Vec<f64>,
1310 pub rejection_short: Vec<f64>,
1311 pub long_bounce: Vec<f64>,
1312 pub short_bounce: Vec<f64>,
1313 pub combos: Vec<FibonacciEntryBandsParams>,
1314 pub rows: usize,
1315 pub cols: usize,
1316}
1317
1318#[derive(Clone, Debug, Default)]
1319pub struct FibonacciEntryBandsBatchBuilder {
1320 range: FibonacciEntryBandsBatchRange,
1321 kernel: Kernel,
1322}
1323
1324impl FibonacciEntryBandsBatchBuilder {
1325 #[inline]
1326 pub fn new() -> Self {
1327 Self::default()
1328 }
1329
1330 #[inline]
1331 pub fn kernel(mut self, kernel: Kernel) -> Self {
1332 self.kernel = kernel;
1333 self
1334 }
1335
1336 #[inline]
1337 pub fn length_range(mut self, start: usize, end: usize, step: usize) -> Self {
1338 self.range.length = (start, end, step);
1339 self
1340 }
1341
1342 #[inline]
1343 pub fn atr_length_range(mut self, start: usize, end: usize, step: usize) -> Self {
1344 self.range.atr_length = (start, end, step);
1345 self
1346 }
1347
1348 #[inline]
1349 pub fn source(mut self, value: &str) -> Self {
1350 self.range.source = value.to_string();
1351 self
1352 }
1353
1354 #[inline]
1355 pub fn use_atr(mut self, value: bool) -> Self {
1356 self.range.use_atr = value;
1357 self
1358 }
1359
1360 #[inline]
1361 pub fn tp_aggressiveness(mut self, value: &str) -> Self {
1362 self.range.tp_aggressiveness = value.to_string();
1363 self
1364 }
1365
1366 #[inline]
1367 pub fn apply_slices(
1368 self,
1369 open: &[f64],
1370 high: &[f64],
1371 low: &[f64],
1372 close: &[f64],
1373 ) -> Result<FibonacciEntryBandsBatchOutput, FibonacciEntryBandsError> {
1374 fibonacci_entry_bands_batch_with_kernel(open, high, low, close, &self.range, self.kernel)
1375 }
1376
1377 #[inline]
1378 pub fn apply_candles(
1379 self,
1380 candles: &Candles,
1381 ) -> Result<FibonacciEntryBandsBatchOutput, FibonacciEntryBandsError> {
1382 fibonacci_entry_bands_batch_with_kernel(
1383 &candles.open,
1384 &candles.high,
1385 &candles.low,
1386 &candles.close,
1387 &self.range,
1388 self.kernel,
1389 )
1390 }
1391}
1392
1393#[inline]
1394fn expand_usize_range(
1395 name: &str,
1396 range: (usize, usize, usize),
1397) -> Result<Vec<usize>, FibonacciEntryBandsError> {
1398 let (start, end, step) = range;
1399 if start > end {
1400 return Err(FibonacciEntryBandsError::InvalidRange {
1401 start: format!("{name}={start}"),
1402 end: format!("{name}={end}"),
1403 step: format!("{name}={step}"),
1404 });
1405 }
1406 if start == end {
1407 return Ok(vec![start]);
1408 }
1409 if step == 0 {
1410 return Err(FibonacciEntryBandsError::InvalidRange {
1411 start: format!("{name}={start}"),
1412 end: format!("{name}={end}"),
1413 step: format!("{name}={step}"),
1414 });
1415 }
1416 let mut out = Vec::new();
1417 let mut value = start;
1418 while value <= end {
1419 out.push(value);
1420 match value.checked_add(step) {
1421 Some(next) if next > value => value = next,
1422 _ => break,
1423 }
1424 }
1425 Ok(out)
1426}
1427
1428#[inline]
1429fn expand_grid(
1430 sweep: &FibonacciEntryBandsBatchRange,
1431) -> Result<Vec<FibonacciEntryBandsParams>, FibonacciEntryBandsError> {
1432 let _ = parse_source(&sweep.source)?;
1433 let _ = parse_tp_aggressiveness(&sweep.tp_aggressiveness)?;
1434 let lengths = expand_usize_range("length", sweep.length)?;
1435 let atr_lengths = expand_usize_range("atr_length", sweep.atr_length)?;
1436 let mut combos = Vec::with_capacity(lengths.len().saturating_mul(atr_lengths.len()));
1437 for &length in &lengths {
1438 for &atr_length in &atr_lengths {
1439 combos.push(FibonacciEntryBandsParams {
1440 source: Some(sweep.source.clone()),
1441 length: Some(length),
1442 atr_length: Some(atr_length),
1443 use_atr: Some(sweep.use_atr),
1444 tp_aggressiveness: Some(sweep.tp_aggressiveness.clone()),
1445 });
1446 }
1447 }
1448 Ok(combos)
1449}
1450
1451#[inline(always)]
1452unsafe fn assume_init_vec(
1453 mut guard: ManuallyDrop<Vec<MaybeUninit<f64>>>,
1454 total: usize,
1455) -> Vec<f64> {
1456 Vec::from_raw_parts(guard.as_mut_ptr() as *mut f64, total, guard.capacity())
1457}
1458
1459#[inline]
1460fn validate_batch_kernel(kernel: Kernel) -> Result<Kernel, FibonacciEntryBandsError> {
1461 let chosen = match kernel {
1462 Kernel::Auto => detect_best_batch_kernel(),
1463 other => other,
1464 };
1465 match chosen {
1466 Kernel::Auto
1467 | Kernel::ScalarBatch
1468 | Kernel::Avx2Batch
1469 | Kernel::Avx512Batch
1470 | Kernel::Scalar
1471 | Kernel::Avx2
1472 | Kernel::Avx512 => Ok(chosen.to_non_batch()),
1473 other => Err(FibonacciEntryBandsError::InvalidKernelForBatch(other)),
1474 }
1475}
1476
1477#[inline]
1478#[allow(clippy::too_many_arguments)]
1479pub fn fibonacci_entry_bands_batch_with_kernel(
1480 open: &[f64],
1481 high: &[f64],
1482 low: &[f64],
1483 close: &[f64],
1484 sweep: &FibonacciEntryBandsBatchRange,
1485 kernel: Kernel,
1486) -> Result<FibonacciEntryBandsBatchOutput, FibonacciEntryBandsError> {
1487 let resolved = expand_grid(sweep)?;
1488 if open.is_empty() || high.is_empty() || low.is_empty() || close.is_empty() {
1489 return Err(FibonacciEntryBandsError::EmptyInputData);
1490 }
1491 if open.len() != high.len() || open.len() != low.len() || open.len() != close.len() {
1492 return Err(FibonacciEntryBandsError::InconsistentSliceLengths {
1493 open_len: open.len(),
1494 high_len: high.len(),
1495 low_len: low.len(),
1496 close_len: close.len(),
1497 });
1498 }
1499 let rows = resolved.len();
1500 let cols = close.len();
1501 let total = rows
1502 .checked_mul(cols)
1503 .ok_or(FibonacciEntryBandsError::OutputLengthMismatch {
1504 expected: usize::MAX,
1505 })?;
1506 let _kernel = validate_batch_kernel(kernel)?;
1507 let zero_prefixes = vec![0usize; rows];
1508
1509 macro_rules! alloc_matrix {
1510 ($name:ident, $guard:ident, $out:ident) => {
1511 let mut $name = make_uninit_matrix(rows, cols);
1512 init_matrix_prefixes(&mut $name, cols, &zero_prefixes);
1513 let mut $guard = ManuallyDrop::new($name);
1514 let $out =
1515 unsafe { std::slice::from_raw_parts_mut($guard.as_mut_ptr() as *mut f64, total) };
1516 };
1517 }
1518
1519 alloc_matrix!(basis_mu, basis_guard, basis_out);
1520 alloc_matrix!(trend_mu, trend_guard, trend_out);
1521 alloc_matrix!(upper_0618_mu, upper_0618_guard, upper_0618_out);
1522 alloc_matrix!(upper_1000_mu, upper_1000_guard, upper_1000_out);
1523 alloc_matrix!(upper_1618_mu, upper_1618_guard, upper_1618_out);
1524 alloc_matrix!(upper_2618_mu, upper_2618_guard, upper_2618_out);
1525 alloc_matrix!(lower_0618_mu, lower_0618_guard, lower_0618_out);
1526 alloc_matrix!(lower_1000_mu, lower_1000_guard, lower_1000_out);
1527 alloc_matrix!(lower_1618_mu, lower_1618_guard, lower_1618_out);
1528 alloc_matrix!(lower_2618_mu, lower_2618_guard, lower_2618_out);
1529 alloc_matrix!(tp_long_mu, tp_long_guard, tp_long_out);
1530 alloc_matrix!(tp_short_mu, tp_short_guard, tp_short_out);
1531 alloc_matrix!(long_entry_mu, long_entry_guard, long_entry_out);
1532 alloc_matrix!(short_entry_mu, short_entry_guard, short_entry_out);
1533 alloc_matrix!(rejection_long_mu, rejection_long_guard, rejection_long_out);
1534 alloc_matrix!(
1535 rejection_short_mu,
1536 rejection_short_guard,
1537 rejection_short_out
1538 );
1539 alloc_matrix!(long_bounce_mu, long_bounce_guard, long_bounce_out);
1540 alloc_matrix!(short_bounce_mu, short_bounce_guard, short_bounce_out);
1541
1542 #[cfg(not(target_arch = "wasm32"))]
1543 {
1544 let basis_ptr = basis_out.as_mut_ptr() as usize;
1545 let trend_ptr = trend_out.as_mut_ptr() as usize;
1546 let upper_0618_ptr = upper_0618_out.as_mut_ptr() as usize;
1547 let upper_1000_ptr = upper_1000_out.as_mut_ptr() as usize;
1548 let upper_1618_ptr = upper_1618_out.as_mut_ptr() as usize;
1549 let upper_2618_ptr = upper_2618_out.as_mut_ptr() as usize;
1550 let lower_0618_ptr = lower_0618_out.as_mut_ptr() as usize;
1551 let lower_1000_ptr = lower_1000_out.as_mut_ptr() as usize;
1552 let lower_1618_ptr = lower_1618_out.as_mut_ptr() as usize;
1553 let lower_2618_ptr = lower_2618_out.as_mut_ptr() as usize;
1554 let tp_long_ptr = tp_long_out.as_mut_ptr() as usize;
1555 let tp_short_ptr = tp_short_out.as_mut_ptr() as usize;
1556 let long_entry_ptr = long_entry_out.as_mut_ptr() as usize;
1557 let short_entry_ptr = short_entry_out.as_mut_ptr() as usize;
1558 let rejection_long_ptr = rejection_long_out.as_mut_ptr() as usize;
1559 let rejection_short_ptr = rejection_short_out.as_mut_ptr() as usize;
1560 let long_bounce_ptr = long_bounce_out.as_mut_ptr() as usize;
1561 let short_bounce_ptr = short_bounce_out.as_mut_ptr() as usize;
1562
1563 resolved
1564 .par_iter()
1565 .enumerate()
1566 .for_each(|(row, combo)| unsafe {
1567 let start = row * cols;
1568 let params = resolve_params(combo, Some(cols)).expect("validated");
1569 fibonacci_entry_bands_row_from_slices(
1570 open,
1571 high,
1572 low,
1573 close,
1574 params,
1575 &mut std::slice::from_raw_parts_mut(basis_ptr as *mut f64, total)
1576 [start..start + cols],
1577 &mut std::slice::from_raw_parts_mut(trend_ptr as *mut f64, total)
1578 [start..start + cols],
1579 &mut std::slice::from_raw_parts_mut(upper_0618_ptr as *mut f64, total)
1580 [start..start + cols],
1581 &mut std::slice::from_raw_parts_mut(upper_1000_ptr as *mut f64, total)
1582 [start..start + cols],
1583 &mut std::slice::from_raw_parts_mut(upper_1618_ptr as *mut f64, total)
1584 [start..start + cols],
1585 &mut std::slice::from_raw_parts_mut(upper_2618_ptr as *mut f64, total)
1586 [start..start + cols],
1587 &mut std::slice::from_raw_parts_mut(lower_0618_ptr as *mut f64, total)
1588 [start..start + cols],
1589 &mut std::slice::from_raw_parts_mut(lower_1000_ptr as *mut f64, total)
1590 [start..start + cols],
1591 &mut std::slice::from_raw_parts_mut(lower_1618_ptr as *mut f64, total)
1592 [start..start + cols],
1593 &mut std::slice::from_raw_parts_mut(lower_2618_ptr as *mut f64, total)
1594 [start..start + cols],
1595 &mut std::slice::from_raw_parts_mut(tp_long_ptr as *mut f64, total)
1596 [start..start + cols],
1597 &mut std::slice::from_raw_parts_mut(tp_short_ptr as *mut f64, total)
1598 [start..start + cols],
1599 &mut std::slice::from_raw_parts_mut(long_entry_ptr as *mut f64, total)
1600 [start..start + cols],
1601 &mut std::slice::from_raw_parts_mut(short_entry_ptr as *mut f64, total)
1602 [start..start + cols],
1603 &mut std::slice::from_raw_parts_mut(rejection_long_ptr as *mut f64, total)
1604 [start..start + cols],
1605 &mut std::slice::from_raw_parts_mut(rejection_short_ptr as *mut f64, total)
1606 [start..start + cols],
1607 &mut std::slice::from_raw_parts_mut(long_bounce_ptr as *mut f64, total)
1608 [start..start + cols],
1609 &mut std::slice::from_raw_parts_mut(short_bounce_ptr as *mut f64, total)
1610 [start..start + cols],
1611 );
1612 });
1613 }
1614
1615 #[cfg(target_arch = "wasm32")]
1616 {
1617 for (row, combo) in resolved.iter().enumerate() {
1618 let start = row * cols;
1619 let params = resolve_params(combo, Some(cols))?;
1620 fibonacci_entry_bands_row_from_slices(
1621 open,
1622 high,
1623 low,
1624 close,
1625 params,
1626 &mut basis_out[start..start + cols],
1627 &mut trend_out[start..start + cols],
1628 &mut upper_0618_out[start..start + cols],
1629 &mut upper_1000_out[start..start + cols],
1630 &mut upper_1618_out[start..start + cols],
1631 &mut upper_2618_out[start..start + cols],
1632 &mut lower_0618_out[start..start + cols],
1633 &mut lower_1000_out[start..start + cols],
1634 &mut lower_1618_out[start..start + cols],
1635 &mut lower_2618_out[start..start + cols],
1636 &mut tp_long_out[start..start + cols],
1637 &mut tp_short_out[start..start + cols],
1638 &mut long_entry_out[start..start + cols],
1639 &mut short_entry_out[start..start + cols],
1640 &mut rejection_long_out[start..start + cols],
1641 &mut rejection_short_out[start..start + cols],
1642 &mut long_bounce_out[start..start + cols],
1643 &mut short_bounce_out[start..start + cols],
1644 );
1645 }
1646 }
1647
1648 Ok(FibonacciEntryBandsBatchOutput {
1649 basis: unsafe { assume_init_vec(basis_guard, total) },
1650 trend: unsafe { assume_init_vec(trend_guard, total) },
1651 upper_0618: unsafe { assume_init_vec(upper_0618_guard, total) },
1652 upper_1000: unsafe { assume_init_vec(upper_1000_guard, total) },
1653 upper_1618: unsafe { assume_init_vec(upper_1618_guard, total) },
1654 upper_2618: unsafe { assume_init_vec(upper_2618_guard, total) },
1655 lower_0618: unsafe { assume_init_vec(lower_0618_guard, total) },
1656 lower_1000: unsafe { assume_init_vec(lower_1000_guard, total) },
1657 lower_1618: unsafe { assume_init_vec(lower_1618_guard, total) },
1658 lower_2618: unsafe { assume_init_vec(lower_2618_guard, total) },
1659 tp_long_band: unsafe { assume_init_vec(tp_long_guard, total) },
1660 tp_short_band: unsafe { assume_init_vec(tp_short_guard, total) },
1661 long_entry: unsafe { assume_init_vec(long_entry_guard, total) },
1662 short_entry: unsafe { assume_init_vec(short_entry_guard, total) },
1663 rejection_long: unsafe { assume_init_vec(rejection_long_guard, total) },
1664 rejection_short: unsafe { assume_init_vec(rejection_short_guard, total) },
1665 long_bounce: unsafe { assume_init_vec(long_bounce_guard, total) },
1666 short_bounce: unsafe { assume_init_vec(short_bounce_guard, total) },
1667 combos: resolved,
1668 rows,
1669 cols,
1670 })
1671}
1672
1673#[inline]
1674#[allow(clippy::too_many_arguments)]
1675pub fn fibonacci_entry_bands_batch_inner_into(
1676 open: &[f64],
1677 high: &[f64],
1678 low: &[f64],
1679 close: &[f64],
1680 sweep: &FibonacciEntryBandsBatchRange,
1681 kernel: Kernel,
1682 basis: &mut [f64],
1683 trend: &mut [f64],
1684 upper_0618: &mut [f64],
1685 upper_1000: &mut [f64],
1686 upper_1618: &mut [f64],
1687 upper_2618: &mut [f64],
1688 lower_0618: &mut [f64],
1689 lower_1000: &mut [f64],
1690 lower_1618: &mut [f64],
1691 lower_2618: &mut [f64],
1692 tp_long_band: &mut [f64],
1693 tp_short_band: &mut [f64],
1694 long_entry: &mut [f64],
1695 short_entry: &mut [f64],
1696 rejection_long: &mut [f64],
1697 rejection_short: &mut [f64],
1698 long_bounce: &mut [f64],
1699 short_bounce: &mut [f64],
1700) -> Result<Vec<FibonacciEntryBandsParams>, FibonacciEntryBandsError> {
1701 let out = fibonacci_entry_bands_batch_with_kernel(open, high, low, close, sweep, kernel)?;
1702 let total = out.rows * out.cols;
1703 if basis.len() != total
1704 || trend.len() != total
1705 || upper_0618.len() != total
1706 || upper_1000.len() != total
1707 || upper_1618.len() != total
1708 || upper_2618.len() != total
1709 || lower_0618.len() != total
1710 || lower_1000.len() != total
1711 || lower_1618.len() != total
1712 || lower_2618.len() != total
1713 || tp_long_band.len() != total
1714 || tp_short_band.len() != total
1715 || long_entry.len() != total
1716 || short_entry.len() != total
1717 || rejection_long.len() != total
1718 || rejection_short.len() != total
1719 || long_bounce.len() != total
1720 || short_bounce.len() != total
1721 {
1722 return Err(FibonacciEntryBandsError::OutputLengthMismatch { expected: total });
1723 }
1724
1725 basis.copy_from_slice(&out.basis);
1726 trend.copy_from_slice(&out.trend);
1727 upper_0618.copy_from_slice(&out.upper_0618);
1728 upper_1000.copy_from_slice(&out.upper_1000);
1729 upper_1618.copy_from_slice(&out.upper_1618);
1730 upper_2618.copy_from_slice(&out.upper_2618);
1731 lower_0618.copy_from_slice(&out.lower_0618);
1732 lower_1000.copy_from_slice(&out.lower_1000);
1733 lower_1618.copy_from_slice(&out.lower_1618);
1734 lower_2618.copy_from_slice(&out.lower_2618);
1735 tp_long_band.copy_from_slice(&out.tp_long_band);
1736 tp_short_band.copy_from_slice(&out.tp_short_band);
1737 long_entry.copy_from_slice(&out.long_entry);
1738 short_entry.copy_from_slice(&out.short_entry);
1739 rejection_long.copy_from_slice(&out.rejection_long);
1740 rejection_short.copy_from_slice(&out.rejection_short);
1741 long_bounce.copy_from_slice(&out.long_bounce);
1742 short_bounce.copy_from_slice(&out.short_bounce);
1743 Ok(out.combos)
1744}
1745
1746#[cfg(feature = "python")]
1747fn output_to_py_dict<'py>(
1748 py: Python<'py>,
1749 output: FibonacciEntryBandsOutput,
1750) -> PyResult<Bound<'py, PyDict>> {
1751 let dict = PyDict::new(py);
1752 dict.set_item("basis", output.basis.into_pyarray(py))?;
1753 dict.set_item("trend", output.trend.into_pyarray(py))?;
1754 dict.set_item("upper_0618", output.upper_0618.into_pyarray(py))?;
1755 dict.set_item("upper_1000", output.upper_1000.into_pyarray(py))?;
1756 dict.set_item("upper_1618", output.upper_1618.into_pyarray(py))?;
1757 dict.set_item("upper_2618", output.upper_2618.into_pyarray(py))?;
1758 dict.set_item("lower_0618", output.lower_0618.into_pyarray(py))?;
1759 dict.set_item("lower_1000", output.lower_1000.into_pyarray(py))?;
1760 dict.set_item("lower_1618", output.lower_1618.into_pyarray(py))?;
1761 dict.set_item("lower_2618", output.lower_2618.into_pyarray(py))?;
1762 dict.set_item("tp_long_band", output.tp_long_band.into_pyarray(py))?;
1763 dict.set_item("tp_short_band", output.tp_short_band.into_pyarray(py))?;
1764 dict.set_item("long_entry", output.long_entry.into_pyarray(py))?;
1765 dict.set_item("short_entry", output.short_entry.into_pyarray(py))?;
1766 dict.set_item("rejection_long", output.rejection_long.into_pyarray(py))?;
1767 dict.set_item("rejection_short", output.rejection_short.into_pyarray(py))?;
1768 dict.set_item("long_bounce", output.long_bounce.into_pyarray(py))?;
1769 dict.set_item("short_bounce", output.short_bounce.into_pyarray(py))?;
1770 Ok(dict)
1771}
1772
1773#[cfg(feature = "python")]
1774fn point_to_py_dict<'py>(
1775 py: Python<'py>,
1776 point: FibonacciEntryBandsPoint,
1777) -> PyResult<Bound<'py, PyDict>> {
1778 let dict = PyDict::new(py);
1779 dict.set_item("basis", point.basis)?;
1780 dict.set_item("trend", point.trend)?;
1781 dict.set_item("upper_0618", point.upper_0618)?;
1782 dict.set_item("upper_1000", point.upper_1000)?;
1783 dict.set_item("upper_1618", point.upper_1618)?;
1784 dict.set_item("upper_2618", point.upper_2618)?;
1785 dict.set_item("lower_0618", point.lower_0618)?;
1786 dict.set_item("lower_1000", point.lower_1000)?;
1787 dict.set_item("lower_1618", point.lower_1618)?;
1788 dict.set_item("lower_2618", point.lower_2618)?;
1789 dict.set_item("tp_long_band", point.tp_long_band)?;
1790 dict.set_item("tp_short_band", point.tp_short_band)?;
1791 dict.set_item("long_entry", point.long_entry)?;
1792 dict.set_item("short_entry", point.short_entry)?;
1793 dict.set_item("rejection_long", point.rejection_long)?;
1794 dict.set_item("rejection_short", point.rejection_short)?;
1795 dict.set_item("long_bounce", point.long_bounce)?;
1796 dict.set_item("short_bounce", point.short_bounce)?;
1797 Ok(dict)
1798}
1799
1800#[cfg(feature = "python")]
1801#[pyclass(name = "FibonacciEntryBandsStream")]
1802pub struct FibonacciEntryBandsStreamPy {
1803 stream: FibonacciEntryBandsStream,
1804}
1805
1806#[cfg(feature = "python")]
1807#[pymethods]
1808impl FibonacciEntryBandsStreamPy {
1809 #[new]
1810 #[pyo3(signature = (source=DEFAULT_SOURCE, length=DEFAULT_LENGTH, atr_length=DEFAULT_ATR_LENGTH, use_atr=DEFAULT_USE_ATR, tp_aggressiveness=DEFAULT_TP_AGGRESSIVENESS))]
1811 fn new(
1812 source: &str,
1813 length: usize,
1814 atr_length: usize,
1815 use_atr: bool,
1816 tp_aggressiveness: &str,
1817 ) -> PyResult<Self> {
1818 Ok(Self {
1819 stream: FibonacciEntryBandsStream::try_new(FibonacciEntryBandsParams {
1820 source: Some(source.to_string()),
1821 length: Some(length),
1822 atr_length: Some(atr_length),
1823 use_atr: Some(use_atr),
1824 tp_aggressiveness: Some(tp_aggressiveness.to_string()),
1825 })
1826 .map_err(|e| PyValueError::new_err(e.to_string()))?,
1827 })
1828 }
1829
1830 fn update<'py>(
1831 &mut self,
1832 py: Python<'py>,
1833 open: f64,
1834 high: f64,
1835 low: f64,
1836 close: f64,
1837 ) -> PyResult<Option<Bound<'py, PyDict>>> {
1838 self.stream
1839 .update(open, high, low, close)
1840 .map(|point| point_to_py_dict(py, point))
1841 .transpose()
1842 }
1843
1844 fn reset(&mut self) {
1845 self.stream.reset();
1846 }
1847
1848 #[getter]
1849 fn warmup_period(&self) -> usize {
1850 self.stream.get_warmup_period()
1851 }
1852}
1853
1854#[cfg(feature = "python")]
1855#[pyfunction(name = "fibonacci_entry_bands")]
1856#[pyo3(signature = (open, high, low, close, source=DEFAULT_SOURCE, length=DEFAULT_LENGTH, atr_length=DEFAULT_ATR_LENGTH, use_atr=DEFAULT_USE_ATR, tp_aggressiveness=DEFAULT_TP_AGGRESSIVENESS, kernel=None))]
1857pub fn fibonacci_entry_bands_py<'py>(
1858 py: Python<'py>,
1859 open: PyReadonlyArray1<'py, f64>,
1860 high: PyReadonlyArray1<'py, f64>,
1861 low: PyReadonlyArray1<'py, f64>,
1862 close: PyReadonlyArray1<'py, f64>,
1863 source: &str,
1864 length: usize,
1865 atr_length: usize,
1866 use_atr: bool,
1867 tp_aggressiveness: &str,
1868 kernel: Option<&str>,
1869) -> PyResult<Bound<'py, PyDict>> {
1870 let input = FibonacciEntryBandsInput::from_slices(
1871 open.as_slice()?,
1872 high.as_slice()?,
1873 low.as_slice()?,
1874 close.as_slice()?,
1875 FibonacciEntryBandsParams {
1876 source: Some(source.to_string()),
1877 length: Some(length),
1878 atr_length: Some(atr_length),
1879 use_atr: Some(use_atr),
1880 tp_aggressiveness: Some(tp_aggressiveness.to_string()),
1881 },
1882 );
1883 let kern = validate_kernel(kernel, false)?;
1884 let output = py
1885 .allow_threads(|| fibonacci_entry_bands_with_kernel(&input, kern))
1886 .map_err(|e| PyValueError::new_err(e.to_string()))?;
1887 output_to_py_dict(py, output)
1888}
1889
1890#[cfg(feature = "python")]
1891#[pyfunction(name = "fibonacci_entry_bands_batch")]
1892#[pyo3(signature = (open, high, low, close, length_range=(DEFAULT_LENGTH, DEFAULT_LENGTH, 0), atr_length_range=(DEFAULT_ATR_LENGTH, DEFAULT_ATR_LENGTH, 0), source=DEFAULT_SOURCE, use_atr=DEFAULT_USE_ATR, tp_aggressiveness=DEFAULT_TP_AGGRESSIVENESS, kernel=None))]
1893pub fn fibonacci_entry_bands_batch_py<'py>(
1894 py: Python<'py>,
1895 open: PyReadonlyArray1<'py, f64>,
1896 high: PyReadonlyArray1<'py, f64>,
1897 low: PyReadonlyArray1<'py, f64>,
1898 close: PyReadonlyArray1<'py, f64>,
1899 length_range: (usize, usize, usize),
1900 atr_length_range: (usize, usize, usize),
1901 source: &str,
1902 use_atr: bool,
1903 tp_aggressiveness: &str,
1904 kernel: Option<&str>,
1905) -> PyResult<Bound<'py, PyDict>> {
1906 let open = open.as_slice()?;
1907 let high = high.as_slice()?;
1908 let low = low.as_slice()?;
1909 let close = close.as_slice()?;
1910 let kern = validate_kernel(kernel, true)?;
1911 let sweep = FibonacciEntryBandsBatchRange {
1912 length: length_range,
1913 atr_length: atr_length_range,
1914 source: source.to_string(),
1915 use_atr,
1916 tp_aggressiveness: tp_aggressiveness.to_string(),
1917 };
1918 let combos = expand_grid(&sweep).map_err(|e| PyValueError::new_err(e.to_string()))?;
1919 let rows = combos.len();
1920 let cols = close.len();
1921 let total = rows
1922 .checked_mul(cols)
1923 .ok_or_else(|| PyValueError::new_err("rows*cols overflow"))?;
1924
1925 macro_rules! arr {
1926 ($name:ident) => {
1927 let $name = unsafe { PyArray1::<f64>::new(py, [total], false) };
1928 };
1929 }
1930
1931 arr!(basis_arr);
1932 arr!(trend_arr);
1933 arr!(upper_0618_arr);
1934 arr!(upper_1000_arr);
1935 arr!(upper_1618_arr);
1936 arr!(upper_2618_arr);
1937 arr!(lower_0618_arr);
1938 arr!(lower_1000_arr);
1939 arr!(lower_1618_arr);
1940 arr!(lower_2618_arr);
1941 arr!(tp_long_arr);
1942 arr!(tp_short_arr);
1943 arr!(long_entry_arr);
1944 arr!(short_entry_arr);
1945 arr!(rejection_long_arr);
1946 arr!(rejection_short_arr);
1947 arr!(long_bounce_arr);
1948 arr!(short_bounce_arr);
1949
1950 let basis_slice = unsafe { basis_arr.as_slice_mut()? };
1951 let trend_slice = unsafe { trend_arr.as_slice_mut()? };
1952 let upper_0618_slice = unsafe { upper_0618_arr.as_slice_mut()? };
1953 let upper_1000_slice = unsafe { upper_1000_arr.as_slice_mut()? };
1954 let upper_1618_slice = unsafe { upper_1618_arr.as_slice_mut()? };
1955 let upper_2618_slice = unsafe { upper_2618_arr.as_slice_mut()? };
1956 let lower_0618_slice = unsafe { lower_0618_arr.as_slice_mut()? };
1957 let lower_1000_slice = unsafe { lower_1000_arr.as_slice_mut()? };
1958 let lower_1618_slice = unsafe { lower_1618_arr.as_slice_mut()? };
1959 let lower_2618_slice = unsafe { lower_2618_arr.as_slice_mut()? };
1960 let tp_long_slice = unsafe { tp_long_arr.as_slice_mut()? };
1961 let tp_short_slice = unsafe { tp_short_arr.as_slice_mut()? };
1962 let long_entry_slice = unsafe { long_entry_arr.as_slice_mut()? };
1963 let short_entry_slice = unsafe { short_entry_arr.as_slice_mut()? };
1964 let rejection_long_slice = unsafe { rejection_long_arr.as_slice_mut()? };
1965 let rejection_short_slice = unsafe { rejection_short_arr.as_slice_mut()? };
1966 let long_bounce_slice = unsafe { long_bounce_arr.as_slice_mut()? };
1967 let short_bounce_slice = unsafe { short_bounce_arr.as_slice_mut()? };
1968
1969 let combos = py
1970 .allow_threads(|| {
1971 fibonacci_entry_bands_batch_inner_into(
1972 open,
1973 high,
1974 low,
1975 close,
1976 &sweep,
1977 kern,
1978 basis_slice,
1979 trend_slice,
1980 upper_0618_slice,
1981 upper_1000_slice,
1982 upper_1618_slice,
1983 upper_2618_slice,
1984 lower_0618_slice,
1985 lower_1000_slice,
1986 lower_1618_slice,
1987 lower_2618_slice,
1988 tp_long_slice,
1989 tp_short_slice,
1990 long_entry_slice,
1991 short_entry_slice,
1992 rejection_long_slice,
1993 rejection_short_slice,
1994 long_bounce_slice,
1995 short_bounce_slice,
1996 )
1997 })
1998 .map_err(|e| PyValueError::new_err(e.to_string()))?;
1999
2000 let dict = PyDict::new(py);
2001 dict.set_item("basis", basis_arr.reshape((rows, cols))?)?;
2002 dict.set_item("trend", trend_arr.reshape((rows, cols))?)?;
2003 dict.set_item("upper_0618", upper_0618_arr.reshape((rows, cols))?)?;
2004 dict.set_item("upper_1000", upper_1000_arr.reshape((rows, cols))?)?;
2005 dict.set_item("upper_1618", upper_1618_arr.reshape((rows, cols))?)?;
2006 dict.set_item("upper_2618", upper_2618_arr.reshape((rows, cols))?)?;
2007 dict.set_item("lower_0618", lower_0618_arr.reshape((rows, cols))?)?;
2008 dict.set_item("lower_1000", lower_1000_arr.reshape((rows, cols))?)?;
2009 dict.set_item("lower_1618", lower_1618_arr.reshape((rows, cols))?)?;
2010 dict.set_item("lower_2618", lower_2618_arr.reshape((rows, cols))?)?;
2011 dict.set_item("tp_long_band", tp_long_arr.reshape((rows, cols))?)?;
2012 dict.set_item("tp_short_band", tp_short_arr.reshape((rows, cols))?)?;
2013 dict.set_item("long_entry", long_entry_arr.reshape((rows, cols))?)?;
2014 dict.set_item("short_entry", short_entry_arr.reshape((rows, cols))?)?;
2015 dict.set_item("rejection_long", rejection_long_arr.reshape((rows, cols))?)?;
2016 dict.set_item(
2017 "rejection_short",
2018 rejection_short_arr.reshape((rows, cols))?,
2019 )?;
2020 dict.set_item("long_bounce", long_bounce_arr.reshape((rows, cols))?)?;
2021 dict.set_item("short_bounce", short_bounce_arr.reshape((rows, cols))?)?;
2022 dict.set_item(
2023 "lengths",
2024 combos
2025 .iter()
2026 .map(|combo| combo.length.unwrap_or(DEFAULT_LENGTH) as u64)
2027 .collect::<Vec<_>>()
2028 .into_pyarray(py),
2029 )?;
2030 dict.set_item(
2031 "atr_lengths",
2032 combos
2033 .iter()
2034 .map(|combo| combo.atr_length.unwrap_or(DEFAULT_ATR_LENGTH) as u64)
2035 .collect::<Vec<_>>()
2036 .into_pyarray(py),
2037 )?;
2038 dict.set_item(
2039 "sources",
2040 combos
2041 .iter()
2042 .map(|combo| combo.source.as_deref().unwrap_or(DEFAULT_SOURCE))
2043 .collect::<Vec<_>>(),
2044 )?;
2045 dict.set_item(
2046 "use_atr_flags",
2047 combos
2048 .iter()
2049 .map(|combo| combo.use_atr.unwrap_or(DEFAULT_USE_ATR))
2050 .collect::<Vec<_>>()
2051 .into_pyarray(py),
2052 )?;
2053 dict.set_item(
2054 "tp_aggressiveness_values",
2055 combos
2056 .iter()
2057 .map(|combo| {
2058 combo
2059 .tp_aggressiveness
2060 .clone()
2061 .unwrap_or_else(|| DEFAULT_TP_AGGRESSIVENESS.to_string())
2062 })
2063 .collect::<Vec<_>>(),
2064 )?;
2065 dict.set_item("rows", rows)?;
2066 dict.set_item("cols", cols)?;
2067 Ok(dict)
2068}
2069
2070#[cfg(feature = "python")]
2071pub fn register_fibonacci_entry_bands_module(
2072 module: &Bound<'_, pyo3::types::PyModule>,
2073) -> PyResult<()> {
2074 module.add_function(wrap_pyfunction!(fibonacci_entry_bands_py, module)?)?;
2075 module.add_function(wrap_pyfunction!(fibonacci_entry_bands_batch_py, module)?)?;
2076 module.add_class::<FibonacciEntryBandsStreamPy>()?;
2077 Ok(())
2078}
2079
2080#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2081#[derive(Serialize, Deserialize)]
2082pub struct FibonacciEntryBandsJsOutput {
2083 pub basis: Vec<f64>,
2084 pub trend: Vec<f64>,
2085 pub upper_0618: Vec<f64>,
2086 pub upper_1000: Vec<f64>,
2087 pub upper_1618: Vec<f64>,
2088 pub upper_2618: Vec<f64>,
2089 pub lower_0618: Vec<f64>,
2090 pub lower_1000: Vec<f64>,
2091 pub lower_1618: Vec<f64>,
2092 pub lower_2618: Vec<f64>,
2093 pub tp_long_band: Vec<f64>,
2094 pub tp_short_band: Vec<f64>,
2095 pub long_entry: Vec<f64>,
2096 pub short_entry: Vec<f64>,
2097 pub rejection_long: Vec<f64>,
2098 pub rejection_short: Vec<f64>,
2099 pub long_bounce: Vec<f64>,
2100 pub short_bounce: Vec<f64>,
2101}
2102
2103#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2104#[wasm_bindgen(js_name = "fibonacci_entry_bands_js")]
2105pub fn fibonacci_entry_bands_js(
2106 open: &[f64],
2107 high: &[f64],
2108 low: &[f64],
2109 close: &[f64],
2110 source: &str,
2111 length: usize,
2112 atr_length: usize,
2113 use_atr: bool,
2114 tp_aggressiveness: &str,
2115) -> Result<JsValue, JsValue> {
2116 let input = FibonacciEntryBandsInput::from_slices(
2117 open,
2118 high,
2119 low,
2120 close,
2121 FibonacciEntryBandsParams {
2122 source: Some(source.to_string()),
2123 length: Some(length),
2124 atr_length: Some(atr_length),
2125 use_atr: Some(use_atr),
2126 tp_aggressiveness: Some(tp_aggressiveness.to_string()),
2127 },
2128 );
2129 let out = fibonacci_entry_bands(&input).map_err(|e| JsValue::from_str(&e.to_string()))?;
2130 serde_wasm_bindgen::to_value(&FibonacciEntryBandsJsOutput {
2131 basis: out.basis,
2132 trend: out.trend,
2133 upper_0618: out.upper_0618,
2134 upper_1000: out.upper_1000,
2135 upper_1618: out.upper_1618,
2136 upper_2618: out.upper_2618,
2137 lower_0618: out.lower_0618,
2138 lower_1000: out.lower_1000,
2139 lower_1618: out.lower_1618,
2140 lower_2618: out.lower_2618,
2141 tp_long_band: out.tp_long_band,
2142 tp_short_band: out.tp_short_band,
2143 long_entry: out.long_entry,
2144 short_entry: out.short_entry,
2145 rejection_long: out.rejection_long,
2146 rejection_short: out.rejection_short,
2147 long_bounce: out.long_bounce,
2148 short_bounce: out.short_bounce,
2149 })
2150 .map_err(|e| JsValue::from_str(&e.to_string()))
2151}
2152
2153#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2154#[wasm_bindgen]
2155pub fn fibonacci_entry_bands_alloc(len: usize) -> *mut f64 {
2156 let mut vec = Vec::<f64>::with_capacity(len);
2157 let ptr = vec.as_mut_ptr();
2158 std::mem::forget(vec);
2159 ptr
2160}
2161
2162#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2163#[wasm_bindgen]
2164pub fn fibonacci_entry_bands_free(ptr: *mut f64, len: usize) {
2165 if !ptr.is_null() {
2166 unsafe {
2167 let _ = Vec::from_raw_parts(ptr, len, len);
2168 }
2169 }
2170}
2171
2172#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2173fn has_duplicate_ptrs(ptrs: &[usize]) -> bool {
2174 for i in 0..ptrs.len() {
2175 for j in (i + 1)..ptrs.len() {
2176 if ptrs[i] == ptrs[j] {
2177 return true;
2178 }
2179 }
2180 }
2181 false
2182}
2183
2184#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2185#[wasm_bindgen]
2186#[allow(clippy::too_many_arguments)]
2187pub fn fibonacci_entry_bands_into(
2188 open_ptr: *const f64,
2189 high_ptr: *const f64,
2190 low_ptr: *const f64,
2191 close_ptr: *const f64,
2192 basis_ptr: *mut f64,
2193 trend_ptr: *mut f64,
2194 upper_0618_ptr: *mut f64,
2195 upper_1000_ptr: *mut f64,
2196 upper_1618_ptr: *mut f64,
2197 upper_2618_ptr: *mut f64,
2198 lower_0618_ptr: *mut f64,
2199 lower_1000_ptr: *mut f64,
2200 lower_1618_ptr: *mut f64,
2201 lower_2618_ptr: *mut f64,
2202 tp_long_ptr: *mut f64,
2203 tp_short_ptr: *mut f64,
2204 long_entry_ptr: *mut f64,
2205 short_entry_ptr: *mut f64,
2206 rejection_long_ptr: *mut f64,
2207 rejection_short_ptr: *mut f64,
2208 long_bounce_ptr: *mut f64,
2209 short_bounce_ptr: *mut f64,
2210 len: usize,
2211 source: &str,
2212 length: usize,
2213 atr_length: usize,
2214 use_atr: bool,
2215 tp_aggressiveness: &str,
2216) -> Result<(), JsValue> {
2217 let ptrs = [
2218 open_ptr as usize,
2219 high_ptr as usize,
2220 low_ptr as usize,
2221 close_ptr as usize,
2222 basis_ptr as usize,
2223 trend_ptr as usize,
2224 upper_0618_ptr as usize,
2225 upper_1000_ptr as usize,
2226 upper_1618_ptr as usize,
2227 upper_2618_ptr as usize,
2228 lower_0618_ptr as usize,
2229 lower_1000_ptr as usize,
2230 lower_1618_ptr as usize,
2231 lower_2618_ptr as usize,
2232 tp_long_ptr as usize,
2233 tp_short_ptr as usize,
2234 long_entry_ptr as usize,
2235 short_entry_ptr as usize,
2236 rejection_long_ptr as usize,
2237 rejection_short_ptr as usize,
2238 long_bounce_ptr as usize,
2239 short_bounce_ptr as usize,
2240 ];
2241 if ptrs.iter().any(|ptr| *ptr == 0) {
2242 return Err(JsValue::from_str("Null pointer provided"));
2243 }
2244
2245 unsafe {
2246 let open = std::slice::from_raw_parts(open_ptr, len);
2247 let high = std::slice::from_raw_parts(high_ptr, len);
2248 let low = std::slice::from_raw_parts(low_ptr, len);
2249 let close = std::slice::from_raw_parts(close_ptr, len);
2250 let input = FibonacciEntryBandsInput::from_slices(
2251 open,
2252 high,
2253 low,
2254 close,
2255 FibonacciEntryBandsParams {
2256 source: Some(source.to_string()),
2257 length: Some(length),
2258 atr_length: Some(atr_length),
2259 use_atr: Some(use_atr),
2260 tp_aggressiveness: Some(tp_aggressiveness.to_string()),
2261 },
2262 );
2263 let output_ptrs = &ptrs[4..];
2264 let need_temp = output_ptrs.iter().any(|ptr| {
2265 *ptr == open_ptr as usize
2266 || *ptr == high_ptr as usize
2267 || *ptr == low_ptr as usize
2268 || *ptr == close_ptr as usize
2269 }) || has_duplicate_ptrs(output_ptrs);
2270
2271 if need_temp {
2272 let mut basis = vec![0.0; len];
2273 let mut trend = vec![0.0; len];
2274 let mut upper_0618 = vec![0.0; len];
2275 let mut upper_1000 = vec![0.0; len];
2276 let mut upper_1618 = vec![0.0; len];
2277 let mut upper_2618 = vec![0.0; len];
2278 let mut lower_0618 = vec![0.0; len];
2279 let mut lower_1000 = vec![0.0; len];
2280 let mut lower_1618 = vec![0.0; len];
2281 let mut lower_2618 = vec![0.0; len];
2282 let mut tp_long = vec![0.0; len];
2283 let mut tp_short = vec![0.0; len];
2284 let mut long_entry = vec![0.0; len];
2285 let mut short_entry = vec![0.0; len];
2286 let mut rejection_long = vec![0.0; len];
2287 let mut rejection_short = vec![0.0; len];
2288 let mut long_bounce = vec![0.0; len];
2289 let mut short_bounce = vec![0.0; len];
2290 fibonacci_entry_bands_into_slices(
2291 &mut basis,
2292 &mut trend,
2293 &mut upper_0618,
2294 &mut upper_1000,
2295 &mut upper_1618,
2296 &mut upper_2618,
2297 &mut lower_0618,
2298 &mut lower_1000,
2299 &mut lower_1618,
2300 &mut lower_2618,
2301 &mut tp_long,
2302 &mut tp_short,
2303 &mut long_entry,
2304 &mut short_entry,
2305 &mut rejection_long,
2306 &mut rejection_short,
2307 &mut long_bounce,
2308 &mut short_bounce,
2309 &input,
2310 Kernel::Auto,
2311 )
2312 .map_err(|e| JsValue::from_str(&e.to_string()))?;
2313 std::slice::from_raw_parts_mut(basis_ptr, len).copy_from_slice(&basis);
2314 std::slice::from_raw_parts_mut(trend_ptr, len).copy_from_slice(&trend);
2315 std::slice::from_raw_parts_mut(upper_0618_ptr, len).copy_from_slice(&upper_0618);
2316 std::slice::from_raw_parts_mut(upper_1000_ptr, len).copy_from_slice(&upper_1000);
2317 std::slice::from_raw_parts_mut(upper_1618_ptr, len).copy_from_slice(&upper_1618);
2318 std::slice::from_raw_parts_mut(upper_2618_ptr, len).copy_from_slice(&upper_2618);
2319 std::slice::from_raw_parts_mut(lower_0618_ptr, len).copy_from_slice(&lower_0618);
2320 std::slice::from_raw_parts_mut(lower_1000_ptr, len).copy_from_slice(&lower_1000);
2321 std::slice::from_raw_parts_mut(lower_1618_ptr, len).copy_from_slice(&lower_1618);
2322 std::slice::from_raw_parts_mut(lower_2618_ptr, len).copy_from_slice(&lower_2618);
2323 std::slice::from_raw_parts_mut(tp_long_ptr, len).copy_from_slice(&tp_long);
2324 std::slice::from_raw_parts_mut(tp_short_ptr, len).copy_from_slice(&tp_short);
2325 std::slice::from_raw_parts_mut(long_entry_ptr, len).copy_from_slice(&long_entry);
2326 std::slice::from_raw_parts_mut(short_entry_ptr, len).copy_from_slice(&short_entry);
2327 std::slice::from_raw_parts_mut(rejection_long_ptr, len)
2328 .copy_from_slice(&rejection_long);
2329 std::slice::from_raw_parts_mut(rejection_short_ptr, len)
2330 .copy_from_slice(&rejection_short);
2331 std::slice::from_raw_parts_mut(long_bounce_ptr, len).copy_from_slice(&long_bounce);
2332 std::slice::from_raw_parts_mut(short_bounce_ptr, len).copy_from_slice(&short_bounce);
2333 } else {
2334 fibonacci_entry_bands_into_slices(
2335 std::slice::from_raw_parts_mut(basis_ptr, len),
2336 std::slice::from_raw_parts_mut(trend_ptr, len),
2337 std::slice::from_raw_parts_mut(upper_0618_ptr, len),
2338 std::slice::from_raw_parts_mut(upper_1000_ptr, len),
2339 std::slice::from_raw_parts_mut(upper_1618_ptr, len),
2340 std::slice::from_raw_parts_mut(upper_2618_ptr, len),
2341 std::slice::from_raw_parts_mut(lower_0618_ptr, len),
2342 std::slice::from_raw_parts_mut(lower_1000_ptr, len),
2343 std::slice::from_raw_parts_mut(lower_1618_ptr, len),
2344 std::slice::from_raw_parts_mut(lower_2618_ptr, len),
2345 std::slice::from_raw_parts_mut(tp_long_ptr, len),
2346 std::slice::from_raw_parts_mut(tp_short_ptr, len),
2347 std::slice::from_raw_parts_mut(long_entry_ptr, len),
2348 std::slice::from_raw_parts_mut(short_entry_ptr, len),
2349 std::slice::from_raw_parts_mut(rejection_long_ptr, len),
2350 std::slice::from_raw_parts_mut(rejection_short_ptr, len),
2351 std::slice::from_raw_parts_mut(long_bounce_ptr, len),
2352 std::slice::from_raw_parts_mut(short_bounce_ptr, len),
2353 &input,
2354 Kernel::Auto,
2355 )
2356 .map_err(|e| JsValue::from_str(&e.to_string()))?;
2357 }
2358 }
2359 Ok(())
2360}
2361
2362#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2363#[derive(Serialize, Deserialize)]
2364pub struct FibonacciEntryBandsBatchJsConfig {
2365 pub length_range: Option<(usize, usize, usize)>,
2366 pub atr_length_range: Option<(usize, usize, usize)>,
2367 pub source: Option<String>,
2368 pub use_atr: Option<bool>,
2369 pub tp_aggressiveness: Option<String>,
2370}
2371
2372#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2373#[derive(Serialize, Deserialize)]
2374pub struct FibonacciEntryBandsBatchJsOutput {
2375 pub basis: Vec<f64>,
2376 pub trend: Vec<f64>,
2377 pub upper_0618: Vec<f64>,
2378 pub upper_1000: Vec<f64>,
2379 pub upper_1618: Vec<f64>,
2380 pub upper_2618: Vec<f64>,
2381 pub lower_0618: Vec<f64>,
2382 pub lower_1000: Vec<f64>,
2383 pub lower_1618: Vec<f64>,
2384 pub lower_2618: Vec<f64>,
2385 pub tp_long_band: Vec<f64>,
2386 pub tp_short_band: Vec<f64>,
2387 pub long_entry: Vec<f64>,
2388 pub short_entry: Vec<f64>,
2389 pub rejection_long: Vec<f64>,
2390 pub rejection_short: Vec<f64>,
2391 pub long_bounce: Vec<f64>,
2392 pub short_bounce: Vec<f64>,
2393 pub combos: Vec<FibonacciEntryBandsParams>,
2394 pub lengths: Vec<usize>,
2395 pub atr_lengths: Vec<usize>,
2396 pub sources: Vec<String>,
2397 pub use_atr_flags: Vec<bool>,
2398 pub tp_aggressiveness_values: Vec<String>,
2399 pub rows: usize,
2400 pub cols: usize,
2401}
2402
2403#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2404#[wasm_bindgen(js_name = "fibonacci_entry_bands_batch_js")]
2405pub fn fibonacci_entry_bands_batch_js(
2406 open: &[f64],
2407 high: &[f64],
2408 low: &[f64],
2409 close: &[f64],
2410 config: JsValue,
2411) -> Result<JsValue, JsValue> {
2412 let config: FibonacciEntryBandsBatchJsConfig = serde_wasm_bindgen::from_value(config)
2413 .map_err(|e| JsValue::from_str(&format!("Invalid config: {e}")))?;
2414 let sweep = FibonacciEntryBandsBatchRange {
2415 length: config
2416 .length_range
2417 .unwrap_or((DEFAULT_LENGTH, DEFAULT_LENGTH, 0)),
2418 atr_length: config
2419 .atr_length_range
2420 .unwrap_or((DEFAULT_ATR_LENGTH, DEFAULT_ATR_LENGTH, 0)),
2421 source: config.source.unwrap_or_else(|| DEFAULT_SOURCE.to_string()),
2422 use_atr: config.use_atr.unwrap_or(DEFAULT_USE_ATR),
2423 tp_aggressiveness: config
2424 .tp_aggressiveness
2425 .unwrap_or_else(|| DEFAULT_TP_AGGRESSIVENESS.to_string()),
2426 };
2427 let out = fibonacci_entry_bands_batch_with_kernel(open, high, low, close, &sweep, Kernel::Auto)
2428 .map_err(|e| JsValue::from_str(&e.to_string()))?;
2429 serde_wasm_bindgen::to_value(&FibonacciEntryBandsBatchJsOutput {
2430 lengths: out
2431 .combos
2432 .iter()
2433 .map(|combo| combo.length.unwrap_or(DEFAULT_LENGTH))
2434 .collect(),
2435 atr_lengths: out
2436 .combos
2437 .iter()
2438 .map(|combo| combo.atr_length.unwrap_or(DEFAULT_ATR_LENGTH))
2439 .collect(),
2440 sources: out
2441 .combos
2442 .iter()
2443 .map(|combo| {
2444 combo
2445 .source
2446 .clone()
2447 .unwrap_or_else(|| DEFAULT_SOURCE.to_string())
2448 })
2449 .collect(),
2450 use_atr_flags: out
2451 .combos
2452 .iter()
2453 .map(|combo| combo.use_atr.unwrap_or(DEFAULT_USE_ATR))
2454 .collect(),
2455 tp_aggressiveness_values: out
2456 .combos
2457 .iter()
2458 .map(|combo| {
2459 combo
2460 .tp_aggressiveness
2461 .clone()
2462 .unwrap_or_else(|| DEFAULT_TP_AGGRESSIVENESS.to_string())
2463 })
2464 .collect(),
2465 basis: out.basis,
2466 trend: out.trend,
2467 upper_0618: out.upper_0618,
2468 upper_1000: out.upper_1000,
2469 upper_1618: out.upper_1618,
2470 upper_2618: out.upper_2618,
2471 lower_0618: out.lower_0618,
2472 lower_1000: out.lower_1000,
2473 lower_1618: out.lower_1618,
2474 lower_2618: out.lower_2618,
2475 tp_long_band: out.tp_long_band,
2476 tp_short_band: out.tp_short_band,
2477 long_entry: out.long_entry,
2478 short_entry: out.short_entry,
2479 rejection_long: out.rejection_long,
2480 rejection_short: out.rejection_short,
2481 long_bounce: out.long_bounce,
2482 short_bounce: out.short_bounce,
2483 combos: out.combos,
2484 rows: out.rows,
2485 cols: out.cols,
2486 })
2487 .map_err(|e| JsValue::from_str(&e.to_string()))
2488}
2489
2490#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
2491#[wasm_bindgen]
2492#[allow(clippy::too_many_arguments)]
2493pub fn fibonacci_entry_bands_batch_into(
2494 open_ptr: *const f64,
2495 high_ptr: *const f64,
2496 low_ptr: *const f64,
2497 close_ptr: *const f64,
2498 basis_ptr: *mut f64,
2499 trend_ptr: *mut f64,
2500 upper_0618_ptr: *mut f64,
2501 upper_1000_ptr: *mut f64,
2502 upper_1618_ptr: *mut f64,
2503 upper_2618_ptr: *mut f64,
2504 lower_0618_ptr: *mut f64,
2505 lower_1000_ptr: *mut f64,
2506 lower_1618_ptr: *mut f64,
2507 lower_2618_ptr: *mut f64,
2508 tp_long_ptr: *mut f64,
2509 tp_short_ptr: *mut f64,
2510 long_entry_ptr: *mut f64,
2511 short_entry_ptr: *mut f64,
2512 rejection_long_ptr: *mut f64,
2513 rejection_short_ptr: *mut f64,
2514 long_bounce_ptr: *mut f64,
2515 short_bounce_ptr: *mut f64,
2516 len: usize,
2517 length_start: usize,
2518 length_end: usize,
2519 length_step: usize,
2520 atr_length_start: usize,
2521 atr_length_end: usize,
2522 atr_length_step: usize,
2523 source: &str,
2524 use_atr: bool,
2525 tp_aggressiveness: &str,
2526) -> Result<usize, JsValue> {
2527 let ptrs = [
2528 open_ptr as usize,
2529 high_ptr as usize,
2530 low_ptr as usize,
2531 close_ptr as usize,
2532 basis_ptr as usize,
2533 trend_ptr as usize,
2534 upper_0618_ptr as usize,
2535 upper_1000_ptr as usize,
2536 upper_1618_ptr as usize,
2537 upper_2618_ptr as usize,
2538 lower_0618_ptr as usize,
2539 lower_1000_ptr as usize,
2540 lower_1618_ptr as usize,
2541 lower_2618_ptr as usize,
2542 tp_long_ptr as usize,
2543 tp_short_ptr as usize,
2544 long_entry_ptr as usize,
2545 short_entry_ptr as usize,
2546 rejection_long_ptr as usize,
2547 rejection_short_ptr as usize,
2548 long_bounce_ptr as usize,
2549 short_bounce_ptr as usize,
2550 ];
2551 if ptrs.iter().any(|ptr| *ptr == 0) {
2552 return Err(JsValue::from_str("Null pointer provided"));
2553 }
2554
2555 let sweep = FibonacciEntryBandsBatchRange {
2556 length: (length_start, length_end, length_step),
2557 atr_length: (atr_length_start, atr_length_end, atr_length_step),
2558 source: source.to_string(),
2559 use_atr,
2560 tp_aggressiveness: tp_aggressiveness.to_string(),
2561 };
2562 let rows = expand_grid(&sweep)
2563 .map_err(|e| JsValue::from_str(&e.to_string()))?
2564 .len();
2565 let total = rows
2566 .checked_mul(len)
2567 .ok_or_else(|| JsValue::from_str("rows*cols overflow"))?;
2568
2569 unsafe {
2570 let open = std::slice::from_raw_parts(open_ptr, len);
2571 let high = std::slice::from_raw_parts(high_ptr, len);
2572 let low = std::slice::from_raw_parts(low_ptr, len);
2573 let close = std::slice::from_raw_parts(close_ptr, len);
2574 let output_ptrs = &ptrs[4..];
2575 let need_temp = output_ptrs.iter().any(|ptr| {
2576 *ptr == open_ptr as usize
2577 || *ptr == high_ptr as usize
2578 || *ptr == low_ptr as usize
2579 || *ptr == close_ptr as usize
2580 }) || has_duplicate_ptrs(output_ptrs);
2581
2582 if need_temp {
2583 let mut basis = vec![0.0; total];
2584 let mut trend = vec![0.0; total];
2585 let mut upper_0618 = vec![0.0; total];
2586 let mut upper_1000 = vec![0.0; total];
2587 let mut upper_1618 = vec![0.0; total];
2588 let mut upper_2618 = vec![0.0; total];
2589 let mut lower_0618 = vec![0.0; total];
2590 let mut lower_1000 = vec![0.0; total];
2591 let mut lower_1618 = vec![0.0; total];
2592 let mut lower_2618 = vec![0.0; total];
2593 let mut tp_long = vec![0.0; total];
2594 let mut tp_short = vec![0.0; total];
2595 let mut long_entry = vec![0.0; total];
2596 let mut short_entry = vec![0.0; total];
2597 let mut rejection_long = vec![0.0; total];
2598 let mut rejection_short = vec![0.0; total];
2599 let mut long_bounce = vec![0.0; total];
2600 let mut short_bounce = vec![0.0; total];
2601 let rows = fibonacci_entry_bands_batch_inner_into(
2602 open,
2603 high,
2604 low,
2605 close,
2606 &sweep,
2607 Kernel::Auto,
2608 &mut basis,
2609 &mut trend,
2610 &mut upper_0618,
2611 &mut upper_1000,
2612 &mut upper_1618,
2613 &mut upper_2618,
2614 &mut lower_0618,
2615 &mut lower_1000,
2616 &mut lower_1618,
2617 &mut lower_2618,
2618 &mut tp_long,
2619 &mut tp_short,
2620 &mut long_entry,
2621 &mut short_entry,
2622 &mut rejection_long,
2623 &mut rejection_short,
2624 &mut long_bounce,
2625 &mut short_bounce,
2626 )
2627 .map_err(|e| JsValue::from_str(&e.to_string()))?
2628 .len();
2629 std::slice::from_raw_parts_mut(basis_ptr, total).copy_from_slice(&basis);
2630 std::slice::from_raw_parts_mut(trend_ptr, total).copy_from_slice(&trend);
2631 std::slice::from_raw_parts_mut(upper_0618_ptr, total).copy_from_slice(&upper_0618);
2632 std::slice::from_raw_parts_mut(upper_1000_ptr, total).copy_from_slice(&upper_1000);
2633 std::slice::from_raw_parts_mut(upper_1618_ptr, total).copy_from_slice(&upper_1618);
2634 std::slice::from_raw_parts_mut(upper_2618_ptr, total).copy_from_slice(&upper_2618);
2635 std::slice::from_raw_parts_mut(lower_0618_ptr, total).copy_from_slice(&lower_0618);
2636 std::slice::from_raw_parts_mut(lower_1000_ptr, total).copy_from_slice(&lower_1000);
2637 std::slice::from_raw_parts_mut(lower_1618_ptr, total).copy_from_slice(&lower_1618);
2638 std::slice::from_raw_parts_mut(lower_2618_ptr, total).copy_from_slice(&lower_2618);
2639 std::slice::from_raw_parts_mut(tp_long_ptr, total).copy_from_slice(&tp_long);
2640 std::slice::from_raw_parts_mut(tp_short_ptr, total).copy_from_slice(&tp_short);
2641 std::slice::from_raw_parts_mut(long_entry_ptr, total).copy_from_slice(&long_entry);
2642 std::slice::from_raw_parts_mut(short_entry_ptr, total).copy_from_slice(&short_entry);
2643 std::slice::from_raw_parts_mut(rejection_long_ptr, total)
2644 .copy_from_slice(&rejection_long);
2645 std::slice::from_raw_parts_mut(rejection_short_ptr, total)
2646 .copy_from_slice(&rejection_short);
2647 std::slice::from_raw_parts_mut(long_bounce_ptr, total).copy_from_slice(&long_bounce);
2648 std::slice::from_raw_parts_mut(short_bounce_ptr, total).copy_from_slice(&short_bounce);
2649 Ok(rows)
2650 } else {
2651 let rows = fibonacci_entry_bands_batch_inner_into(
2652 open,
2653 high,
2654 low,
2655 close,
2656 &sweep,
2657 Kernel::Auto,
2658 std::slice::from_raw_parts_mut(basis_ptr, total),
2659 std::slice::from_raw_parts_mut(trend_ptr, total),
2660 std::slice::from_raw_parts_mut(upper_0618_ptr, total),
2661 std::slice::from_raw_parts_mut(upper_1000_ptr, total),
2662 std::slice::from_raw_parts_mut(upper_1618_ptr, total),
2663 std::slice::from_raw_parts_mut(upper_2618_ptr, total),
2664 std::slice::from_raw_parts_mut(lower_0618_ptr, total),
2665 std::slice::from_raw_parts_mut(lower_1000_ptr, total),
2666 std::slice::from_raw_parts_mut(lower_1618_ptr, total),
2667 std::slice::from_raw_parts_mut(lower_2618_ptr, total),
2668 std::slice::from_raw_parts_mut(tp_long_ptr, total),
2669 std::slice::from_raw_parts_mut(tp_short_ptr, total),
2670 std::slice::from_raw_parts_mut(long_entry_ptr, total),
2671 std::slice::from_raw_parts_mut(short_entry_ptr, total),
2672 std::slice::from_raw_parts_mut(rejection_long_ptr, total),
2673 std::slice::from_raw_parts_mut(rejection_short_ptr, total),
2674 std::slice::from_raw_parts_mut(long_bounce_ptr, total),
2675 std::slice::from_raw_parts_mut(short_bounce_ptr, total),
2676 )
2677 .map_err(|e| JsValue::from_str(&e.to_string()))?
2678 .len();
2679 Ok(rows)
2680 }
2681 }
2682}
2683
2684#[cfg(test)]
2685mod tests {
2686 use super::*;
2687
2688 fn sample_ohlc(len: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
2689 let open: Vec<f64> = (0..len)
2690 .map(|i| {
2691 let x = i as f64;
2692 100.0 + x * 0.05 + (x * 0.09).sin() * 1.8 + (x * 0.021).cos() * 0.7
2693 })
2694 .collect();
2695 let close: Vec<f64> = open
2696 .iter()
2697 .enumerate()
2698 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.9)
2699 .collect();
2700 let high: Vec<f64> = open
2701 .iter()
2702 .zip(close.iter())
2703 .enumerate()
2704 .map(|(i, (&o, &c))| o.max(c) + 0.7 + (i as f64 * 0.05).sin().abs() * 0.2)
2705 .collect();
2706 let low: Vec<f64> = open
2707 .iter()
2708 .zip(close.iter())
2709 .enumerate()
2710 .map(|(i, (&o, &c))| o.min(c) - 0.7 - (i as f64 * 0.04).cos().abs() * 0.2)
2711 .collect();
2712 (open, high, low, close)
2713 }
2714
2715 fn assert_series_eq(left: &[f64], right: &[f64], tol: f64) {
2716 assert_eq!(left.len(), right.len());
2717 for (&lhs, &rhs) in left.iter().zip(right.iter()) {
2718 assert!(
2719 (lhs.is_nan() && rhs.is_nan()) || (lhs - rhs).abs() <= tol,
2720 "series mismatch: left={lhs:?}, right={rhs:?}"
2721 );
2722 }
2723 }
2724
2725 #[test]
2726 fn fibonacci_entry_bands_output_contract() {
2727 let (open, high, low, close) = sample_ohlc(320);
2728 let out = fibonacci_entry_bands(&FibonacciEntryBandsInput::from_slices(
2729 &open,
2730 &high,
2731 &low,
2732 &close,
2733 FibonacciEntryBandsParams::default(),
2734 ))
2735 .unwrap();
2736 assert_eq!(out.basis.len(), close.len());
2737 assert_eq!(out.upper_2618.len(), close.len());
2738 assert_eq!(out.long_entry.len(), close.len());
2739 assert!(out.basis.iter().any(|v| v.is_finite()));
2740 }
2741
2742 #[test]
2743 fn fibonacci_entry_bands_rejects_invalid_params() {
2744 let (open, high, low, close) = sample_ohlc(32);
2745 let err = fibonacci_entry_bands(&FibonacciEntryBandsInput::from_slices(
2746 &open,
2747 &high,
2748 &low,
2749 &close,
2750 FibonacciEntryBandsParams {
2751 source: Some("bad".to_string()),
2752 ..FibonacciEntryBandsParams::default()
2753 },
2754 ))
2755 .unwrap_err();
2756 assert!(matches!(
2757 err,
2758 FibonacciEntryBandsError::InvalidSource { .. }
2759 ));
2760
2761 let err = fibonacci_entry_bands(&FibonacciEntryBandsInput::from_slices(
2762 &open,
2763 &high,
2764 &low,
2765 &close,
2766 FibonacciEntryBandsParams {
2767 length: Some(0),
2768 ..FibonacciEntryBandsParams::default()
2769 },
2770 ))
2771 .unwrap_err();
2772 assert!(matches!(
2773 err,
2774 FibonacciEntryBandsError::InvalidLength { .. }
2775 ));
2776 }
2777
2778 #[test]
2779 fn fibonacci_entry_bands_stream_matches_batch_with_reset() {
2780 let (mut open, mut high, mut low, mut close) = sample_ohlc(220);
2781 open[110] = f64::NAN;
2782 high[110] = f64::NAN;
2783 low[110] = f64::NAN;
2784 close[110] = f64::NAN;
2785
2786 let params = FibonacciEntryBandsParams {
2787 source: Some("hlc3".to_string()),
2788 length: Some(16),
2789 atr_length: Some(11),
2790 use_atr: Some(true),
2791 tp_aggressiveness: Some("high".to_string()),
2792 };
2793 let batch = fibonacci_entry_bands(&FibonacciEntryBandsInput::from_slices(
2794 &open,
2795 &high,
2796 &low,
2797 &close,
2798 params.clone(),
2799 ))
2800 .unwrap();
2801 let mut stream = FibonacciEntryBandsStream::try_new(params).unwrap();
2802
2803 let mut basis = Vec::with_capacity(close.len());
2804 let mut trend = Vec::with_capacity(close.len());
2805 let mut tp_long = Vec::with_capacity(close.len());
2806 for i in 0..close.len() {
2807 if let Some(point) = stream.update(open[i], high[i], low[i], close[i]) {
2808 basis.push(point.basis);
2809 trend.push(point.trend);
2810 tp_long.push(point.tp_long_band);
2811 } else {
2812 basis.push(f64::NAN);
2813 trend.push(f64::NAN);
2814 tp_long.push(f64::NAN);
2815 }
2816 }
2817
2818 assert_series_eq(&basis, &batch.basis, 1e-12);
2819 assert_series_eq(&trend, &batch.trend, 1e-12);
2820 assert_series_eq(&tp_long, &batch.tp_long_band, 1e-12);
2821 }
2822
2823 #[test]
2824 fn fibonacci_entry_bands_into_matches_api() {
2825 let (open, high, low, close) = sample_ohlc(192);
2826 let input = FibonacciEntryBandsInput::from_slices(
2827 &open,
2828 &high,
2829 &low,
2830 &close,
2831 FibonacciEntryBandsParams {
2832 source: Some("hlc3".to_string()),
2833 length: Some(14),
2834 atr_length: Some(9),
2835 use_atr: Some(false),
2836 tp_aggressiveness: Some("low".to_string()),
2837 },
2838 );
2839 let direct = fibonacci_entry_bands(&input).unwrap();
2840 let mut basis = vec![0.0; close.len()];
2841 let mut trend = vec![0.0; close.len()];
2842 let mut upper_0618 = vec![0.0; close.len()];
2843 let mut upper_1000 = vec![0.0; close.len()];
2844 let mut upper_1618 = vec![0.0; close.len()];
2845 let mut upper_2618 = vec![0.0; close.len()];
2846 let mut lower_0618 = vec![0.0; close.len()];
2847 let mut lower_1000 = vec![0.0; close.len()];
2848 let mut lower_1618 = vec![0.0; close.len()];
2849 let mut lower_2618 = vec![0.0; close.len()];
2850 let mut tp_long = vec![0.0; close.len()];
2851 let mut tp_short = vec![0.0; close.len()];
2852 let mut long_entry = vec![0.0; close.len()];
2853 let mut short_entry = vec![0.0; close.len()];
2854 let mut rejection_long = vec![0.0; close.len()];
2855 let mut rejection_short = vec![0.0; close.len()];
2856 let mut long_bounce = vec![0.0; close.len()];
2857 let mut short_bounce = vec![0.0; close.len()];
2858
2859 fibonacci_entry_bands_into(
2860 &input,
2861 &mut basis,
2862 &mut trend,
2863 &mut upper_0618,
2864 &mut upper_1000,
2865 &mut upper_1618,
2866 &mut upper_2618,
2867 &mut lower_0618,
2868 &mut lower_1000,
2869 &mut lower_1618,
2870 &mut lower_2618,
2871 &mut tp_long,
2872 &mut tp_short,
2873 &mut long_entry,
2874 &mut short_entry,
2875 &mut rejection_long,
2876 &mut rejection_short,
2877 &mut long_bounce,
2878 &mut short_bounce,
2879 )
2880 .unwrap();
2881
2882 assert_series_eq(&basis, &direct.basis, 1e-12);
2883 assert_series_eq(&upper_2618, &direct.upper_2618, 1e-12);
2884 assert_series_eq(&short_bounce, &direct.short_bounce, 1e-12);
2885 }
2886
2887 #[test]
2888 fn fibonacci_entry_bands_batch_single_param_matches_single() {
2889 let (open, high, low, close) = sample_ohlc(160);
2890 let sweep = FibonacciEntryBandsBatchRange {
2891 length: (17, 17, 0),
2892 atr_length: (10, 10, 0),
2893 source: "hlc3".to_string(),
2894 use_atr: true,
2895 tp_aggressiveness: "medium".to_string(),
2896 };
2897 let batch = fibonacci_entry_bands_batch_with_kernel(
2898 &open,
2899 &high,
2900 &low,
2901 &close,
2902 &sweep,
2903 Kernel::Auto,
2904 )
2905 .unwrap();
2906 let single = fibonacci_entry_bands(&FibonacciEntryBandsInput::from_slices(
2907 &open,
2908 &high,
2909 &low,
2910 &close,
2911 FibonacciEntryBandsParams {
2912 source: Some("hlc3".to_string()),
2913 length: Some(17),
2914 atr_length: Some(10),
2915 use_atr: Some(true),
2916 tp_aggressiveness: Some("medium".to_string()),
2917 },
2918 ))
2919 .unwrap();
2920 assert_eq!(batch.rows, 1);
2921 assert_eq!(batch.cols, close.len());
2922 assert_series_eq(&batch.basis[..close.len()], &single.basis, 1e-12);
2923 assert_series_eq(
2924 &batch.rejection_short[..close.len()],
2925 &single.rejection_short,
2926 1e-12,
2927 );
2928 }
2929}