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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#[cfg(feature = "simd_assets")]
pub use crate::indicators::simd_indicators::by_asset::volatility::indicator_by_assets;
#[cfg(feature = "simd_options")]
pub use crate::indicators::simd_indicators::by_option::volatility::indicator_by_options;
pub mod imports {
//! Internal imports shared by the [`assets`] and [`options`] SIMD sub-modules
//! for the Volatility indicator.
pub(crate) use crate::indicators::simd_indicators::{
simd_types::F64Constants,
stddev_simd::{calc_simd as stddev_calc_simd, SimdState as StddevSimdState},
};
pub(crate) use crate::indicators::volatility::State;
pub(crate) use crate::ring_buffer::single_buffer::generic_buffer::RingBuffer;
pub(crate) use std::simd::Simd;
}
pub mod assets {
//! Per-asset SIMD state and compute for the Volatility indicator.
use super::imports::*;
pub(crate) use crate::ring_buffer::single_buffer::generic_buffer::{
SimdBuffer, SimdRingBuffer,
};
/// SIMD-parallel state for the Volatility indicator, holding `N` lanes of per-asset state.
pub struct SimdState<const N: usize> {
pub buffer: SimdBuffer<N>,
pub stddev_state: StddevSimdState<N>,
pub prev_real: Simd<f64, N>,
}
impl<const N: usize> SimdState<N> {
/// Constructs a [`SimdState`] by interleaving the fields of `N` scalar [`State`] references
/// into SIMD lanes.
pub fn new(states: &mut [&mut State]) -> Self {
debug_assert_eq!(states.len(), N, "Number of states must match SIMD width");
let mut stddev_refs = Vec::with_capacity(N);
let mut buffer_refs = Vec::with_capacity(N);
let mut prev_real = [0.0; N];
for (i, state) in states.iter_mut().enumerate() {
stddev_refs.push(&mut state.stddev_state);
buffer_refs.push(&state.buffer);
prev_real[i] = state.prev_real;
}
let stddev_state = StddevSimdState::new(&mut stddev_refs);
let buffer = SimdBuffer::from_f64_buffers(buffer_refs);
Self {
buffer,
stddev_state,
prev_real: Simd::from_array(prev_real),
}
}
/// Converts this SIMD state into an owned array of `N` scalar [`State`] values.
pub fn to_states(&self) -> [State; N] {
let stddev_states = self.stddev_state.to_states();
let prev_real = self.prev_real.to_array();
let buffers = self.buffer.to_f64_buffers();
// Use into_iter() to consume the arrays and avoid move issues
let states_vec: Vec<State> = buffers
.into_iter()
.zip(stddev_states.into_iter())
.zip(prev_real.iter())
.map(|((buffer, stddev_state), &prev_real)| State {
buffer,
stddev_state,
prev_real,
})
.collect();
// Convert Vec to array
states_vec
.try_into()
.unwrap_or_else(|_| panic!("Failed to convert states_vec to array"))
}
/// Writes SIMD state back into `N` scalar [`State`] references.
pub fn write_states(&self, states: &mut [&mut State]) {
// First, handle the buffer updates
let buffers = self.buffer.to_f64_buffers();
let prev_real = self.prev_real.to_array();
let mut stddev_refs = Vec::with_capacity(N);
for (i, (state, buffer)) in states.iter_mut().zip(buffers.into_iter()).enumerate() {
stddev_refs.push(&mut state.stddev_state);
state.buffer = buffer;
state.prev_real = prev_real[i];
}
// Finally, update the ADX states
self.stddev_state.write_states(&mut stddev_refs);
}
/// Computes one bar of the Volatility indicator for `N` assets simultaneously.
///
/// Calculates the log return `(real - prev_real) / prev_real`, advances the rolling
/// standard deviation state, and returns the annualised volatility for each lane.
///
/// # Arguments
///
/// * `real` - Current price for each asset lane.
/// * `multiplier` - Per-lane stddev multiplier derived from the period.
///
/// # Returns
///
/// Annualised volatility values for all `N` lanes.
#[inline(always)]
pub fn calc_simd(&mut self, real: Simd<f64, N>, multiplier: Simd<f64, N>) -> Simd<f64, N> {
// Rearranged for better numerical stability when prices are large and close
let value = (real - self.prev_real) / self.prev_real;
self.prev_real = real;
let prev_value = self.buffer.push_with_info(value).unwrap();
let (sd, _) = stddev_calc_simd(&mut self.stddev_state, value, prev_value, multiplier);
sd * F64Constants::ANNUAL
}
/// Unchecked variant of [`calc_simd`](SimdState::calc_simd) that skips buffer-full checks.
///
/// # Arguments
///
/// * `real` - Current price for each asset lane.
/// * `multiplier` - Per-lane stddev multiplier derived from the period.
///
/// # Returns
///
/// Annualised volatility values for all `N` lanes.
///
/// # Safety
///
/// The internal ring buffer must be fully initialised (i.e., at least `period` bars have
/// been processed) before calling this function. Calling it on an uninitialised buffer
/// will produce incorrect results or undefined behaviour.
#[inline(always)]
pub unsafe fn calc_unchecked_simd(
&mut self,
real: Simd<f64, N>,
multiplier: Simd<f64, N>,
) -> Simd<f64, N> {
// Rearranged for better numerical stability when prices are large and close
let value = (real - self.prev_real) / self.prev_real;
self.prev_real = real;
let prev_value = self.buffer.push_with_info_unchecked(value);
let (sd, _) = stddev_calc_simd(&mut self.stddev_state, value, prev_value, multiplier);
sd * F64Constants::ANNUAL
}
}
}
pub mod options {
//! Per-option SIMD state and compute for the Volatility indicator.
use super::imports::*;
pub(crate) use crate::ring_buffer::single_buffer::generic_buffer::Buffer;
/// SIMD-parallel state for the Volatility indicator, holding `N` lanes of per-option state.
pub struct SimdState<const N: usize> {
pub buffer: Buffer,
pub stddev_state: StddevSimdState<N>,
pub prev_real: f64,
periods: [usize; N],
}
impl<const N: usize> SimdState<N> {
/// Constructs a [`SimdState`] from `N` scalar [`State`] references, one per option-set lane.
///
/// Selects the largest-capacity buffer as the shared ring buffer and initialises the
/// per-lane stddev state.
///
/// # Arguments
///
/// * `states` - Mutable references to `N` scalar states (one per option set).
/// * `periods` - Per-lane period values.
pub fn new(states: &mut [&mut State], periods: [usize; N]) -> Self {
debug_assert_eq!(states.len(), N, "Number of states must match SIMD width");
let mut main_buffer = 0;
for i in 1..N {
if states[main_buffer].buffer.capacity < states[i].buffer.capacity {
main_buffer = i;
}
}
let buffer = states[main_buffer].buffer.clone();
let mut stddev_refs = Vec::with_capacity(N);
for state in states.iter_mut() {
stddev_refs.push(&mut state.stddev_state);
}
let stddev_state = StddevSimdState::new(&mut stddev_refs);
Self {
buffer,
stddev_state,
prev_real: states[main_buffer].prev_real,
periods,
}
}
/// Writes SIMD state back into `N` scalar [`State`] references, one per option-set lane.
pub fn write_states(&self, states: &mut [&mut State]) {
// First, handle the buffer updates
let vals: [Vec<f64>; N] =
std::array::from_fn(|i| self.buffer.to_ordered_by_period(self.periods[i]));
let prev_real = self.prev_real;
let mut stddev_refs = Vec::with_capacity(N);
for (state, vals) in states.iter_mut().zip(vals.into_iter()) {
stddev_refs.push(&mut state.stddev_state);
state.buffer = {
let len = vals.len();
Buffer {
vals,
index: 0,
prev_idx: len - 1,
capacity: len,
count: len,
}
};
state.prev_real = prev_real;
}
// Finally, update the ADX states
self.stddev_state.write_states(&mut stddev_refs);
}
/*#[inline(always)]
pub fn calc_simd(&mut self, real: Simd<f64, N>, multiplier: Simd<f64, N>) -> Simd<f64, N> {
// Rearranged for better numerical stability when prices are large and close
let value = (real - self.prev_real) / self.prev_real;
self.prev_real = real;
let prev_value = self.buffer.push_with_info(value).unwrap();
let (sd, _) = stddev_calc_simd(&mut self.stddev_state, value, prev_value, multiplier);
sd * F64Constants::ANNUAL
}*/
/// Unchecked SIMD variant that computes one Volatility bar for `N` option-set lanes simultaneously.
///
/// Accepts a scalar `real` input (shared across all option lanes) and a per-lane
/// `multiplier`, and returns annualised volatility for each lane.
///
/// # Arguments
///
/// * `real` - Current price (scalar, shared across lanes).
/// * `multiplier` - Per-lane stddev multiplier derived from each lane's period.
///
/// # Returns
///
/// Annualised volatility values for all `N` option-set lanes.
///
/// # Safety
///
/// The internal ring buffer must be fully initialised (i.e., at least `max(period)` bars
/// have been processed) before calling this function. Calling it on an uninitialised buffer
/// will produce incorrect results or undefined behaviour.
#[inline(always)]
pub unsafe fn calc_unchecked_simd(
&mut self,
real: f64,
multiplier: Simd<f64, N>,
) -> Simd<f64, N> {
// Rearranged for better numerical stability when prices are large and close
let value = (real - self.prev_real) / self.prev_real;
self.prev_real = real;
let prev_value = Simd::from_array(
self.buffer
.push_with_info_periods_unchecked(value, self.periods),
);
let (sd, _) = stddev_calc_simd(
&mut self.stddev_state,
Simd::splat(value),
prev_value,
multiplier,
);
sd * F64Constants::ANNUAL
}
}
}