下面你可以看到我的C#方法来计算每个点的布林带(移动平均线,上行带,下行带)。
如您所见,此方法使用2 for循环来计算移动平均值的移动标准差。它曾经包含一个额外的循环来计算过去n个时期的移动平均值。这个我可以通过在循环开始时将新的点值添加到total_average并在循环结束时删除i-n点值来删除。
我现在的问题基本上是:我可以用移动平均线管理的类似方式删除剩余的内部循环吗?
public static void AddBollingerBands(SortedList<DateTime, Dictionary<string, double>> data, int period, int factor)
{
double total_average = 0;
for (int i = 0; i < data.Count(); i++)
{
total_average += data.Values[i]["close"];
if (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"];
}
}
}
答案 0 :(得分:28)
计算平方和的方法的问题在于它和和的平方可以变得非常大,并且它们的差异的计算可能会引入very large error,所以让我们想一些更好的东西。为什么需要这个,请参阅Algorithms for computing variance上的维基百科文章和Theoretical explanation for numerical results上的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)(a-b)
的差异之间的差异是:
Var[x[1],..,x[n]] - Var[x[0],..,x[n-1]]
= (\sum_1^n x[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's method)在开头计算一次均值和方差。之后,每当您必须将窗口x[0]
中的值替换为另一个x[n]
时,您就会更新平均值和方差,如下所示:
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)
答案 1 :(得分:21)
答案是肯定的,你可以。在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
,一旦周期(n
)被再次划分,它是平均平方的-2倍。所以最终的组合公式是:
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<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);
}
}
}
答案 2 :(得分:1)
我已经使用了commons-math(并为该库做出了贡献!)。它是开源的,移植到C#应该很容易作为商店购买的馅饼(你试过从头开始做馅饼!?)。看看:http://commons.apache.org/math/api-3.1.1/index.html。他们有一个StandardDeviation类。去镇上!
答案 3 :(得分:0)
上面已经提供了最重要的信息---但这可能仍然是普遍感兴趣的。
此处提供了一个用于计算移动平均值和标准差的小型Java库: https://github.com/tools4j/meanvar
该实施基于上述Welford方法的变体。已导出删除和替换值的方法,可用于移动值窗口。