1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use crate::indicators::aroon::State;
#[cfg(feature = "simd_assets")]
pub use crate::indicators::simd_indicators::by_asset::aroon::indicator_by_assets;
#[cfg(feature = "simd_options")]
pub use crate::indicators::simd_indicators::by_option::aroon::indicator_by_options;
use crate::indicators::simd_indicators::{
max_simd::SimdState as SimdMaxState, min_simd::SimdState as SimdMinState,
};
use std::simd::{num::SimdUint, Simd};
/// SIMD-parallel state for computing the Aroon indicator across `N` assets simultaneously.
/// Wraps dedicated min/max ring-buffer SIMD states for tracking the lookback window.
pub struct SimdState<const N: usize> {
/// SIMD state for the rolling minimum (tracks periods since the lowest low).
min_state: SimdMinState<N>,
/// SIMD state for the rolling maximum (tracks periods since the highest high).
max_state: SimdMaxState<N>,
}
impl<const N: usize> SimdState<N> {
/// Gathers `N` scalar [`State`] references into a single `SimdState`,
/// packing each field into a SIMD lane.
pub fn new(states: &mut [&mut State]) -> Self {
let mut min_state = Vec::with_capacity(N);
let mut max_state = Vec::with_capacity(N);
for state in states.iter_mut() {
min_state.push(&mut state.min_state);
max_state.push(&mut state.max_state);
}
let min_state = SimdMinState::new(&min_state);
let max_state = SimdMaxState::new(&max_state);
Self {
min_state,
max_state,
}
}
/// Writes the SIMD state back into `N` existing mutable scalar [`State`] references in place,
/// avoiding allocation compared to a `to_states` conversion.
pub fn write_states(&self, states: &mut [&mut State]) {
let mut max_refs = Vec::with_capacity(N);
let mut min_refs = Vec::with_capacity(N);
for state in states.iter_mut() {
max_refs.push(&mut state.max_state);
min_refs.push(&mut state.min_state);
}
self.max_state.write_states(&mut max_refs);
self.min_state.write_states(&mut min_refs);
}
}
pub mod assets {
use super::*;
use crate::indicators::simd_indicators::{
max_simd::assets::Calc as CalcMax, min_simd::assets::Calc as CalcMin,
};
/// SIMD computation trait for the Aroon indicator, operating on `N` asset lanes simultaneously.
pub trait Calc<const N: usize> {
/// Computes Aroon Down and Aroon Up for one bar across `N` asset lanes using SIMD.
///
/// Delegates to the underlying min/max SIMD states to find the trailing index of the
/// period low and period high respectively, then scales both by `multiplier = 100 / period`.
/// The `WINDOW_LANES` const parameter tunes the inner ring-buffer SIMD width.
///
/// # Safety
///
/// Callers must ensure that `high[lane]` and `low[lane]` point to valid memory at
/// index `i`, and that `i >= period` so the lookback window is fully in bounds.
///
/// # Returns
///
/// A tuple `(aroon_down, aroon_up)` of SIMD vectors for all `N` lanes.
unsafe fn calc_unchecked_simd<const WINDOW_LANES: usize>(
&mut self,
high: [*const f64; N],
low: [*const f64; N],
i: usize,
period: usize,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>);
}
impl<const N: usize> Calc<N> for SimdState<N> {
#[inline(always)]
unsafe fn calc_unchecked_simd<const WINDOW_LANES: usize>(
&mut self,
high: [*const f64; N],
low: [*const f64; N],
i: usize,
period: usize,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>) {
let period_simd = Simd::splat(period);
let (_, min_trail) = self
.min_state
.calc_unchecked_simd::<WINDOW_LANES>(low, i, period);
let (_, max_trail) = self
.max_state
.calc_unchecked_simd::<WINDOW_LANES>(high, i, period);
let aroon_up = (period_simd - max_trail).cast() * multiplier;
let aroon_down = (period_simd - min_trail).cast() * multiplier;
(aroon_down, aroon_up)
}
}
}
pub mod options {
use super::*;
use crate::indicators::simd_indicators::{
max_simd::options::Calc as CalcMax, min_simd::options::Calc as CalcMin,
};
/// SIMD computation trait for the Aroon indicator, operating on `N` option lanes simultaneously.
///
/// Unlike the `assets` variant, `i` and `period` are SIMD vectors so each lane can be at
/// a different bar position and use a different lookback period.
pub trait Calc<const N: usize> {
/// Computes Aroon Down and Aroon Up for one bar across `N` option lanes using SIMD.
///
/// Each lane independently looks up the trailing-min and trailing-max indices within
/// its own `period` window, then scales by its own `multiplier`.
///
/// # Safety
///
/// Callers must ensure that `high[lane]` and `low[lane]` point to valid memory at
/// `i[lane]`, and that `i[lane] >= period[lane]` for every lane.
///
/// # Returns
///
/// A tuple `(aroon_down, aroon_up)` of SIMD vectors for all `N` lanes.
unsafe fn calc_unchecked_simd(
&mut self,
high: [*const f64; N],
low: [*const f64; N],
i: Simd<usize, N>,
period: Simd<usize, N>,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>);
}
impl<const N: usize> Calc<N> for SimdState<N> {
#[inline(always)]
unsafe fn calc_unchecked_simd(
&mut self,
high: [*const f64; N],
low: [*const f64; N],
i: Simd<usize, N>,
period: Simd<usize, N>,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>) {
let (_, min_trail) = self.min_state.calc_unchecked_simd(low, i, period);
let (_, max_trail) = self.max_state.calc_unchecked_simd(high, i, period);
let aroon_up = (period - max_trail).cast() * multiplier;
let aroon_down = (period - min_trail).cast() * multiplier;
(aroon_down, aroon_up)
}
}
}