it-swarm.com.de

Wie berechnet man einen einfachen gleitenden Durchschnitt in C # schneller?

Was ist der schnellste Bibliothek/Algorithmus zur Berechnung eines einfachen gleitenden Durchschnitts? Ich habe mein eigenes geschrieben, aber es dauert zu lange für 330 000 Artikel. 

  • zeitraum/Zeit (ms) 
  • 20/300; 
  • 60/1500; 
  • 120/3500.

Hier ist der Code meiner Methode:

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

Der Data.Close[] ist ein dezimales Array mit fester Größe (1 000 000).

13
zozed

Ihr Hauptproblem besteht darin, dass Sie für jede Iteration zu viele Informationen wegwerfen. Wenn Sie so schnell ablaufen möchten, müssen Sie einen Puffer mit der gleichen Größe wie die Bildlänge beibehalten.

Dieser Code führt gleitende Durchschnitte für den gesamten Datensatz aus:

(Nicht echtes C #, aber Sie sollten die Idee bekommen)

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;

Bitte beachten Sie, dass es möglicherweise verlockend ist, einen laufenden Cumsum beizubehalten, anstatt den gesamten Puffer beizubehalten und den Wert für jede Iteration zu berechnen. Dies funktioniert jedoch nicht für sehr lange Datenlängen, da Ihre kumulierte Summe so groß wird, dass kleine zusätzliche Werte hinzugefügt werden zu Rundungsfehlern führen.

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

Wenn die Daten statisch sind, können Sie das Array vorverarbeiten, um Abfragen für gleitende Durchschnittswerte sehr schnell durchzuführen:

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

Nun ist die Berechnung des gleitenden Durchschnitts einfach und schnell:

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

Heutzutage hat die Math DotNet - Bibliothek eine Klasse namens RunningStatistics , die dies für Sie erledigen wird. Wenn Sie nur die letzten "X" -Elemente ausführen möchten, verwenden Sie stattdessen MovingStatistics

Beide berechnen laufende Durchschnitte, Abweichungen und Standardabweichungen im laufenden Betrieb nur mit einem Durchlauf und ohne zusätzliche Kopien der Daten zu speichern. 

4
Randolpho

Die aktuelle (akzeptierte) Lösung enthält eine innere Schleife. Es wäre effizienter, dies ebenfalls zu entfernen. Wie das geht, sehen Sie hier:

Wie man eine bewegte Standardabweichung effizient berechnet

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

Eine C-Funktion, 13 Codezeilen, einfacher gleitender Durchschnitt . Anwendungsbeispiel:

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

Dies ist MA, den ich in meiner App verwende.

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

Sobald Sie es für die gesamte Datenreihe berechnet haben, können Sie sofort einen bestimmten Wert ermitteln.

1
Miroslav Popov

So habe ich es versucht. Aber Warnung, ich bin ein kompletter Amateur, also kann dies völlig falsch sein.

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

Gibt eine Liste von Dezimalzahlen zurück, die die gleitenden Durchschnitte für Ihre Daten enthalten.

1
Mahalo Quaker

Wie wäre es mit 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();
    }
}

Verwendungszweck:

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

Ich finde die Antworten etwas speicherhungrig und langsam hast du nach schnell gefragt. Fügen Sie zwei Felder hinzu, eines, um die laufende Summe beizubehalten, und eines für die Zeiten, in denen sich der Wert als Durchschnitt geändert hat, als Summe/Anzahl einer Werteliste. Ich habe eine Add-Methode hinzugefügt. Sie können jedoch auch nur Variablen in einer Methode verwenden.

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

um es threadsicher zu machen:

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

Sie müssen keine Warteschlange führen. Wählen Sie einfach den neuesten Eintrag im Fenster aus und legen Sie den älteren Eintrag ab. Beachten Sie, dass dies nur eine Schleife und keinen zusätzlichen Speicher außer einer Summe verwendet.

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