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
use crate::indicators::dema::State;
#[cfg(feature = "simd_assets")]
pub use crate::indicators::simd_indicators::by_asset::dema::indicator_by_assets;
#[cfg(feature = "simd_options")]
pub use crate::indicators::simd_indicators::by_option::dema::indicator_by_options;
use crate::indicators::simd_indicators::{
ema_simd::calc_simd as calc_ema_simd, simd_types::F64Constants,
};
use std::simd::{Simd, StdFloat};
/// SIMD-parallel state for computing the Double Exponential Moving Average (DEMA) across `N`
/// assets simultaneously. Each field is a SIMD vector where lane `i` corresponds to asset `i`.
pub struct SimdState<const N: usize> {
/// First-order EMA of the input price series.
pub ema1: Simd<f64, N>,
/// Second-order EMA — EMA of `ema1`.
pub ema2: Simd<f64, N>,
}
impl<const N: usize> SimdState<N> {
/// Gathers `N` mutable scalar [`State`] references into a single `SimdState`,
/// packing each field into a SIMD lane.
pub fn new_mut_ref(states: &[&mut State]) -> Self {
let mut ema1 = [0.0; N];
let mut ema2 = [0.0; N];
for i in 0..N {
ema1[i] = states[i].ema1;
ema2[i] = states[i].ema2;
}
Self {
ema1: Simd::from_array(ema1),
ema2: Simd::from_array(ema2),
}
}
/// Gathers `N` immutable scalar [`State`] references into a single `SimdState`,
/// packing each field into a SIMD lane.
pub fn new(states: &[&State]) -> Self {
let mut ema1 = [0.0; N];
let mut ema2 = [0.0; N];
for i in 0..N {
ema1[i] = states[i].ema1;
ema2[i] = states[i].ema2;
}
Self {
ema1: Simd::from_array(ema1),
ema2: Simd::from_array(ema2),
}
}
/// Scatters the SIMD state back into an array of `N` scalar [`State`] values.
pub fn to_states(&self) -> [State; N] {
let ema1 = self.ema1.to_array();
let ema2 = self.ema2.to_array();
let states: [State; N] = std::array::from_fn(|i| State::new(ema1[i], ema2[i]));
states
}
/// Writes the SIMD state back into `N` existing mutable scalar [`State`] references in place,
/// avoiding allocation compared to [`to_states`].
pub fn write_states(&self, states: &mut [&mut State]) {
let ema1 = self.ema1.to_array();
let ema2 = self.ema2.to_array();
for i in 0..N {
states[i].ema1 = ema1[i];
states[i].ema2 = ema2[i];
}
}
}
/// Advances the DEMA by one bar for `N` assets simultaneously.
///
/// Applies EMA twice: `ema1 = EMA(value)`, `ema2 = EMA(ema1)`. Returns
/// `DEMA = 2 * ema1 - ema2` (using a fused multiply-add) and the intermediate `ema1`.
///
/// # Returns
///
/// A tuple `(dema, ema1)` of SIMD vectors for all `N` lanes.
#[inline(always)]
pub fn calc_simd<const N: usize>(
state: &mut SimdState<N>,
value: Simd<f64, N>,
multiplier: (Simd<f64, N>, Simd<f64, N>),
) -> (Simd<f64, N>, Simd<f64, N>) {
state.ema1 = calc_ema_simd(value, state.ema1, multiplier);
state.ema2 = calc_ema_simd(state.ema1, state.ema2, multiplier);
//(F64Constants::TWO * state.ema1 - state.ema2, state.ema1)
(
state.ema1.mul_add(F64Constants::TWO, -state.ema2),
state.ema1,
)
}