Calculate a moving Standard Deviation

北战南征 提交于 2019-11-29 15:34:08

A numerically more stable variant is preferable when doing incremental / moving average and standard deviation calculations. One way to do this is using Knuth's algorithm, as shown in the code block below:

public class MovingAverageCalculator
{
    public MovingAverageCalculator(int period)
    {
        _period = period;
        _window = new double[period];
    }

    public double Average 
    {
        get { return _average; }
    }

    public double StandardDeviation
    {
        get 
        {
            var variance = Variance;
            if (variance >= double.Epsilon)
            {
                var sd = Math.Sqrt(variance);
                return double.IsNaN(sd) ? 0.0 : sd;
            }
            return 0.0;
        }
    }

    public double Variance
    {
        get 
        { 
            var n = N;
            return n > 1 ? _variance_sum / (n - 1) : 0.0; 
        }
    }

    public bool HasFullPeriod
    {
        get { return _num_added >= _period; }
    }

    public IEnumerable<double> Observations
    {
        get { return _window.Take(N); }
    }

    public int N
    {
        get { return Math.Min(_num_added, _period); }
    }

    public void AddObservation(double observation)
    {
        // Window is treated as a circular buffer.
        var ndx = _num_added % _period;
        var old = _window[ndx];     // get value to remove from window
        _window[ndx] = observation; // add new observation in its place.
        _num_added++;

        // Update average and standard deviation using deltas
        var old_avg = _average;
        if (_num_added <= _period)
        {
            var delta = observation - old_avg;
            _average += delta / _num_added;
            _variance_sum += (delta * (observation - _average));
        } 
        else // use delta vs removed observation.
        {
            var delta = observation - old;
            _average += delta / _period;
            _variance_sum += (delta * ((observation - _average) + (old - old_avg)));
        }
    }

    private readonly int _period;
    private readonly double[] _window;
    private int _num_added;
    private double _average;
    private double _variance_sum;
}

You could then use it in the following manner in your code example:

public static void AddBollingerBands(ref SortedList<DateTime, Dictionary<string, double>> data, int period, int factor)
{
    var moving_avg = new MovingAverageCalculator(period);
    for (int i = 0; i < data.Count(); i++)
    {
        moving_avg.AddObservation(data.Values[i]["close"]);
        if (moving_avg.HasFullPeriod)
        {
            var average = moving_avg.Average;
            var limit = factor * moving_avg.StandardDeviation;
            data.Values[i]["bollinger_average"] = average;
            data.Values[i]["bollinger_top"] = average + limit;
            data.Values[i]["bollinger_bottom"] = average - limit;
        }
    }
}

For stdev to become NaN something has to be going wrong in this assignment:

double stdev = Math.Sqrt((total_squares - Math.Pow(total_average,2)/period) / period);

You can't divide by zero, so make sure period isn't set to that. Easiest way to solve is to print out every variable before this line is called and see if something is already NaN or is mathematically unusable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!