Question

What is the fastest library/algorithm for calculating simple moving average? I wrote my own, but it takes too long on 330 000 items decimal dataset.

  • period / time(ms)
  • 20 / 300;
  • 60 / 1500;
  • 120 / 3500.

Here is the code of my method:

public decimal MA_Simple(int period, int ii) {
    if (period != 0 && ii > period) {
        //stp.Start();
        decimal summ = 0;
        for (int i = ii; i > ii - period; i--) {
            summ = summ + Data.Close[i];
        }
        summ = summ / period;
        //stp.Stop();
        //if (ii == 1500) System.Windows.Forms.MessageBox.Show((stp.ElapsedTicks * 1000.0) / Stopwatch.Frequency + " ms");
        return summ;
    } else return -1;
}

The Data.Close[] is a fixed size(1 000 000) decimal array.

Was it helpful?

Solution

Your main problem is that you throw away too much information for each iteration. If you want to run this fast, you need to keep a buffer of the same size as the frame length.

This code will run moving averages for your whole dataset:

(Not real C# but you should get the idea)

decimal buffer[] = new decimal[period];
decimal output[] = new decimal[data.Length];
current_index = 0;
for (int i=0; i<data.Length; i++)
    {
        buffer[current_index] = data[i]/period;
        decimal ma = 0.0;
        for (int j=0;j<period;j++)
            {
                ma += buffer[j];
            }
        output[i] = ma;
        current_index = (current_index + 1) % period;
    }
return output;

Please note that it may be tempting to keep a running cumsum instead of keeping the whole buffer and calculating the value for each iteration, but this does not work for very long data lengths as your cumulative sum will grow so big that adding small additional values will result in rounding errors.

OTHER TIPS

    public class MovingAverage  
    {
        private Queue<Decimal> samples = new Queue<Decimal>();
        private int windowSize = 16;
        private Decimal sampleAccumulator;
        public Decimal Average { get; private set; }

        /// <summary>
        /// Computes a new windowed average each time a new sample arrives
        /// </summary>
        /// <param name="newSample"></param>
        public void ComputeAverage(Decimal newSample)
        {
            sampleAccumulator += newSample;
            samples.Enqueue(newSample);

            if (samples.Count > windowSize)
            {
                sampleAccumulator -= samples.Dequeue();
            }

            Average = sampleAccumulator / samples.Count;
        }
    }

These days, the Math DotNet library has a class called RunningStatistics that will do this for you. If you want to do it over the last "X" items only, use MovingStatistics instead.

Both will calculate running averages, variance, and standard deviation, on the fly with one-pass only and without storing extra copies of the data.

If the data is static, you can preprocess the array to make moving average queries very fast:

decimal[] GetCSum(decimal[] data) {
    decimal csum[] = new decimal[data.Length];
    decimal cursum = 0;
    for(int i=0; i<data.Length; i++) {
        cursum += data[i];
        csum[i] = cursum;
    }
    return csum;
}

Now the moving average calculation is easy and fast:

decimal CSumMovingAverage(decimal[] csum, int period, int ii) {
    if(period == 0 || ii <= period)
        return -1;
    return csum[ii] - csum[ii - period];
}

The current (accepted) solution contains an inner loop. It would be more efficient to remove this as well. You can see how this is achieved here:

How to efficiently calculate a moving Standard Deviation

You don't need to keep a running queue. Just pick the latest new entry to the window and drop off the older entry. Notice that this only uses one loop and no extra storage other than a sum.

  // n is the window for your Simple Moving Average
  public List<double> GetMovingAverages(List<Price> prices, int n)
  {
    var movingAverages = new double[prices.Count];
    var runningTotal = 0.0d;       

    for (int i = 0; i < prices.Count; ++i)
    {
      runningTotal += prices[i].Value;
      if( i - n >= 0) {
        var lost = prices[i - n].Value;
        runningTotal -= lost;
        movingAverages[i] = runningTotal / n;
      }
    }
    return movingAverages.ToList();
  }

I find the provide answers a bit to memory hungry, and slow, you asked for fast. Add 2 fields one to keep the running total and one for the times the value changed as average is the sum/count of a list of values. I added a Add method, however you can also just use variables in a method….

public class Sample
{
    private decimal sum = 0;
    private uint count = 0;

    public void Add(decimal value)
    {
        sum += value;
        count++;
    }

    public decimal AverageMove => count > 0 ? sum / count : 0;
}

to make it thread safe:

public class ThreadSafeSample
{
private decimal sum = 0;
private uint count = 0;

private static object locker = new object();
public void Add(decimal value)
{
    lock (locker)
    {
        sum += value;
        count++;
    }
}

public decimal AverageMove => count > 0 ? sum / count : 0;

}

// simple moving average
int moving_average(double *values, double *&averages, int size, int periods)
{
    double sum = 0;
    for (int i = 0; i < size; i ++)
        if (i < periods) {
            sum += values[i];
            averages[i] = (i == periods - 1) ? sum / (double)periods : 0;
        } else {
            sum = sum - values[i - periods] + values[i];
            averages[i] = sum / (double)periods;
        }
    return (size - periods + 1 > 0) ? size - periods + 1 : 0;
}

One C function, 13 lines of codes, simple moving average. Example of usage:

double *values = new double[10]; // the input
double *averages = new double[10]; // the output
values[0] = 55;
values[1] = 113;
values[2] = 92.6;
...
values[9] = 23;
moving_average(values, averages, 10, 5); // 5-day moving average

This is MA I'm using in my app.

double[] MovingAverage(int period, double[] source)
{
    var ma = new double[source.Length];

    double sum = 0;
    for (int bar = 0; bar < period; bar++)
        sum += source[bar];

    ma[period - 1] = sum/period;

    for (int bar = period; bar < source.Length; bar++)
        ma[bar] = ma[bar - 1] + source[bar]/period
                              - source[bar - period]/period;

    return ma;
}

Once you have it calculated for the whole data series, you can grab a particular value instantly.

Here's how I tried it. But warning I'm a complete amateur so this may be completely wrong.

List<decimal> MovingAverage(int period, decimal[] Data)
{
     decimal[] interval = new decimal[period];
     List<decimal> MAs = new List<decimal>();

     for (int i=0, i < Data.length, i++)
     {
          interval[i % period] = Data[i];
          if (i > period - 1)
          {
               MAs.Add(interval.Average());
          }
     }
     return MAs;
}

Should return a list of decimals containing the moving averages for your data.

How aboutQueue ?

using System.Collections.Generic;
using System.Linq;

public class MovingAverage
{
    private readonly Queue<decimal> _queue;
    private readonly int _period;

    public MovingAverage(int period)
    {
        _period = period;
        _queue = new Queue<decimal>(period);
    }

    public decimal Compute(decimal x)
    {
        if (_queue.Count >= _period)
        {
            _queue.Dequeue();
        }

        _queue.Enqueue(x);

        return _queue.Average();
    }
}

Usage:

MovingAverage ma = new MovingAverage(3);

foreach(var val in new decimal[] { 1,2,3,4,5,6,7,8,9 })
{
   Console.WriteLine(ma.Compute(val));
}
/// <summary>
/// Fast low CPU usage moving average based on floating point math
/// Note: This algorithm algorithm compensates for floating point error by re-summing the buffer for every 1000 values
/// </summary>
public class FastMovingAverageDouble
{
    /// <summary>
    /// Adjust this as you see fit to suit the scenario
    /// </summary>
    const int MaximumWindowSize = 100;

    /// <summary>
    /// Adjust this as you see fit
    /// </summary>
    const int RecalculateEveryXValues = 1000;

    /// <summary>
    /// Initializes moving average for specified window size
    /// </summary>
    /// <param name="_WindowSize">Size of moving average window between 2 and MaximumWindowSize 
    /// Note: this value should not be too large and also bear in mind the possibility of overflow and floating point error as this class internally keeps a sum of the values within the window</param>
    public FastMovingAverageDouble(int _WindowSize)
    {
        if (_WindowSize < 2)
        {
            _WindowSize = 2;
        }
        else if (_WindowSize > MaximumWindowSize)
        {
            _WindowSize = MaximumWindowSize;
        }
        m_WindowSize = _WindowSize;
    }
    private object SyncRoot = new object();
    private Queue<double> Buffer = new Queue<double>();
    private int m_WindowSize;
    private double m_MovingAverage = 0d;
    private double MovingSum = 0d;
    private bool BufferFull;
    private int Counter = 0;

    /// <summary>
    /// Calculated moving average
    /// </summary>
    public double MovingAverage
    {
        get
        {
            lock (SyncRoot)
            {
                return m_MovingAverage;
            }
        }
    }

    /// <summary>
    /// Size of moving average window set by constructor during intialization
    /// </summary>
    public int WindowSize
    {
        get
        {
            return m_WindowSize;
        }
    }

    /// <summary>
    /// Add new value to sequence and recalculate moving average seee <see cref="MovingAverage"/>
    /// </summary>
    /// <param name="NewValue">New value to be added</param>
    public void AddValue(int NewValue)
    {
        lock (SyncRoot)
        {
            Buffer.Enqueue(NewValue);
            MovingSum += NewValue;
            if (!BufferFull)
            {
                int BufferSize = Buffer.Count;
                BufferFull = BufferSize == WindowSize;
                m_MovingAverage = MovingSum / BufferSize;
            }
            else
            {
                Counter += 1;
                if (Counter > RecalculateEveryXValues)
                {
                    MovingSum = 0;
                    foreach (double BufferValue in Buffer)
                    {
                        MovingSum += BufferValue;
                    }
                    Counter = 0;
                }
                MovingSum -= Buffer.Dequeue();
                m_MovingAverage = MovingSum / WindowSize;
            }
        }
    }
}

Tested with Dotnet Core 3 & Linq:

int period = 20;
for(int k=0;data.Count()-period;k++){
   decimal summe = data.Skip(k).Take(period).Sum();
   summe /= (decimal)period;
}

It does rely on Linq and its internal optimization, did not time it.
Uses Skip() and Take() as a "rangeBetween" solution for moving average and then divide the summe by the period quantity.
*The for loop is upper capped to avoid incomplete sum operations.
Reference (C# Microsoft): Skip(), Take(), Sum();

My MovingAverage class implementation is:

  • Thread safe
  • Lock free
  • limited to windowSize that is power of two

Here is the class:

using System;
using System.Linq;
using System.Threading;

public class MovingAverage
{
    private readonly int _mask;
    private readonly double?[] _values;
    private int _nextIndex = -1;

    public MovingAverage(int windowSize)
    {
        _mask = windowSize - 1;
        if (windowSize == 0 || (windowSize & _mask) != 0)
        {
            throw new ArgumentException("Must be power of two", nameof(windowSize));
        }
        _values = new double?[windowSize];
    }

    public void Add(double newValue)
    {
        var index = Interlocked.Increment(ref _nextIndex) & _mask;
        _values[index] = newValue;
    }

    public double ComputeAverage()
    {
        return _values.TakeWhile(x => x.HasValue)
            .Select(x => x ?? 0)
            .DefaultIfEmpty(0)
            .Average();
    }
}

here is the NUnit test

using NUnit.Framework;

public class MovingAverageTest
{
    [Test]
    public void Should_compute_average()
    {
        var sut = new MovingAverage(4);

        Assert.That(sut.ComputeAverage(), Is.EqualTo(0));
        sut.Add(2);
        Assert.That(sut.ComputeAverage(), Is.EqualTo(2));
        sut.Add(4);
        Assert.That(sut.ComputeAverage(), Is.EqualTo(3));
        sut.Add(0);
        Assert.That(sut.ComputeAverage(), Is.EqualTo(2));
        sut.Add(6);
        Assert.That(sut.ComputeAverage(), Is.EqualTo(3));
        sut.Add(6);
        Assert.That(sut.ComputeAverage(), Is.EqualTo(4));
        sut.Add(0);
        sut.Add(0);
        sut.Add(0);
        sut.Add(0);
        Assert.That(sut.ComputeAverage(), Is.EqualTo(0));
        sut.Add(10);
        sut.Add(10);
        sut.Add(10);
        sut.Add(10);
        Assert.That(sut.ComputeAverage(), Is.EqualTo(10));
    }

    [Test]
    public void Should_check_windowsize_param()
    {
        Assert.That(() => new MovingAverage(3), Throws.ArgumentException);
    }
}

In practice, this is what I have found to work even for millions of samples. It computes a running moving average and is faster than any other method I have tried.

public class Sma
  {
    decimal mult = 0;
    private decimal[] samples;
    private readonly int max;

    private decimal average;
    public Sma(int period)
    {
        mult = 1m / period; //cache to avoid expensive division on each step.
        samples = new decimal[period];
        max = period - 1;
    }
    public decimal ComputeAverage(decimal value)
    {
        average -= samples[max];
        var sample = value * mult;
        average += sample;
        Array.Copy(samples, 0, samples, 1, max);
        samples[0] = sample;
        return average = average - samples[0];
    }
}

I found I often need access to history. I accomplish this by keeping track of the averages:

public class Sma
{
    private readonly int max;
    private decimal[] history;
    public readonly int Period;
    public int Counter = -1;
    public SimpleSma RunningSma { get; }

    public Sma(int period, int maxSamples)
    {
        this.Period = period;
        this.RunningSma = new SimpleSma(period);
        max = maxSamples - 1;
        history = new decimal[maxSamples];
    }


    public decimal ComputeAverage(decimal value)
    {
        Counter++;
        Array.Copy(history, 0, history, 1, max);
        return history[0] = RunningSma.ComputeAverage(value);
    }

    public decimal Average => history[0];
    public decimal this[int index] => history[index];
    public int Length => history.Length;

}

Now in practice, your use case sounds like mine where you need track multiple time frames:

public class MtfSma // MultiTimeFrame Sma
{
    public Dictionary<int, Sma> Smas { get; private set; }
    public MtfSma(int[] periods, int maxHistorySize = 100)
    {
        Smas = periods.ToDictionary(x=> x, x=> new Sma(x, maxHistorySize));
    }
}

A dictionary is no necessary, but is helpful to map an Sma to its period.

This can be used as follows:

IEnumerable<decimal> dataPoints = new List<Decimal>(); //330 000 data points.
foreach (var dataPoint in dataPoints)
{
    foreach (var kvp in Smas)
    {
        var sma = kvp.Value;
        var period = sma.Period;
        var average = sma.Average; // or sma[0];
        var lastAverage = sma[1];
        Console.WriteLine($"Sma{period} [{sma.Counter}]: Current {average.ToString("n2")}, Previous {lastAverage.ToString("n2")}");
    }
}

Another point is you can see this is strongly typed to decimal, which means a complete rewrite for other data types.

To handle this the classes can be made generic and use an interface to provide type conversions and the needed arithmetic operation providers.

I have a complete working example of the actual code I use, again for millions upon millions of data points, along with implementations for CrossOver detection, etc on Github here. The code relevant to this question and answer:

public interface INumericOperationsProvider<TNumeric>
    where TNumeric : IConvertible
{
    TNumeric Divide(TNumeric dividend, TNumeric divisor);
    TNumeric Multiply(TNumeric multiplicand, TNumeric multiplier);
    TNumeric Add(TNumeric operandA, TNumeric operandB);
    TNumeric Subtract(TNumeric operandA, TNumeric operandB);

    bool IsLessThan(TNumeric operandA, TNumeric operandB);
    bool IsLessThanOrEqual(TNumeric operandA, TNumeric operandB);
    bool IsEqual(TNumeric operandA, TNumeric operandB);
    bool IsGreaterThanOrEqual(TNumeric operandA, TNumeric operandB);
    bool IsGreaterThan(TNumeric operandA, TNumeric operandB);

    TNumeric ToNumeric(sbyte value);
    TNumeric ToNumeric(short value);
    TNumeric ToNumeric(int value);
    TNumeric ToNumeric(long value);
    TNumeric ToNumeric(byte value);
    TNumeric ToNumeric(ushort value);
    TNumeric ToNumeric(uint value);
    TNumeric ToNumeric(ulong value);
    TNumeric ToNumeric(float value);
    TNumeric ToNumeric(double value);
    TNumeric ToNumeric(decimal value);
    TNumeric ToNumeric(IConvertible value);
}



public abstract class OperationsProviderBase<TNumeric>
    : INumericOperationsProvider<TNumeric>
    where TNumeric : IConvertible
{

    private static Type Type = typeof(TNumeric);
    public abstract TNumeric Divide(TNumeric dividend, TNumeric divisor);
    public abstract TNumeric Multiply(TNumeric multiplicand, TNumeric multiplier);
    public abstract TNumeric Add(TNumeric operandA, TNumeric operandB);
    public abstract TNumeric Subtract(TNumeric operandA, TNumeric operandB);



    public TNumeric ToNumeric(sbyte value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(short value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(int value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(long value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(byte value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(ushort value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(uint value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(ulong value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(float value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(double value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(decimal value) => (TNumeric)Convert.ChangeType(value, Type);
    public TNumeric ToNumeric(IConvertible value) => (TNumeric)Convert.ChangeType(value, Type);


    public bool IsLessThan(TNumeric operandA, TNumeric operandB)
        => ((IComparable<TNumeric>)operandA).CompareTo(operandB) < 0;

    public bool IsLessThanOrEqual(TNumeric operandA, TNumeric operandB)
        => ((IComparable<TNumeric>)operandA).CompareTo(operandB) <= 0;

    public bool IsEqual(TNumeric operandA, TNumeric operandB)
        => ((IComparable<TNumeric>)operandA).CompareTo(operandB) == 0;

    public bool IsGreaterThanOrEqual(TNumeric operandA, TNumeric operandB)
        => ((IComparable<TNumeric>)operandA).CompareTo(operandB) >= 0;

    public bool IsGreaterThan(TNumeric operandA, TNumeric operandB)
        => ((IComparable<TNumeric>)operandA).CompareTo(operandB) > 0;
}

public class OperationsProviderFactory
{
    public static OperationsProviderBase<TNumeric> GetProvider<TNumeric>()
        where TNumeric : IConvertible
    {
        var name = typeof(TNumeric).Name;
        switch (name)
        {
            case nameof(Decimal):
                return new DecimalOperationsProvider() as OperationsProviderBase<TNumeric>;
            case nameof(Single):
                return new FloatOperationsProvider() as OperationsProviderBase<TNumeric>;
            case nameof(Double):
                return new DoubleOperationsProvider() as OperationsProviderBase<TNumeric>;
            default:
                throw new NotImplementedException();
        }
    }
}
public class DecimalOperationsProvider : OperationsProviderBase<decimal>
{
    public override decimal Add(decimal a, decimal b)
        => a + b;

    public override decimal Divide(decimal dividend, decimal divisor)
        => dividend / divisor;


    public override decimal Multiply(decimal multiplicand, decimal multiplier)
        => multiplicand * multiplier;

    public override decimal Subtract(decimal a, decimal b)
       => a - b;
}

public class FloatOperationsProvider : OperationsProviderBase<float>
{
    public override float Add(float a, float b)
        => a + b;

    public override float Divide(float dividend, float divisor)
        => dividend / divisor;


    public override float Multiply(float multiplicand, float multiplier)
        => multiplicand * multiplier;

    public override float Subtract(float a, float b)
       => a - b;
}

public class DoubleOperationsProvider : OperationsProviderBase<double>
{
    public override double Add(double a, double b)
        => a + b;

    public override double Divide(double dividend, double divisor)
        => dividend / divisor;


    public override double Multiply(double multiplicand, double multiplier)
        => multiplicand * multiplier;

    public override double Subtract(double a, double b)
       => a - b;
}

public interface ISma<TNumeric>
{
    int Count { get; }
    void AddSample(TNumeric sample);
    void AddSample(IConvertible sample);
    TNumeric Average { get; }
    TNumeric[] History { get; }
}

public class SmaBase<T> : ISma<T>
    where T : IConvertible
{
    public int Count { get; private set; }
    private int maxLen;
    public T[] History { get; private set; }
    public T Average { get; private set; } = default(T);
    public INumericOperationsProvider<T> OperationsProvider { get; private set; }
    public T SampleRatio { get; private set; }
    public SmaBase(int count, INumericOperationsProvider<T> operationsProvider = null)
    {
        if (operationsProvider == null)
            operationsProvider = OperationsProviderFactory.GetProvider<T>();
        this.Count = count;
        this.maxLen = Count - 1;
        History = new T[count];
        this.OperationsProvider = operationsProvider;
        SampleRatio = OperationsProvider.Divide(OperationsProvider.ToNumeric(1), OperationsProvider.ToNumeric(count));
    }

    public void AddSample(T sample)
    {
        T sampleValue = OperationsProvider.Multiply(SampleRatio, sample);

        if (maxLen==0)
        {
            History[0] = sample;
            Average = sample;
        }
        else
        {
            var remValue = OperationsProvider.Multiply(SampleRatio, History[0]);
            Average = OperationsProvider.Subtract(Average, remValue);
            Average = OperationsProvider.Add(Average, sampleValue);
            Array.Copy(History, 1, History, 0, Count - 1);
            History[maxLen]= sample;
        }
    }


    public void AddSample(IConvertible sample)
        => AddSample(OperationsProvider.ToNumeric(sample));

}
public class SmaOfDecimal : SmaBase<decimal>
{

    public SmaOfDecimal(int count) : base(count)
    {

    }
}

public class MultiTimeFrameSma<TNumeric>
    where TNumeric : IConvertible
{
    public Dictionary<int, SmaBase<TNumeric>> SimpleMovingAverages;
    public Dictionary<int, int> SimpleMovingAverageIndexes;
    public int[] SimpleMovingAverageKeys;
    private List<Action<TNumeric>> SampleActions;
    public TNumeric[] Averages;
    public int TotalSamples = 0;
    public TNumeric LastSample;

    public TNumeric[] History { get; private set; }
    public int MaxSampleLength { get; private set; }
    private int maxLen;
    public MultiTimeFrameSma(int maximumMovingAverage) : this(Enumerable.Range(1, maximumMovingAverage))
    {

    }

    public MultiTimeFrameSma(IEnumerable<int> movingAverageSizes)
    {
        SimpleMovingAverages = new Dictionary<int, SmaBase<TNumeric>>();
        SimpleMovingAverageIndexes = new Dictionary<int, int>();
        SimpleMovingAverageKeys = movingAverageSizes.ToArray();

        MaxSampleLength = SimpleMovingAverageKeys.Max(x => x);
        maxLen = MaxSampleLength - 1;
        History = new TNumeric[MaxSampleLength];//new List<TNumeric>();
        this.SampleActions = new List<Action<TNumeric>>();
        var averages = new List<TNumeric>();
        int i = 0;
        foreach (var smaSize in movingAverageSizes.OrderBy(x => x))
        {
            var sma = new SmaBase<TNumeric>(smaSize);
            SampleActions.Add((x) => { sma.AddSample(x); Averages[SimpleMovingAverageIndexes[sma.Count]] = sma.Average; });
            SimpleMovingAverages.Add(smaSize, sma);
            SimpleMovingAverageIndexes.Add(smaSize, i++);
            averages.Add(sma.Average);
        }
        this.Averages = averages.ToArray();
    }
    public void AddSample(TNumeric value)
    {
        if (maxLen > 0)
        {
            Array.Copy(History, 1, History, 0, maxLen);
            History[maxLen] = value;
        }
        else
        {
            History[0] = value;
        }
        LastSample = value;
        SampleActions.ForEach(action => action(value));
        TotalSamples++;
    }

}

public class MultiTimeFrameCrossOver<TNumeric>
    where TNumeric : IConvertible
{
    public MultiTimeFrameSma<TNumeric> SimpleMovingAverages { get; }
    public TNumeric[] History => SimpleMovingAverages.History;
    public TNumeric[] Averages => SimpleMovingAverages.Averages;
    public int TotalSamples => SimpleMovingAverages.TotalSamples;
    public TNumeric LastSample => SimpleMovingAverages.LastSample;
    private bool[][] matrix;
    public MultiTimeFrameCrossOver(MultiTimeFrameSma<TNumeric> simpleMovingAverages)
    {
        this.SimpleMovingAverages = simpleMovingAverages;
        int length = this.SimpleMovingAverages.Averages.Length;
        this.matrix = SimpleMovingAverages.Averages.Select(avg => SimpleMovingAverages.Averages.Select(x => true).ToArray()).ToArray();

    }
    public void AddSample(TNumeric value)
    {
        SimpleMovingAverages.AddSample(value);
        int max = SimpleMovingAverages.Averages.Length;

        for (var maIndex = 0; maIndex < max; maIndex++)
        {
            IComparable<TNumeric> ma = (IComparable<TNumeric>)SimpleMovingAverages.Averages[maIndex];
            var row = matrix[maIndex];
            for (var otherIndex = 0; otherIndex < max; otherIndex++)
            {
                row[otherIndex] = ma.CompareTo(SimpleMovingAverages.Averages[otherIndex]) >= 0;
            }
        }
    }

    public bool[][] GetMatrix() => matrix;

}

Since none shown my methode will suggest it. I think Linq in most case will perform enough fast without the need create buffer or code complexity. considering a financial _originalDataserie OHLC open higth low close i want sma the closes wich is a Ilist<double>

  double[] smaSerie = new double[_originalDataSeries.Count];
      for (int i = 0; i < _originalDataSeries.Count;i++)
            {
                double sma = double.NaN;
                int period = 50;
              //  var rangeOfinterest = _originalDataSeries.CloseValues.AsParallel().Skip(i - period).Take(period).ToList();
                var rangeOfinterest = _originalDataSeries.CloseValues.Skip(i - period).Take(period).ToList();
                if (rangeOfinterest.Any())
                {
                    sma = rangeOfinterest.Average();                   
                }              
                 smaSerie[i] = sma;

            }

Sma computed 720 point :00:00:00.0075765

i cant tell if the paralel version in coment perform better cuz it would need implement the average as paralel and used for _originalSerie and deal with empty range but it can improved in this way if you have million point to show one shot.But in this case i would go GPU calculation since sma is eligible for this gpu Task

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top