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
#[cfg(feature = "simd_assets")]
pub use crate::indicators::simd_indicators::by_asset::keltnerchannel::indicator_by_assets;
#[cfg(feature = "simd_options")]
pub use crate::indicators::simd_indicators::by_option::keltnerchannel::indicator_by_options;
use crate::indicators::keltnerchannel::State;
pub use crate::indicators::simd_indicators::{
atr_simd::SimdState as AtrSimdState, ema_simd::calc_simd as ema_calc_simd,
};
use std::simd::Simd;
/// SIMD-parallel state for computing the Keltner Channel across `N` assets or option-set lanes.
///
/// Holds a Wilder ATR state and an EMA value for each lane packed into SIMD vectors.
pub struct SimdState<const N: usize> {
pub atr_state: AtrSimdState<N>,
pub ema: Simd<f64, 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 {
// Create vectors to collect the references
let mut atr_refs = Vec::with_capacity(N);
let mut ema = [0.0; N];
// Collect references and values
for (i, state) in states.iter_mut().enumerate() {
atr_refs.push(&mut state.atr_state);
ema[i] = state.ema;
}
let atr_state = AtrSimdState::new(&mut atr_refs);
Self {
atr_state,
ema: Simd::from_array(ema),
}
}
/// 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 atr_refs = Vec::with_capacity(N);
let ema = self.ema.to_array();
// Collect references and values
for (i, state) in states.iter_mut().enumerate() {
atr_refs.push(&mut state.atr_state);
state.ema = ema[i];
}
self.atr_state.write_states(&mut atr_refs);
}
/// Advances the Keltner Channel by one bar across `N` lanes simultaneously.
///
/// Updates the ATR and EMA states for all lanes in parallel, then computes
/// the lower and upper channel bands as `EMA ± step × ATR`.
///
/// # Arguments
///
/// * `high` / `low` / `close` - SIMD vectors of current bar prices, one value per lane.
/// * `step` - Per-lane ATR multiplier controlling channel width.
/// * `multipliers` - `((atr_alpha, atr_1m_alpha), (ema_alpha, ema_1m_alpha))` per lane.
///
/// # Returns
///
/// `(lower, middle, upper, atr, tr)` as SIMD vectors, one value per lane,
/// where `middle` is the current EMA of close.
#[inline(always)]
pub fn calc_simd(
&mut self,
high: Simd<f64, N>,
low: Simd<f64, N>,
close: Simd<f64, N>,
step: Simd<f64, N>,
multipliers: ((Simd<f64, N>, Simd<f64, N>), (Simd<f64, N>, Simd<f64, N>)),
) -> (
Simd<f64, N>,
Simd<f64, N>,
Simd<f64, N>,
Simd<f64, N>,
Simd<f64, N>,
) {
let (atr, tr) = self.atr_state.calc_simd(high, low, close, multipliers.0);
self.ema = ema_calc_simd(close, self.ema, multipliers.1);
let per = atr * step;
let upper = self.ema + per;
let lower = self.ema - per;
//let upper = atr.mul_add(step, self.ema);
//let lower = atr.mul_add(-step, self.ema);
(lower, self.ema, upper, atr, tr)
}
}