如何在C#中更快地计算出简单的移动平均线?

计算简单移动平均值的最快库/算法是什么? 我写了自己的,但是在33万项十进制数据集上需要太长时间。

  • 期间/时间(ms)
  • 20/300;
  • 60/1500;
  • 120/3500。

这是我的方法的代码:

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; } 

Data.Close[]是固定大小(1 000 000)十进制数组。

您的主要问题是每次迭代都会丢失太多信息。 如果要快速运行,则需要保留与帧长度相同大小的缓冲区。

此代码将运行整个数据集的移动平均值:

(不是真正的C#,但你应该明白)

 decimal buffer[] = new decimal[period]; decimal output[] = new decimal[data.Length]; current_index = 0; for (int i=0; i 

请注意,保持正在运行的cumsum可能很有吸引力,而不是保留整个缓冲区并计算每次迭代的值,但这对于很长的数据长度不起作用,因为累积的总和会增长得太大以至于添加小的附加值将会导致舍入错误。

  public class MovingAverage { private Queue samples = new Queue(); private int windowSize = 16; private Decimal sampleAccumulator; public Decimal Average { get; private set; } ///  /// Computes a new windowed average each time a new sample arrives ///  ///  public void ComputeAverage(Decimal newSample) { sampleAccumulator += newSample; samples.Enqueue(newSample); if (samples.Count > windowSize) { sampleAccumulator -= samples.Dequeue(); } Average = sampleAccumulator / samples.Count; } } 

如果数据是静态的,您可以预处理数组以使移动平均查询非常快:

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

现在移动平均值计算简单快捷:

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

当前(接受的)解决方案包含内部循环。 删除它也会更有效。 你可以在这里看到这是如何实现的:

如何有效地计算移动标准差

目前, Math DotNet库有一个名为RunningStatistics的类,它将为您完成此任务。 如果您只想在最后的“X”项目上执行此操作,请改用MovingStatistics

两者都将仅使用一次通过即可计算运行平均值,方差和标准偏差,而无需存储额外的数据副本。

 // 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; } 

一个Cfunction,13行代码,简单移动平均。 用法示例:

 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 

这是我在我的应用程序中使用的MA。

 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; } 

计算完整个数据系列后,您可以立即获取特定值。

这是我尝试的方式。 但警告我是一个完全的业余爱好者所以这可能是完全错误的。

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

应返回包含数据移动平均值的小数列表。

Queue怎么样?

 using System.Collections.Generic; using System.Linq; public class MovingAverage { private readonly Queue _queue; private readonly int _period; public MovingAverage(int period) { _period = period; _queue = new Queue(period); } public double Compute(decimal x) { if (_queue.Count >= _period) { _queue.Dequeue(); } _queue.Enqueue(x); return _queue.Average(); } } 

用法:

 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)); } 
 ///  /// 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 ///  public class FastMovingAverageDouble { ///  /// Adjust this as you see fit to suit the scenario ///  const int MaximumWindowSize = 100; ///  /// Adjust this as you see fit ///  const int RecalculateEveryXValues = 1000; ///  /// Initializes moving average for specified window size ///  /// 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 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 Buffer = new Queue(); private int m_WindowSize; private double m_MovingAverage = 0d; private double MovingSum = 0d; private bool BufferFull; private int Counter = 0; ///  /// Calculated moving average ///  public double MovingAverage { get { lock (SyncRoot) { return m_MovingAverage; } } } ///  /// Size of moving average window set by constructor during intialization ///  public int WindowSize { get { return m_WindowSize; } } ///  /// Add new value to sequence and recalculate moving average seee  ///  /// New value to be added 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; } } } } 

您不需要保持正在运行的队列。 只需选择窗口中的最新新条目,然后删除旧条目。 请注意,这只使用一个循环,除了总和之外没有额外的存储空间。

  // n is the window for your Simple Moving Average public List GetMovingAverages(List 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(); }