【问题标题】:Calculate a moving Standard Deviation计算移动标准差
【发布时间】:2015-03-25 20:13:23
【问题描述】:

我在*上找到了以下代码sn-p,但我遇到了stdev变成NaN的问题。任何想法如何解决这个问题?

public static void AddBollingerBands(ref SortedList<DateTime, Dictionary<string, double>> data, int period, int factor)
{
    double total_average = 0;
    double total_squares = 0;

    for (int i = 0; i < data.Count(); i++)
    {
        total_average += data.Values[i]["close"];
        total_squares += Math.Pow(data.Values[i]["close"], 2);

        if (i >= period - 1)
        {
            double total_bollinger = 0;
            double average = total_average / period;

            double stdev = Math.Sqrt((total_squares - Math.Pow(total_average,2)/period) / period);
            data.Values[i]["bollinger_average"] = average;
            data.Values[i]["bollinger_top"] = average + factor * stdev;
            data.Values[i]["bollinger_bottom"] = average - factor * stdev;

            total_average -= data.Values[i - period + 1]["close"];
            total_squares -= Math.Pow(data.Values[i - period + 1]["close"], 2);
        }
    }
}

【问题讨论】:

  • 你确定period 永远不会是0吗?自己解决这个问题。它在你的循环中什么时候变成 NaN(即在多少次迭代之后)。是正无穷还是负无穷?在 var stdev = ... 行的计算中使用的任何其他值是否在此时变为 NaN?
  • 在我看来,您需要的不是这个特定问题的答案,而是花一些时间学习调试代码。检测代码并找出导致问题的操作?缩小问题范围?
  • 谢谢,但经过几次迭代后,参数具有以下值:total_squares = 18.42483;总平均 = 19.19628;周期 = 20;以 NaN 结束
  • 使用这些数字:total_squares - Math.Pow(total_average,2)/period-0.00002829192。 IE。你取平方根的负数。这将导致 NaN。
  • 顺便说一句 - 参数列表中的ref 似乎是不必要的。

标签: c# standard-deviation


【解决方案1】:

在进行增量/移动平均和标准偏差计算时,最好使用数值更稳定的变体。一种方法是使用 Knuth 算法,如下面的代码块所示:

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

然后您可以在代码示例中以下列方式使用它:

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

【讨论】:

  • 非常感谢,你真的救了我的命。祝你有美好的一天:))
  • @endeffects 很高兴这有帮助。一个忠告:确保你付出了一些努力来了解这个网站和社区是如何运作的。当您提出问题或遇到其他人对您有帮助的问题或答案时该怎么办。
【解决方案2】:

要让stdev 变成 NaN,这个作业肯定有问题:

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

您不能除以零,因此请确保 period 未设置为该值。 最简单的解决方法是在调用此行之前打印出每个变量,看看是否有东西已经是 NaN 或者在数学上不可用

【讨论】:

    最近更新 更多