Как рассчитать простую скользящую среднюю быстрее в C#?
Какова самая быстрая библиотека / алгоритм для вычисления простой скользящей средней? Я написал свой собственный, но он занимает слишком много времени на 330 000 элементов десятичного набора данных.
- период / время(МС)
- 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).
11 ответов
ваша главная проблема заключается в том, что вы выбрасываете слишком много информации для каждой итерации. Если вы хотите запустить это быстро, вам нужно сохранить буфер того же размера, что и длина кадра.
этот код будет запускать скользящие средние для всего набора данных:
(Не настоящий 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;
обратите внимание, что может возникнуть соблазн сохранить текущий cumsum вместо сохранения всего буфера и вычисления значения для каждой итерации, но это не работает для очень длинных данных, накопленная сумма будет расти настолько большой, что добавление небольших дополнительных значений приведет к ошибкам округления.
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;
}
}
Если данные статические, вы можете предварительно обработать массив, чтобы сделать запросы скользящей средней очень быстро:
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];
}
текущее (принятое) решение содержит внутренний цикл. Было бы более эффективно удалить и это. Вы можете увидеть, как это достигается здесь:
в эти дни 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;
}
одна функция 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
Это Ма, которую я использую в своем приложении.
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<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;
}
должен возвращать список десятичных знаков, содержащий скользящие средние для ваших данных.
как о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));
}
/// <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;
}
}
}
}
вам не нужно, чтобы сохранить очередь. Просто выберите последнюю новую запись в окне и отбросьте старую запись. Обратите внимание, что это использует только один цикл и никакого дополнительного хранилища, кроме суммы.
// 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();
}