如何有效地计算移动标准差

下面你可以看到我的C#方法来计算每个点的布林带(移动平均线,上行带,下行带)。

如您所见,此方法使用2 for循环来计算移动平均值的移动标准偏差。 它曾经包含一个额外的循环来计算过去n个时期的移动平均值。 这个我可以通过在循环开始时将新点值添加到total_average并在循环结束时删除i-n点值来删除。

我现在的问题基本上是:我可以用移动平均线管理的类似方式删除剩余的内部循环吗?

public static void AddBollingerBands(SortedList<DateTime, Dictionary> data, int period, int factor) { double total_average = 0; for (int i = 0; i = period - 1) { double total_bollinger = 0; double average = total_average / period; for (int x = i; x > (i - period); x--) { total_bollinger += Math.Pow(data.Values[x]["close"] - average, 2); } double stdev = Math.Sqrt(total_bollinger / 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"]; } } } 

答案是肯定的,你可以。 在80年代中期,我在FORTRAN中开发了这样一种算法(可能不是原始算法),用于过程监视和控制应用程序。 不幸的是,那是在25年前,我不记得确切的公式,但该技术是移动平均线的延伸,使用二阶计算而不仅仅是线性计算。


在看了你的代码之后,我认为我可以说我当时是怎么做的。 注意你的内循环是如何形成平方和的?

  for (int x = i; x > (i - period); x--) { total_bollinger += Math.Pow(data.Values[x]["close"] - average, 2); } 

与你的平均值最初必须具有一个值的总和的方式大致相同? 唯一的两个区别是顺序(它的幂2而不是1),并且你在平方之前减去每个值的平均值。 现在这可能看起来不可分割,但事实上它们可以分开:

 SUM(i=1; n){ (v[i] - k)^2 } 

 SUM(i=1..n){v[i]^2 -2*v[i]*k + k^2} 

变成了

 SUM(i=1..n){v[i]^2 -2*v[i]*k} + k^2*n 

是的

 SUM(i=1..n){v[i]^2} + SUM(i=1..n){-2*v[i]*k} + k^2*n 

这也是

 SUM(i=1..n){v[i]^2} + SUM(i=1..n){-2*v[i]}*k + k^2*n 

现在第一个术语只是一个平方和,你可以像处理平均值的总和那样处理它。 最后一个项( k^2*n )只是period的平均平方时间。 由于您无论如何都将结果除以周期,您只需添加新的平均平方而无需额外的循环。

最后,在第二项( SUM(-2*v[i]) * k )中,由于SUM(v[i]) = total = k*n您可以将其更改为:

 -2 * k * k * n 

或者只是-2*k^2*n ,这是平均平方的-2倍,一旦周期( n )再次被分割出来。 所以最终的组合公式是:

 SUM(i=1..n){v[i]^2} - n*k^2 

要么

 SUM(i=1..n){values[i]^2} - period*(average^2) 

(一定要检查一下它的有效性,因为我从头脑中得出它)

并入您的代码应该看起来像这样:

 public static void AddBollingerBands(ref SortedList> 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); } } } 

计算平方和的方法的问题在于它和和的平方可能变得非常大,并且它们的差异的计算可能引入非常大的误差 ,所以让我们想一些更好的东西。 为什么需要这个,请参阅维基百科关于算法计算方差的文章和John Cook关于数值结果的理论解释的文章 )

首先,不是计算stddev而是关注方差。 一旦我们得到方差,stddev就是方差的平方根。

假设数据在一个名为x的数组中; 将一个n大小的窗口滚动一个可以被认为是删除x[0]的值并添加x[n]的值。 设分别用μ和μ’表示x[0]..x[n-1]x[1]..x[n]的平均值。 x[0]..x[n-1]x[1]..x[n]的方差之差在取消某些项并应用(a²-b²) = (a+b)(ab)

 Var[x[1],..,x[n]] - Var[x[0],..,x[n-1]] = (\sum_1^nx[i]² - n µ'²)/(n-1) - (\sum_0^{n-1} x[i]² - n µ²)/(n-1) = (x[n]² - x[0]² - n(µ'² - µ²))/(n-1) = (x[n]-µ' + x[0]-µ)(x[n]-x[0])/(n-1) 

因此,方差受到不需要你保持平方和的东西的干扰,这对于数值精度更好。

您可以使用适当的算法( Welford方法 )在开头计算一次均值和方差。 之后,每次你必须用另一个x[n]替换窗口x[0]的值时,你更新平均值和方差,如下所示:

 new_Avg = Avg + (x[n]-x[0])/n new_Var = Var + (x[n]-new_Avg + x[0]-Avg)(x[n] - x[0])/(n-1) new_StdDev = sqrt(new_Var) 

我已经使用了commons-math(并为这个库做出了贡献!)用于与此类似的东西。 它是开源的,移植到C#应该很容易作为商店购买的馅饼(你试过从头开始做馅饼!?)。 看看: http : //commons.apache.org/math/api-3.1.1/index.html 。 他们有一个StandardDeviation类。 去镇上!

最重要的信息已经在上面给出了 – 但也许这仍然是普遍感兴趣的。

可以在这里找到一个用于计算移动平均值和标准差的小型Java库: https : //github.com/tools4j/meanvar

该实现基于上面提到的Welford方法的变体。 已经导出了可以用于移动值窗口的移除和替换值的方法。