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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12884600/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 00:48:42  来源:igfitidea点击:

How to calculate simple moving average faster in C#?

c#algorithmfinancialmoving-average

提问by zozed

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.

计算简单移动平均线的最快库/算法是什么?我自己写的,但在 330 000 项十进制数据集上花费的时间太长。

  • period / time(ms)
  • 20 / 300;
  • 60 / 1500;
  • 120 / 3500.
  • 周期/时间(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.

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

采纳答案by Storstamp

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)

(不是真正的 C#,但您应该明白这一点)

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.

请注意,保持运行中的累积总和而不是保持整个缓冲区并计算每次迭代的值可能很诱人,但这不适用于很长的数据长度,因为累积总和会变得​​如此之大,以至于添加小的附加值将导致舍入错误。

回答by nneonneo

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

回答by Sorin

// 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:

一个C函数,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

回答by Miroslav Popov

This is MA I'm using in my app.

这是我在我的应用程序中使用的 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;
}

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

一旦您为整个数据系列计算了它,您就可以立即获取特定值。

回答by Mahalo Quaker

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.

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

回答by Christiaan Wevers

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

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

回答by tcwicks

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

回答by Randolpho

These days, the Math DotNetlibrary has a class called RunningStatisticsthat will do this for you. If you want to do it over the last "X" items only, use MovingStatisticsinstead.

现在,Math DotNet库有一个名为的类RunningStatistics可以为您执行此操作。如果您只想在最后的“X”项上执行此操作,请MovingStatistics改用。

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

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

回答by J.L. Haynes

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

回答by koryakinp

How aboutQueue?

怎么样Queue

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