Add Aroon Indicator implementation and tests

- Implemented Aroon Indicator with constructor, initialization, and update methods.
- Added unit tests for AroonIndicator to verify default settings, historical depth, short name, source code link, and processing of historical bars.
- Created Aroon class for core calculations, including methods for updating with TBar and TBarSeries.
- Added validation tests to ensure Aroon calculations match results from Skender and TA-Lib.
- Updated documentation for Aroon Indicator with calculation methods and usage examples.
- Refactored Dema and Wma classes to use Batch methods for calculations.
- Enhanced performance benchmarks by increasing bar count and integrating OoplesFinance indicators.
- Updated project dependencies to include OoplesFinance.StockIndicators.
This commit is contained in:
Miha Kralj
2025-12-17 13:18:25 -08:00
parent 15c4e832ed
commit 1084644a3d
14 changed files with 921 additions and 310 deletions
+1 -1
View File
@@ -20,7 +20,7 @@
| APCHANNEL | Andrews' Pitchfork | Channels |
| APO | Absolute Price Oscillator | Momentum |
| APZ | Adaptive Price Zone | Channels |
| AROON | Aroon | Momentum |
| [AROON](momentum/aroon/Aroon.md) | Aroon | Momentum |
| AROONOSC | Aroon Oscillator | Momentum |
| ATAN2 | Two-Argument Arctangent | Numerics |
| ATR | Average True Range | Volatility |
+1 -1
View File
@@ -9,7 +9,7 @@ Momentum indicators measure the speed or strength of price movements. This inclu
| ADXR | Average Directional Movement Rating | |
| [AO](ao/Ao.md) | Awesome Oscillator | Measures market momentum using the difference between 34-period and 5-period SMAs of median price. |
| APO | Absolute Price Oscillator | |
| AROON | Aroon | |
| [AROON](aroon/Aroon.md) | Aroon | Identifies trend changes and strength using time since high/low. |
| AROONOSC | Aroon Oscillator | |
| BBB | Bollinger %B | |
| BBS | Bollinger Band Squeeze | |
@@ -0,0 +1,89 @@
using Xunit;
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class AroonIndicatorTests
{
[Fact]
public void AroonIndicator_Constructor_SetsDefaults()
{
var indicator = new AroonIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("Aroon", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void AroonIndicator_MinHistoryDepths_EqualsPeriod()
{
var indicator = new AroonIndicator { Period = 20 };
Assert.Equal(20, indicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void AroonIndicator_ShortName_IncludesParameters()
{
var indicator = new AroonIndicator { Period = 20 };
indicator.Initialize();
Assert.Contains("Aroon", indicator.ShortName);
Assert.Contains("20", indicator.ShortName);
}
[Fact]
public void AroonIndicator_SourceCodeLink_IsValid()
{
var indicator = new AroonIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink);
Assert.Contains("Aroon.Quantower.cs", indicator.SourceCodeLink);
}
[Fact]
public void AroonIndicator_Initialize_CreatesInternalAroon()
{
var indicator = new AroonIndicator { Period = 14 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist (Up, Down, Osc)
Assert.Equal(3, indicator.LinesSeries.Count);
}
[Fact]
public void AroonIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new AroonIndicator { Period = 5 };
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
// Need enough bars for Period
for (int i = 0; i < 20; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
// Process update for each bar to simulate history loading
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
// Line series should have a value
double up = indicator.LinesSeries[0].GetValue(0);
double down = indicator.LinesSeries[1].GetValue(0);
double osc = indicator.LinesSeries[2].GetValue(0);
Assert.True(double.IsFinite(up));
Assert.True(double.IsFinite(down));
Assert.True(double.IsFinite(osc));
}
}
+64
View File
@@ -0,0 +1,64 @@
using System.Drawing;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
public class AroonIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 14;
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Aroon? _aroon;
protected LineSeries? UpSeries;
protected LineSeries? DownSeries;
protected LineSeries? OscSeries;
public int MinHistoryDepths => Period;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"Aroon {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/aroon/Aroon.Quantower.cs";
public AroonIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "Aroon";
Description = "Identifies trend changes and strength";
UpSeries = new(name: "Aroon Up", color: Color.Green, width: 1, style: LineStyle.Solid);
DownSeries = new(name: "Aroon Down", color: Color.Red, width: 1, style: LineStyle.Solid);
OscSeries = new(name: "Aroon Osc", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(UpSeries);
AddLineSeries(DownSeries);
AddLineSeries(OscSeries);
}
protected override void OnInit()
{
_aroon = new Aroon(Period);
base.OnInit();
}
protected override void OnUpdate(UpdateArgs args)
{
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
TBar bar = this.GetInputBar(args);
TValue result = _aroon!.Update(bar, isNew);
if (!_aroon.IsHot && !ShowColdValues)
{
return;
}
UpSeries!.SetValue(_aroon.Up.Value);
DownSeries!.SetValue(_aroon.Down.Value);
OscSeries!.SetValue(result.Value);
}
}
+165
View File
@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using Xunit;
namespace QuanTAlib;
public class AroonTests
{
[Fact]
public void BasicCalculation_DoesNotCrash()
{
var aroon = new Aroon(14);
var gbm = new GBM();
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
aroon.Update(bars[i]);
}
Assert.True(double.IsFinite(aroon.Last.Value));
Assert.True(double.IsFinite(aroon.Up.Value));
Assert.True(double.IsFinite(aroon.Down.Value));
}
[Fact]
public void IsNew_Consistency()
{
var aroon = new Aroon(14);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed first 99
for (int i = 0; i < 99; i++)
{
aroon.Update(bars[i]);
}
// Update with 100th point (isNew=true)
aroon.Update(bars[99], true);
// Update with modified 100th point (isNew=false)
var modifiedBar = new TBar(bars[99].Time, bars[99].Open, bars[99].High + 10.0, bars[99].Low - 10.0, bars[99].Close, bars[99].Volume);
var val2 = aroon.Update(modifiedBar, false);
// Create new instance and feed up to modified
var aroon2 = new Aroon(14);
for (int i = 0; i < 99; i++)
{
aroon2.Update(bars[i]);
}
var val3 = aroon2.Update(modifiedBar, true);
Assert.Equal(val3.Value, val2.Value, 1e-9);
Assert.Equal(aroon2.Up.Value, aroon.Up.Value, 1e-9);
Assert.Equal(aroon2.Down.Value, aroon.Down.Value, 1e-9);
}
[Fact]
public void Reset_Works()
{
var aroon = new Aroon(14);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
aroon.Update(bars[i]);
}
aroon.Reset();
Assert.Equal(0, aroon.Last.Value);
Assert.False(aroon.IsHot);
// Feed again
for (int i = 0; i < bars.Count; i++)
{
aroon.Update(bars[i]);
}
Assert.True(double.IsFinite(aroon.Last.Value));
}
[Fact]
public void TBarSeries_Update_Matches_Streaming()
{
var aroon = new Aroon(14);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var streamingResults = new List<double>();
for (int i = 0; i < bars.Count; i++)
{
streamingResults.Add(aroon.Update(bars[i]).Value);
}
var aroon2 = new Aroon(14);
var seriesResults = aroon2.Update(bars);
Assert.Equal(streamingResults.Count, seriesResults.Count);
for (int i = 0; i < seriesResults.Count; i++)
{
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var aroon = new Aroon(14);
var streamingResults = new List<double>();
for (int i = 0; i < bars.Count; i++)
{
streamingResults.Add(aroon.Update(bars[i]).Value);
}
var staticResults = Aroon.Batch(bars, 14);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
[Fact]
public void Constructor_InvalidParameters_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Aroon(0));
Assert.Throws<ArgumentException>(() => new Aroon(-1));
}
[Fact]
public void ManualCalculation_Verify()
{
// Simple manual test
// Period = 2
// Highs: 10, 12, 11
// Lows: 8, 9, 7
// T=0: H=10, L=8. Not enough data.
// T=1: H=12, L=9. Not enough data.
// T=2: H=11, L=7.
// Window Highs: [10, 12, 11]. Max is 12 at index 1 (1 day ago).
// Window Lows: [8, 9, 7]. Min is 7 at index 2 (0 days ago).
// Up = ((2 - 1) / 2) * 100 = 50
// Down = ((2 - 0) / 2) * 100 = 100
// Osc = 50 - 100 = -50
var aroon = new Aroon(2);
var time = DateTime.UtcNow;
aroon.Update(new TBar(time, 10, 10, 8, 9, 100));
aroon.Update(new TBar(time.AddMinutes(1), 11, 12, 9, 10, 100));
var result = aroon.Update(new TBar(time.AddMinutes(2), 10, 11, 7, 8, 100));
Assert.Equal(50.0, aroon.Up.Value, 1e-9);
Assert.Equal(100.0, aroon.Down.Value, 1e-9);
Assert.Equal(-50.0, result.Value, 1e-9);
}
}
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Skender.Stock.Indicators;
using TALib;
using Xunit;
using QuanTAlib.Tests;
namespace QuanTAlib;
public sealed class AroonValidationTests : IDisposable
{
private readonly ValidationTestData _data;
public AroonValidationTests()
{
_data = new ValidationTestData();
}
public void Dispose()
{
_data.Dispose();
}
[Fact]
public void MatchesSkender()
{
var aroon = new Aroon(14);
var results = new List<double>();
var upResults = new List<double>();
var downResults = new List<double>();
for (int i = 0; i < _data.Bars.Count; i++)
{
var res = aroon.Update(_data.Bars[i]);
results.Add(res.Value);
upResults.Add(aroon.Up.Value);
downResults.Add(aroon.Down.Value);
}
var skenderResults = _data.SkenderQuotes.GetAroon(14).ToList();
// Verify Oscillator
ValidationHelper.VerifyData(results, skenderResults, x => x.Oscillator);
// Verify Up
ValidationHelper.VerifyData(upResults, skenderResults, x => x.AroonUp);
// Verify Down
ValidationHelper.VerifyData(downResults, skenderResults, x => x.AroonDown);
}
[Fact]
public void MatchesTalib()
{
var aroon = new Aroon(14);
var results = new List<double>();
var upResults = new List<double>();
var downResults = new List<double>();
for (int i = 0; i < _data.Bars.Count; i++)
{
var res = aroon.Update(_data.Bars[i]);
results.Add(res.Value);
upResults.Add(aroon.Up.Value);
downResults.Add(aroon.Down.Value);
}
double[] hData = _data.Bars.High.Select(x => x.Value).ToArray();
double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray();
double[] outAroonUp = new double[_data.Bars.Count];
double[] outAroonDown = new double[_data.Bars.Count];
double[] outAroonOsc = new double[_data.Bars.Count];
// TA-Lib Aroon (Up/Down)
var retCode = TALib.Functions.Aroon(hData, lData, 0..^0, outAroonDown, outAroonUp, out var outRange, 14);
Assert.Equal(Core.RetCode.Success, retCode);
// TA-Lib AroonOsc
var retCodeOsc = TALib.Functions.AroonOsc(hData, lData, 0..^0, outAroonOsc, out var outRangeOsc, 14);
Assert.Equal(Core.RetCode.Success, retCodeOsc);
int lookback = TALib.Functions.AroonLookback(14);
// Verify Up
ValidationHelper.VerifyData(upResults, outAroonUp, outRange, lookback);
// Verify Down
ValidationHelper.VerifyData(downResults, outAroonDown, outRange, lookback);
// Verify Oscillator
ValidationHelper.VerifyData(results, outAroonOsc, outRangeOsc, lookback);
}
}
+185
View File
@@ -0,0 +1,185 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Aroon Indicator
/// </summary>
/// <remarks>
/// The Aroon indicator is used to identify trend changes in the price of an asset, as well as the strength of that trend.
/// It consists of two lines: Aroon Up and Aroon Down.
///
/// Calculation:
/// Aroon Up = ((Period - Days Since Period High) / Period) * 100
/// Aroon Down = ((Period - Days Since Period Low) / Period) * 100
/// Aroon Oscillator = Aroon Up - Aroon Down
///
/// The indicator requires Period + 1 samples to fully calculate "Period" days ago.
///
/// Sources:
/// https://www.investopedia.com/terms/a/aroon.asp
/// Tushar Chande (1995)
/// </remarks>
[SkipLocalsInit]
public sealed class Aroon : ITValuePublisher
{
private readonly int _period;
private readonly RingBuffer _highs;
private readonly RingBuffer _lows;
/// <summary>
/// Display name for the indicator.
/// </summary>
public string Name { get; }
public event Action<TValue>? Pub;
/// <summary>
/// Current Aroon Oscillator value (Up - Down).
/// </summary>
public TValue Last { get; private set; }
/// <summary>
/// Current Aroon Up value.
/// </summary>
public TValue Up { get; private set; }
/// <summary>
/// Current Aroon Down value.
/// </summary>
public TValue Down { get; private set; }
/// <summary>
/// True if the indicator has enough data for a full period calculation.
/// </summary>
public bool IsHot => _highs.IsFull;
/// <summary>
/// The number of bars required for the indicator to warm up.
/// </summary>
public int WarmupPeriod { get; }
/// <summary>
/// Creates Aroon indicator with specified period.
/// </summary>
/// <param name="period">Lookback period (must be > 0)</param>
public Aroon(int period)
{
if (period <= 0)
throw new ArgumentException("Period must be greater than 0", nameof(period));
_period = period;
Name = $"Aroon({period})";
WarmupPeriod = period;
// We need Period + 1 samples to cover the range [0, Period] days ago.
_highs = new RingBuffer(period + 1);
_lows = new RingBuffer(period + 1);
}
/// <summary>
/// Resets the indicator state.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
_highs.Clear();
_lows.Clear();
Last = default;
Up = default;
Down = default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(TBar input, bool isNew = true)
{
_highs.Add(input.High, isNew);
_lows.Add(input.Low, isNew);
if (_highs.Count == 0)
{
return default;
}
// Find max index in highs (Zero allocation)
var highsBuffer = _highs.InternalBuffer;
int count = _highs.Count;
int capacity = _highs.Capacity;
int start = _highs.StartIndex;
double maxVal = double.MinValue;
int maxIdxRelative = 0;
for (int i = 0; i < count; i++)
{
int idx = (start + i) % capacity;
double val = highsBuffer[idx];
// Use >= to find the most recent high if values are equal
if (val >= maxVal)
{
maxVal = val;
maxIdxRelative = i;
}
}
// Find min index in lows (Zero allocation)
var lowsBuffer = _lows.InternalBuffer;
double minVal = double.MaxValue;
int minIdxRelative = 0;
for (int i = 0; i < count; i++)
{
int idx = (start + i) % capacity;
double val = lowsBuffer[idx];
// Use <= to find the most recent low if values are equal
if (val <= minVal)
{
minVal = val;
minIdxRelative = i;
}
}
// Calculate days since (0 means current bar is the high/low)
int daysSinceHigh = (count - 1) - maxIdxRelative;
int daysSinceLow = (count - 1) - minIdxRelative;
double up = ((double)(_period - daysSinceHigh) / _period) * 100.0;
double down = ((double)(_period - daysSinceLow) / _period) * 100.0;
double osc = up - down;
Up = new TValue(input.Time, up);
Down = new TValue(input.Time, down);
Last = new TValue(input.Time, osc);
Pub?.Invoke(Last);
return Last;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(TValue input, bool isNew = true)
{
return Update(new TBar(input.Time, input.Value, input.Value, input.Value, input.Value, 0), isNew);
}
public TSeries Update(TBarSeries source)
{
var t = new List<long>(source.Count);
var v = new List<double>(source.Count);
Reset();
for (int i = 0; i < source.Count; i++)
{
var val = Update(source[i], true);
t.Add(val.Time);
v.Add(val.Value);
}
return new TSeries(t, v);
}
public static TSeries Batch(TBarSeries source, int period)
{
var aroon = new Aroon(period);
return aroon.Update(source);
}
}
+66
View File
@@ -0,0 +1,66 @@
# Aroon Indicator
The Aroon indicator is a technical indicator used to identify trend changes in the price of an asset, as well as the strength of that trend. It consists of two lines: Aroon Up and Aroon Down.
## Calculation
The Aroon indicator measures the time between highs and the time between lows over a time period.
$$
\text{Aroon Up} = \frac{\text{Period} - \text{Days Since Period High}}{\text{Period}} \times 100
$$
$$
\text{Aroon Down} = \frac{\text{Period} - \text{Days Since Period Low}}{\text{Period}} \times 100
$$
$$
\text{Aroon Oscillator} = \text{Aroon Up} - \text{Aroon Down}
$$
Where:
- **Period**: The lookback period (typically 25).
- **Days Since Period High**: The number of days since the highest high within the period.
- **Days Since Period Low**: The number of days since the lowest low within the period.
## Interpretation
- **Aroon Up**: Measures the strength of the uptrend. Values close to 100 indicate a strong uptrend, while values close to 0 indicate a weak uptrend.
- **Aroon Down**: Measures the strength of the downtrend. Values close to 100 indicate a strong downtrend, while values close to 0 indicate a weak downtrend.
- **Crossovers**: When Aroon Up crosses above Aroon Down, it signals a potential uptrend. When Aroon Down crosses above Aroon Up, it signals a potential downtrend.
- **Extremes**: Values above 70 indicate a strong trend, while values below 30 indicate a weak trend.
## Usage
### C# code
```csharp
using QuanTAlib;
// Create Aroon with period 14
var aroon = new Aroon(14);
// Update with a TBar
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
var result = aroon.Update(bar);
// Access values
var osc = result.Value;
var up = aroon.Up.Value;
var down = aroon.Down.Value;
Console.WriteLine($"Aroon Osc: {osc:F2}, Up: {up:F2}, Down: {down:F2}");
```
### Quantower
The Aroon indicator is available in Quantower as "Aroon".
- **Period**: The lookback period (default: 14).
- **Show cold values**: Whether to show values before the indicator is fully warmed up.
## References
- [Investopedia: Aroon Indicator](https://www.investopedia.com/terms/a/aroon.asp)
- Tushar Chande (1995)
+3 -3
View File
@@ -200,16 +200,16 @@ public sealed class Dema : AbstractBase
return dema.Update(source);
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
{
if (period <= 0)
throw new ArgumentException("Period must be greater than 0", nameof(period));
double alpha = 2.0 / (period + 1);
Calculate(source, output, alpha);
Batch(source, output, alpha);
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double alpha)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, double alpha)
{
if (source.Length != output.Length)
throw new ArgumentException("Source and output must have the same length");
+14 -7
View File
@@ -37,7 +37,14 @@ public sealed class Wma : AbstractBase
private State _state;
private State _p_state;
private const int ResyncInterval = 1000;
private const int ResyncInterval = 10000;
private static readonly Vector512<long> V512_Idx_1 = Vector512.Create(0L, 0, 1, 2, 3, 4, 5, 6);
private static readonly Vector512<long> V512_Idx_2 = Vector512.Create(0L, 0, 0, 0, 1, 2, 3, 4);
private static readonly Vector512<long> V512_Idx_4 = Vector512.Create(0L, 0, 0, 0, 0, 0, 1, 2);
private static readonly Vector512<double> V512_Mask_1 = Vector512.Create(0.0, 1, 1, 1, 1, 1, 1, 1);
private static readonly Vector512<double> V512_Mask_2 = Vector512.Create(0.0, 0, 1, 1, 1, 1, 1, 1);
private static readonly Vector512<double> V512_Mask_4 = Vector512.Create(0.0, 0, 0, 0, 1, 1, 1, 1);
public Wma(int period)
{
@@ -366,13 +373,13 @@ public sealed class Wma : AbstractBase
var vDeltaS = Avx512F.Subtract(vNew, vOld);
// Prefix sum of DeltaS
var vShiftS1 = Vector512.Create(0.0, vDeltaS.GetElement(0), vDeltaS.GetElement(1), vDeltaS.GetElement(2), vDeltaS.GetElement(3), vDeltaS.GetElement(4), vDeltaS.GetElement(5), vDeltaS.GetElement(6));
var vShiftS1 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vDeltaS, V512_Idx_1), V512_Mask_1);
var vPS1 = Avx512F.Add(vDeltaS, vShiftS1);
var vShiftS2 = Vector512.Create(0.0, 0.0, vPS1.GetElement(0), vPS1.GetElement(1), vPS1.GetElement(2), vPS1.GetElement(3), vPS1.GetElement(4), vPS1.GetElement(5));
var vShiftS2 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPS1, V512_Idx_2), V512_Mask_2);
var vPS2 = Avx512F.Add(vPS1, vShiftS2);
var vShiftS4 = Vector512.Create(0.0, 0.0, 0.0, 0.0, vPS2.GetElement(0), vPS2.GetElement(1), vPS2.GetElement(2), vPS2.GetElement(3));
var vShiftS4 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPS2, V512_Idx_4), V512_Mask_4);
var vPS4 = Avx512F.Add(vPS2, vShiftS4);
var vSums = Avx512F.Add(vSumState, vPS4);
@@ -382,13 +389,13 @@ public sealed class Wma : AbstractBase
var vU = Avx512F.FusedMultiplySubtract(vPeriod, vNew, vSumsShifted);
// Prefix sum of vU
var vShiftW1 = Vector512.Create(0.0, vU.GetElement(0), vU.GetElement(1), vU.GetElement(2), vU.GetElement(3), vU.GetElement(4), vU.GetElement(5), vU.GetElement(6));
var vShiftW1 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vU, V512_Idx_1), V512_Mask_1);
var vPW1 = Avx512F.Add(vU, vShiftW1);
var vShiftW2 = Vector512.Create(0.0, 0.0, vPW1.GetElement(0), vPW1.GetElement(1), vPW1.GetElement(2), vPW1.GetElement(3), vPW1.GetElement(4), vPW1.GetElement(5));
var vShiftW2 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPW1, V512_Idx_2), V512_Mask_2);
var vPW2 = Avx512F.Add(vPW1, vShiftW2);
var vShiftW4 = Vector512.Create(0.0, 0.0, 0.0, 0.0, vPW2.GetElement(0), vPW2.GetElement(1), vPW2.GetElement(2), vPW2.GetElement(3));
var vShiftW4 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPW2, V512_Idx_4), V512_Mask_4);
var vPW4 = Avx512F.Add(vPW2, vShiftW4);
var vWsums = Avx512F.Add(vWsumState, vPW4);