using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace QuanTAlib; /// /// Multi-Period Simple Moving Average (SMA) - SIMD optimized. /// Calculates multiple SMAs with different periods for the same input series in parallel. /// Uses last-value substitution for invalid inputs (NaN/Infinity). /// [SkipLocalsInit] public class SmaVector { private readonly RingBuffer[] _buffers; private readonly RingBuffer[] _p_buffers; // Previous state for bar correction private readonly int _count; private double _lastValidValue; /// /// Current SMA values for all periods. /// public ReadOnlySpan Values => _values; private readonly TValue[] _values; /// /// Initializes SmaVector with specified periods. /// /// Array of periods (each must be > 0) public SmaVector(int[] periods) { _count = periods.Length; _buffers = new RingBuffer[_count]; _p_buffers = new RingBuffer[_count]; _values = new TValue[_count]; for (int i = 0; i < _count; i++) { ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(periods[i], 0); _buffers[i] = new RingBuffer(periods[i]); _p_buffers[i] = new RingBuffer(periods[i]); } } /// /// Gets a valid input value, using last-value substitution for non-finite inputs. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private double GetValidValue(double input) { if (double.IsFinite(input)) { _lastValidValue = input; return input; } return _lastValidValue; } /// /// Resets all SMA states. /// public void Reset() { for (int i = 0; i < _count; i++) { _buffers[i].Clear(); _p_buffers[i].Clear(); } _lastValidValue = 0; Array.Clear(_values); } /// /// Updates SMAs with the given value. /// Uses last-value substitution: invalid inputs (NaN/Infinity) are replaced with /// the last known good value, providing continuity in the output series. /// /// Input value /// True for new bar, false for update to current bar (default: true) /// Array of SMA values [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue[] Update(TValue input, bool isNew = true) { if (isNew) { // Save current state for potential bar correction for (int i = 0; i < _count; i++) { _p_buffers[i].CopyFrom(_buffers[i]); } } else { // Restore previous state for bar correction for (int i = 0; i < _count; i++) { _buffers[i].CopyFrom(_p_buffers[i]); } } // Last-value substitution: replace non-finite inputs with last valid value double val = GetValidValue(input.Value); // Update each buffer and calculate SMA for (int i = 0; i < _count; i++) { _buffers[i].Add(val); _values[i] = new TValue(input.Time, _buffers[i].Average); } return _values; } /// /// Calculates SMAs for the entire series. /// /// Input series /// Array of SMA series public TSeries[] Calculate(TSeries source) { int len = source.Count; var resultSeries = new TSeries[_count]; // Reset state for fresh calculation for (int i = 0; i < _count; i++) { _buffers[i].Clear(); } _lastValidValue = 0; // Pre-allocate lists var tLists = new List[_count]; var vLists = new List[_count]; for (int i = 0; i < _count; i++) { tLists[i] = new List(len); vLists[i] = new List(len); CollectionsMarshal.SetCount(tLists[i], len); CollectionsMarshal.SetCount(vLists[i], len); } var sourceValues = source.Values; var sourceTimes = source.Times; for (int t = 0; t < len; t++) { double val = sourceValues[t]; long time = sourceTimes[t]; // Last-value substitution: replace non-finite inputs with last valid value val = GetValidValue(val); for (int i = 0; i < _count; i++) { _buffers[i].Add(val); CollectionsMarshal.AsSpan(tLists[i])[t] = time; CollectionsMarshal.AsSpan(vLists[i])[t] = _buffers[i].Average; } } // Create TSeries and update Values for (int i = 0; i < _count; i++) { resultSeries[i] = new TSeries(tLists[i], vLists[i]); var lastT = CollectionsMarshal.AsSpan(tLists[i])[len - 1]; var lastV = CollectionsMarshal.AsSpan(vLists[i])[len - 1]; _values[i] = new TValue(lastT, lastV); } return resultSeries; } /// /// Calculates SMAs for the entire series using specified periods. /// /// Input series /// Array of periods /// Array of SMA series public static TSeries[] Calculate(TSeries source, int[] periods) { var smaVector = new SmaVector(periods); return smaVector.Calculate(source); } }