web-dev-qa-db-fra.com

Comment calculer efficacement un écart-type mobile

Ci-dessous, vous pouvez voir ma méthode C # pour calculer les bandes de Bollinger pour chaque point (moyenne mobile, bande montante, bande descendante).

Comme vous pouvez le constater, cette méthode utilise 2 boucles for pour calculer l'écart type mobile à l'aide de la moyenne mobile. Auparavant, il contenait une boucle supplémentaire pour calculer la moyenne mobile sur les n dernières périodes. Je pourrais supprimer celui-ci en ajoutant la nouvelle valeur du point à total_average au début de la boucle et en supprimant la valeur du point i-n à la fin de la boucle.

Ma question est maintenant la suivante: puis-je supprimer la boucle interne restante de la même manière que j'ai géré avec la moyenne mobile?

    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
ChrisW

La réponse est oui, vous pouvez. Au milieu des années 80, j'ai développé un tel algorithme (probablement pas original) en FORTRAN pour une application de surveillance et de contrôle de processus. Malheureusement, c'était il y a plus de 25 ans et je ne me souviens pas des formules exactes, mais la technique était une extension de celle utilisée pour les moyennes mobiles, avec des calculs de second ordre plutôt que des calculs linéaires. 


Après avoir examiné votre code, je pense pouvoir comprendre comment je l’ai fait à l’époque. Remarquez comment votre boucle intérieure fait une somme de carrés ?:

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

de la même manière que votre moyenne doit avoir à l'origine une somme de valeurs? Les deux seules différences sont l’ordre (sa puissance 2 au lieu de 1) et le fait que vous soustrayez la moyenne de chaque valeur avant de la mettre au carré. Cela peut paraître inséparable, mais en réalité, ils peuvent être séparés:

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

est

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

qui devient

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

lequel est

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

qui est aussi

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

Maintenant, le premier terme est juste une somme de carrés, vous gérez cela de la même manière que vous faites la somme de valeurs pour la moyenne. Le dernier terme (k^2*n) est simplement la moyenne des carrés au carré de la period. Puisque vous divisez le résultat par la période, vous pouvez simplement ajouter la nouvelle moyenne au carré sans la boucle supplémentaire.

Enfin, dans le deuxième terme (SUM(-2*v[i]) * k), puisque SUM(v[i]) = total = k*n, vous pouvez le changer en ceci:

-2 * k * k * n

ou simplement -2*k^2*n, ce qui correspond à -2 fois la moyenne au carré, une fois que la période (n) est à nouveau divisée. La formule combinée finale est donc:

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

ou

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

(assurez-vous de vérifier la validité de ceci, puisque je le tire de ma tête)

Et incorporer dans votre code devrait ressembler à ceci:

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);
        }
    }
}
21
RBarryYoung

Le problème avec les approches qui calculent la somme des carrés est que celle-ci et le carré des sommes peuvent devenir assez grands, et le calcul de leur différence peut introduire une très grande erreur , alors imaginons quelque chose de mieux. Pour savoir pourquoi cela est nécessaire, voir l'article de Wikipedia sur Algorithmes de calcul de la variance et John Cook sur Explication théorique des résultats numériques )

Premièrement, au lieu de calculer stddev, concentrons-nous sur la variance. Une fois que nous avons la variance, stddev n'est que la racine carrée de la variance.

Supposons que les données se trouvent dans un tableau appelé x; On peut penser que le fait de rouler une fenêtre de taille n en supprimant la valeur de x[0] et en ajoutant la valeur de x[n]. Notons les moyennes de x[0]..x[n-1] et x[1]..x[n] par µ et µ ’respectivement. La différence entre les variantes de x[0]..x[n-1] et de x[1]..x[n] est la suivante: après avoir annulé certains termes et appliqué (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)

Par conséquent, la variance est perturbée par quelque chose qui ne vous oblige pas à conserver la somme des carrés, ce qui est préférable pour la précision numérique.

Vous pouvez calculer la moyenne et la variance une fois au début avec un algorithme approprié ( méthode de Welford ). Ensuite, chaque fois que vous devez remplacer une valeur dans la fenêtre x[0] par un autre x[n], vous mettez à jour la moyenne et la variance comme ceci:

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)
28
Joni

J'ai utilisé commons-math (et contribué à cette bibliothèque!) Pour quelque chose de très similaire à celui-ci. Il est open-source, le portage en C # devrait être simple comme un gâteau acheté en magasin (avez-vous essayé de faire un gâteau à partir de rien!?) Allez voir: http://commons.Apache.org/math/api-3.1.1/index.html . Ils ont une classe StandardDeviation. Aller en ville!

1
Jason

L’information la plus importante a déjà été donnée ci-dessus - mais cela reste peut-être d’intérêt général.

Une petite bibliothèque Java permettant de calculer la moyenne mobile et l'écart type est disponible ici: https://github.com/tools4j/meanvar

La mise en œuvre est basée sur une variante de la méthode de Welford mentionnée ci-dessus. Des méthodes permettant de supprimer et de remplacer des valeurs ont été dérivées et peuvent être utilisées pour déplacer des fenêtres de valeurs.

0
marco