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
use crate::indicators::atr::State;
#[cfg(feature = "simd_assets")]
pub use crate::indicators::simd_indicators::by_asset::atr::indicator_by_assets;
#[cfg(feature = "simd_options")]
pub use crate::indicators::simd_indicators::by_option::atr::indicator_by_options;
use crate::indicators::simd_indicators::{
tr_simd::calc_simd as calc_tr_simd,
wilders_simd::{
calc_simd as calc_wilders_simd, partial_calc_simd as partial_calc_wilders_simd,
},
};
use std::simd::Simd;
/// SIMD-parallel state for computing the Average True Range (ATR) across `N` assets
/// simultaneously. Each field is a SIMD vector where lane `i` corresponds to asset `i`.
pub struct SimdState<const N: usize> {
/// Current Wilder-smoothed ATR value for each asset.
pub atr: Simd<f64, N>,
/// Previous bar's close price for each asset, used to compute the True Range.
pub prev_close: 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 State]) -> Self {
let mut atr = [0.0; N];
let mut prev_close = [0.0; N];
for i in 0..N {
atr[i] = states[i].atr;
prev_close[i] = states[i].prev_close;
}
Self {
atr: Simd::from_array(atr),
prev_close: Simd::from_array(prev_close),
}
}
/*pub fn to_states(&self) -> [State; N] {
let atr = self.atr.to_array();
let prev_close = self.prev_close.to_array();
let states: [State; N] = std::array::from_fn(|i| State::new(atr[i], prev_close[i]));
states
}*/
/// 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 atr = self.atr.to_array();
let prev_close = self.prev_close.to_array();
for i in 0..N {
states[i].atr = atr[i];
states[i].prev_close = prev_close[i];
}
}
/// Advances the ATR by one bar using Wilder smoothing for all `N` lanes.
///
/// Computes the True Range from `high`, `low`, and the stored `prev_close`, then blends
/// it into the running ATR with the Wilder multiplier. Updates `prev_close`.
///
/// # Returns
///
/// A tuple `(atr, tr)` of SIMD vectors for all `N` lanes.
#[inline(always)]
pub fn calc_simd(
&mut self,
high: Simd<f64, N>,
low: Simd<f64, N>,
close: Simd<f64, N>,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>) {
let tr = calc_tr_simd(high, low, self.prev_close);
self.atr = calc_wilders_simd(self.atr, tr, multiplier);
self.prev_close = close;
(self.atr, tr)
}
/// Advances the ATR by one bar using the partial Wilder update for all `N` lanes.
///
/// Uses the partial (non-corrected) Wilder formula, suitable for the warm-up phase
/// before the ATR is fully initialised. Updates `prev_close`.
///
/// # Returns
///
/// A tuple `(atr, tr)` of SIMD vectors for all `N` lanes.
#[inline(always)]
pub fn partial_calc_simd(
&mut self,
high: Simd<f64, N>,
low: Simd<f64, N>,
close: Simd<f64, N>,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>) {
let tr = calc_tr_simd(high, low, self.prev_close);
self.atr = partial_calc_wilders_simd(self.atr, tr, multiplier);
self.prev_close = close;
(self.atr, tr)
}
}
#[inline(always)]
pub fn calc_simd<const N: usize>(
state: &mut SimdState<N>,
high: Simd<f64, N>,
low: Simd<f64, N>,
close: Simd<f64, N>,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>) {
state.calc_simd(high, low, close, multiplier)
}
#[inline(always)]
pub fn partial_calc_simd<const N: usize>(
state: &mut SimdState<N>,
high: Simd<f64, N>,
low: Simd<f64, N>,
close: Simd<f64, N>,
multiplier: Simd<f64, N>,
) -> (Simd<f64, N>, Simd<f64, N>) {
state.partial_calc_simd(high, low, close, multiplier)
}