web-dev-qa-db-ja.com

C#で簡単な移動平均をより速く計算する方法は?

単純な移動平均を計算するための最速のライブラリ/アルゴリズムは何ですか?独自に作成しましたが、330 000アイテムの10進数データセットでは時間がかかりすぎます。

  • 期間/時間(ミリ秒)
  • 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)の10進数配列です。

14
zozed

あなたの主な問題は、反復ごとに多くの情報を捨てることです。これを高速で実行する場合は、フレーム長と同じサイズのバッファーを保持する必要があります。

このコードは、データセット全体の移動平均を実行します。

(実際の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;

バッファ全体を保持して各反復の値を計算するのではなく、実行中の累積を維持するのは魅力的かもしれませんが、累積合計が非常に長くなり、小さな追加値を追加すると非常に長いデータ長では機能しないことに注意してください丸め誤差が発生します。

12
Storstamp
    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;
        }
    }
9
J.L. Haynes

データが静的な場合、配列を前処理して、移動平均クエリを非常に高速にすることができます。

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

移動平均の計算が簡単かつ高速になりました。

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

最近、 Math DotNet ライブラリには RunningStatistics と呼ばれるクラスがあり、これがあなたのためにこれを行います。最後の「X」項目のみでそれを行いたい場合は、代わりに MovingStatistics を使用します。

両方とも、実行中の平均、分散、標準偏差を、ワンパスのみで、データの余分なコピーを保存せずにその場で計算します。

4
Randolpho

現在の(受け入れられた)ソリューションには内部ループが含まれています。これも削除する方が効率的です。ここでこれがどのように達成されるかを見ることができます:

移動する標準偏差を効率的に計算する方法

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

1つの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
1
Sorin

これはアプリで使用している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;
}

データ系列全体について計算したら、特定の値をすぐに取得できます。

1
Miroslav Popov

試した方法は次のとおりです。しかし、私は完全なアマチュアなので、これは完全に間違っている可能性があります。

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

データの移動平均を含む小数のリストを返す必要があります。

1
Mahalo Quaker

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

使用法:

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));
}
1
koryakinp

私は、メモリ不足に少し答えを提供し、遅いと思います、あなたは高速を求めました。 2つのフィールドを追加して、1つは現在の合計を保持し、もう1つは平均が値のリストの合計/カウントとして変更された時間を保持します。 Addメソッドを追加しましたが、メソッドで変数を使用することもできます…。

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

スレッドセーフにするには:

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;

}

1
PPann

実行中のキューを保持する必要はありません。ウィンドウへの最新の新しいエントリを選択し、古いエントリを削除します。これは1つのループのみを使用し、合計以外の追加のストレージは使用しないことに注意してください。

  // 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();
  }
1
arviman
/// <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
tcwicks