web-dev-qa-db-ja.com

移動する標準偏差を効率的に計算する方法

以下に、各ポイント(移動平均、アップバンド、ダウンバンド)のボリンジャーバンドを計算する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"];
            }
        }
    }
21

答えは「はい」です。 80年代半ば、私はFORTRANでプロセスモニタリングおよび制御アプリケーション用にこのようなアルゴリズム(おそらくオリジナルではない)を開発しました。残念ながら、それは25年以上前であり、正確な式を覚えていませんが、この手法は移動平均の拡張であり、線形の計算ではなく2次の計算でした。


あなたのコードをいくつか見た後、私はそれが私が当時どのようにそれをしたかを知ることができると思います。内側のループが二乗和を作成していることに注意してください。

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

あなたの平均がもともと合計値を持っていたに違いないのと同じように?唯一の2つの違いは、次数(1ではなく2のべき乗)であり、2乗する前に各値の平均を差し引くことです。今では分離できないように見えるかもしれませんが、実際にはそれらを分離することができます:

_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の2乗の平均のちょうど倍数です。とにかく結果を期間で除算するので、余分なループなしで新しい平均二乗を追加できます。

最後に、2番目の用語(SUM(-2*v[i]) * k)では、SUM(v[i]) = total = k*nなので、次のように変更できます。

_-2 * k * k * n
_

または、期間(n)が再度分割されると、平均二乗の-2倍である_-2*k^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);
        }
    }
}
_
24
RBarryYoung

二乗和を計算するアプローチの問題は、二乗和と二乗和が非常に大きくなる可能性があり、それらの差の計算により very大きなエラー なので、もっと良いものを考えましょう。これが必要な理由については、Wikipediaの記事 分散計算のアルゴリズム およびJohn Cookの 数値結果の理論的説明 )を参照してください。

まず、stddevを計算する代わりに、分散に注目しましょう。分散があると、stddevは分散の平方根になります。

データがxという配列にあるとします。 nサイズのウィンドウを1つ回転させることは、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)

したがって、分散は、平方和を維持する必要がないものによって乱されます。これは、数値の精度を高めるために適しています。

適切なアルゴリズム( ウェルフォードの方法 )を使用すると、最初に平均と分散を一度計算できます。その後、ウィンドウ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)
29
Joni

Commons-mathを使用して(そしてそのライブラリに貢献しました!)、これに非常に似たものを使用しました。それはオープンソースであり、C#への移植は、店で買ったパイと同じくらい簡単です(パイを最初から作ってみましたか?)。確認してください: http://commons.Apache.org/math/api-3.1.1/index.html 。彼らはStandardDeviationクラスを持っています。町に行く!

1
Jason

最も重要な情報はすでに上記で説明されています---しかし、おそらくこれはまだ一般的な関心事です。

小さなJava移動平均と標準偏差を計算するライブラリがここにあります: https://github.com/tools4j/meanvar

実装は、上記のウェルフォードの方法の変形に基づいています。値ウィンドウを移動するために使用できる値を削除および置換するメソッドが導出されました。

0
marco