How to calculate simple moving average faster in C#?
Asked Answered
L

16

27

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.

Limewater answered 14/10, 2012 at 17:18 Comment(3)
What are you using the moving averages for? If you are averaging over a sliding window then you can incrementally update the average which makes it much faster. If you are calculating random windows, you can preprocess the array into a cumulative sum array to make moving averages fast. The optimization depends on your use case, here.Loge
A cumulative sum over a large array will lead to loss of precision unless using a numeric library with arbitrary precision.Percussionist
decimal has 96 bits of precision, which will perform a lot better than double or float for such a cumulative sum calculation.Loge
P
19

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.

Percussionist answered 14/10, 2012 at 21:44 Comment(11)
Note: "rounding errors" of this nature are only a problem for floating-point calculations, not for fixed-point (decimal).Caption
But decimal in C# is floating point (128bit). 28-29 significant digits. But the errors might be small enough. I guess it depends on what is calculated. If this is about currency, I would use a fixed point library.Percussionist
decimal has a 96 bit mantissa, but crucially the floating point base is 10 not 2. Thus, if all you are doing is manipulating values with a bounded number of digits after the decimal point (10 decimal places is plenty for most financial calculations), decimal has no error.Loge
I guess, as long as data[i]/cumsum > 5e-34Percussionist
Hmm, I confess that I did not know that C# decimal was floating-point. Good to know...Caption
Minor improvement: the decimal arrays should be defined as decimal[] buffer not decimal buffer[]Bumbling
Minor point @Loge . Incorrect on decimal has no error. For example for a moving average with a window size of 3 and values of 1 (using the sliding window method). Just run the following code: decimal Value; decimal Divisor; decimal Calc; decimal Sum; Value = 1m; Divisor = 3m; Calc = (Value / Divisor); Sum = (Calc + Calc + Calc); bool NoError = (Sum == 1m);Spicule
@tcwicks: you'll note that I said "bounded number of digits after the decimal point". 1/3 has an unbounded number of digits in decimal, hence the error.Loge
@Loge Round off error, overflow error, etc... The decimal data type even though fixed point with 10 decimal places. Still ends up with cumulative error depending on the number of consecutive division operations. Reason is because real world division operations often end up with more than 10 decimals and the error in this becomes cumulative. Real world division is never bounded even with 10 decimal places.Spicule
Note that this will make the values with an index < period move towards 0.Chauncey
This approach works but is not as efficient as it could be. It will get slower as the window size increases because of the inner loop. It's possible to do this incrementally without iterating over the sliding window each time it moves over by one.Promethean
U
28
    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;
        }
    }
Unsegregated answered 1/6, 2017 at 22:55 Comment(1)
Converted this to a PowerShell script so I can estimate how much time is left when calling a web service. gist.github.com/michaellwest/d7712f97bd3fba6109ea2369e50347c6Hebbe
P
19

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.

Percussionist answered 14/10, 2012 at 21:44 Comment(11)
Note: "rounding errors" of this nature are only a problem for floating-point calculations, not for fixed-point (decimal).Caption
But decimal in C# is floating point (128bit). 28-29 significant digits. But the errors might be small enough. I guess it depends on what is calculated. If this is about currency, I would use a fixed point library.Percussionist
decimal has a 96 bit mantissa, but crucially the floating point base is 10 not 2. Thus, if all you are doing is manipulating values with a bounded number of digits after the decimal point (10 decimal places is plenty for most financial calculations), decimal has no error.Loge
I guess, as long as data[i]/cumsum > 5e-34Percussionist
Hmm, I confess that I did not know that C# decimal was floating-point. Good to know...Caption
Minor improvement: the decimal arrays should be defined as decimal[] buffer not decimal buffer[]Bumbling
Minor point @Loge . Incorrect on decimal has no error. For example for a moving average with a window size of 3 and values of 1 (using the sliding window method). Just run the following code: decimal Value; decimal Divisor; decimal Calc; decimal Sum; Value = 1m; Divisor = 3m; Calc = (Value / Divisor); Sum = (Calc + Calc + Calc); bool NoError = (Sum == 1m);Spicule
@tcwicks: you'll note that I said "bounded number of digits after the decimal point". 1/3 has an unbounded number of digits in decimal, hence the error.Loge
@Loge Round off error, overflow error, etc... The decimal data type even though fixed point with 10 decimal places. Still ends up with cumulative error depending on the number of consecutive division operations. Reason is because real world division operations often end up with more than 10 decimals and the error in this becomes cumulative. Real world division is never bounded even with 10 decimal places.Spicule
Note that this will make the values with an index < period move towards 0.Chauncey
This approach works but is not as efficient as it could be. It will get slower as the window size increases because of the inner loop. It's possible to do this incrementally without iterating over the sliding window each time it moves over by one.Promethean
L
11

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.

Lobbyism answered 2/2, 2017 at 19:14 Comment(0)
L
5

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];
}
Loge answered 14/10, 2012 at 17:37 Comment(0)
A
3

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();
  }
Alburg answered 16/6, 2018 at 21:10 Comment(1)
You can git rid of the local double array also. Instead of storing the value in the array, call yield return runningTotal / n; in the loop. You'll need to change the return type to IEnumerable<double>.Tudela
S
2

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

Sauerbraten answered 19/2, 2016 at 21:13 Comment(0)
C
2

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;

}

Cedrickceevah answered 31/7, 2019 at 19:42 Comment(1)
Note that this answer is just a simple average calculation. A moving average behaves differently.Handclasp
F
1
// 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
Fact answered 21/12, 2013 at 9:12 Comment(1)
This looks similar to what TA-Lib is doing. Seems optimal.Directorial
G
1

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.

Griceldagrid answered 26/6, 2014 at 21:36 Comment(0)
P
1

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.

Pico answered 19/2, 2016 at 21:6 Comment(0)
S
1
/// <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;
            }
        }
    }
}
Spicule answered 18/1, 2017 at 14:36 Comment(0)
G
1

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));
}
Gail answered 12/12, 2017 at 20:31 Comment(0)
S
0

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();

Scrivens answered 25/11, 2019 at 14:43 Comment(0)
A
0

Here's my MovingAverage class implementation, that 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 = Enumerable.Repeat(double.NaN, windowSize).ToArray();
    }

    public int WindowSize => _mask + 1;

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

    public double Average => _values.TakeWhile(double.IsFinite)
        .DefaultIfEmpty(0)
        .Average();

    public double Min => _values.TakeWhile(double.IsFinite)
        .DefaultIfEmpty(0)
        .Min();

    public double Max => _values.TakeWhile(double.IsFinite)
        .DefaultIfEmpty(0)
        .Max();
}

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.WindowSize, Is.EqualTo(4));

        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((0, 0, 0)));
        Assert.That(sut.Add(2), Is.False);
        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((2, 2, 2)));
        Assert.That(sut.Add(4), Is.False);
        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((2, 3, 4)));
        Assert.That(sut.Add(0), Is.False);
        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((0, 2, 4)));
        Assert.That(sut.Add(6), Is.True);
        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((0, 3, 6)));
        Assert.That(sut.Add(6), Is.False);
        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((0, 4, 6)));
        Assert.That(sut.Add(0), Is.False);
        Assert.That(sut.Add(0), Is.False);
        Assert.That(sut.Add(0), Is.True);
        Assert.That(sut.Add(0), Is.False);
        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((0, 0, 0)));
        Assert.That(sut.Add(10), Is.False);
        Assert.That(sut.Add(10), Is.False);
        Assert.That(sut.Add(10), Is.True);
        Assert.That(sut.Add(10), Is.False);
        Assert.That((sut.Min, sut.Average, sut.Max), Is.EqualTo((10, 10, 10)));
    }

    [Test]
    public void Should_check_windowsize_param()
    {
        Assert.That(() => new MovingAverage(3), Throws.ArgumentException);
    }
}
Aphanite answered 6/12, 2020 at 17:42 Comment(0)
S
0

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;

}
Soul answered 4/5, 2021 at 17:42 Comment(0)
D
0

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

Delacroix answered 18/6, 2021 at 12:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.