2013-01-31 66 views
18

下面您可以看到我的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"]; 
      } 
     } 
    } 

回答

20

答案是肯定的,你可以。在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<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); 
     } 
    } 
} 
+4

非常感謝!我瞎了眼睛。 你只會忘記減少最後的total_squares: total_squares - = Math.Pow(data。值[i - period + 1] [「close」],2); – ChrisW

+2

http://www.johndcook.com/blog/standard_deviation/ – odyth

+0

@odyth很好的參考!我沒有意識到這是在Knuth。在80年代我寫這本書之前的幾年,我實際上已經閱讀過TAoCP,現在我想知道我是不是下意識地剽竊了它。 – RBarryYoung

1

我已經用commons-math(並且貢獻給那個庫!)來做一些非常相似的事情。它是開源的,移植到C#應該很容易,就像商店買的派(你嘗試從頭開始製作派!)。檢查出來:http://commons.apache.org/math/api-3.1.1/index.html。他們有一個StandardDeviation類。去鎮上!

+0

謝謝,但我不認爲我需要一個像這樣的簡單計算的數學庫。 – ChrisW

+0

不客氣!對不起,我沒有找到你要找的答案。我絕對不是想要建議移植整個圖書館!只需要最少的代碼,這應該是幾百行左右。請注意,我不知道apache對該代碼有哪些法律/版權限制,因此您必須檢查該代碼。如果你追求它,這裏是[鏈接](http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/stat/descriptive /moment/StandardDeviation.java?revision=1416643&view=markup)。那麼+ Variance + FastMath? –

25

問題與計算方法的總和ares和它的平方和可以變得相當大,並且它們的差異的計算可能會引入一個very large error,所以讓我們想一些更好的東西。爲什麼這是必要的,請參閱維基百科有關Algorithms for computing variance和約翰庫克的文章Theoretical explanation for numerical results

首先,不是計算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[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) 
+1

謝謝你。我用它作爲CLR中C#實現的基礎。我發現在實踐中,您可能會更新'new_Var'是一個_very_小的負數,並且sqrt失敗。我介紹了一個'if'來限制這個值爲零。不知道,但穩定。發生這種情況時,我的窗口中的每個值都具有相同的值(我使用的窗口大小爲20,所涉及的值爲0.5,以防有人想要重現此問題。) –

0

最重要的信息已經在上面給出---但也許這仍然是一般興趣。

一個微小的Java庫來計算移動平均值和標準偏差,請訪問: https://github.com/tools4j/meanvar

的實現是基於上述維爾福德的方法的一種變體。已經推導出可以用於移動值窗口的方法來移除和替換值。