How to calculate simple moving average faster in C#?

后端 未结 14 2508
囚心锁ツ
囚心锁ツ 2020-12-15 06:42

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 /
相关标签:
14条回答
  • 2020-12-15 06:50
    // 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
    
    0 讨论(0)
  • 2020-12-15 06:50
    /// <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;
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-15 06:52

    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

    0 讨论(0)
  • 2020-12-15 06:52

    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();
      }
    
    0 讨论(0)
  • 2020-12-15 06:54

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

    0 讨论(0)
  • 2020-12-15 06:55

    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));
    }
    
    0 讨论(0)
提交回复
热议问题