2013-01-31 52 views
18

Di seguito è possibile vedere il metodo C# per calcolare le bande di Bollinger per ciascun punto (media mobile, banda in su, banda in giù).Come calcolare in modo efficiente una deviazione standard in movimento

Come si può vedere questo metodo usa 2 cicli per calcolare la deviazione standard mobile utilizzando la media mobile. Utilizzato per contenere un ciclo aggiuntivo per calcolare la media mobile negli ultimi n periodi. Questo potrei rimuovere aggiungendo il nuovo valore punto a total_average all'inizio del ciclo e rimuovendo il valore del punto i-n alla fine del ciclo.

La mia domanda ora è fondamentalmente: Posso rimuovere il circuito interno rimanente in modo simile a me gestito con la media 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"]; 
      } 
     } 
    } 

risposta

20

La risposta è sì, è possibile. A metà degli anni '80 sviluppai proprio un tale algoritmo (probabilmente non originale) in FORTRAN per un'applicazione di monitoraggio e controllo dei processi. Sfortunatamente, questo era più di 25 anni fa e non ricordo le formule esatte, ma la tecnica era un'estensione di quella per le medie mobili, con calcoli del secondo ordine invece di quelli lineari.


Dopo aver esaminato il tuo codice, penso di poter scoprire come ho fatto allora. Si noti come il vostro ciclo interno sta facendo la somma dei quadrati ?:

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

più o meno allo stesso modo in cui il vostro media deve avere in origine aveva una somma di valori? Le uniche due differenze sono l'ordine (la sua potenza 2 invece di 1) e che stai sottragliando la media di ciascun valore prima di metterlo in quadratura. Ora che potrebbe sembrare inseparabili, ma in realtà possono essere separati:

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

è

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

che diventa

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

che è

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

che è anche

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

Ora il primo termine è solo una somma di quadrati, lo gestisci nello stesso modo in cui esegui la somma dei valori per la media. L'ultimo termine (k^2*n) è solo il tempo medio quadrato del period. Poiché dividi comunque il risultato per il periodo, puoi semplicemente aggiungere la nuova media al quadrato senza il ciclo aggiuntivo.

Infine, nel secondo termine (SUM(-2*v[i]) * k), dal momento che SUM(v[i]) = total = k*n quindi è possibile cambiare in questo modo:

-2 * k * k * n 

o semplicemente -2*k^2*n, che è -2 volte la media al quadrato, una volta che il periodo di (n) è diviso di nuovo.Così la formula finale combinato è:

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

o

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

(assicurati di controllare la validità di questo, dal momento che sto derivandolo fuori dalla parte superiore della mia testa)

e incorporando nel tuo codice dovrebbe apparire qualcosa del genere:

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

Grazie mille! Stavo fissando cieco su questo. Alla fine si è solo dimenticato di ridurre total_squares: total_squares - = Math.Pow (dati.Valori [i - periodo + 1] ["chiudi"], 2); – ChrisW

+2

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

+0

@odyth Bella referenza! Non avevo capito che era a Knuth. In realtà avevo letto TAoCP diversi anni prima di averlo scritto negli anni '80, e ora mi chiedo se l'ho inconsciamente plagiato. – RBarryYoung

1

Ho usato commons-math (e ho contribuito a quella libreria!) Per qualcosa di molto simile a questo. È open source, il porting su C# dovrebbe essere facile come torta comprato dal negozio (hai provato a fare una torta da zero !?). Dategli un'occhiata: http://commons.apache.org/math/api-3.1.1/index.html. Hanno una classe StandardDeviation. Andare in città!

+0

Grazie, ma non credo di aver bisogno di una libreria matematica per un calcolo semplice come questo. – ChrisW

+0

Prego! Scusa se non ho avuto la risposta che stai cercando. Sicuramente non intendevo suggerire il porting dell'intera libreria! Solo il codice minimo necessario, che dovrebbe essere di poche centinaia di righe o giù di lì. Nota che non ho idea di quali restrizioni legali/copyright apache abbiano su quel codice, quindi dovresti verificarlo. Nel caso in cui lo perseguiate, ecco il [collegamento] (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). In modo che + Variance + FastMath? –

25

Il problema con approcci che calcolano la somma di squ è che esso e il quadrato delle somme possono diventare piuttosto grandi, e il calcolo della loro differenza può introdurre un very large error, quindi pensiamo a qualcosa di meglio. Perchè è necessario, consultare l'articolo di Wikipedia su Algorithms for computing variance e John Cook su Theoretical explanation for numerical results)

Innanzitutto, anziché calcolare lo stddev concentriamoci sulla varianza. Una volta ottenuta la varianza, stddev è solo la radice quadrata della varianza.

Supponiamo che i dati siano in una matrice denominata x; la rotazione di una finestra di dimensioni n di una può essere considerata come la rimozione del valore di x[0] e l'aggiunta del valore di x[n]. Indichiamo le medie di x[0]..x[n-1] e x[1]..x[n] rispettivamente di μ e μ '. La differenza tra le varianze di x[0]..x[n-1] e x[1]..x[n] è, dopo l'annullamento alcuni termini e l'applicazione (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) 

Pertanto la varianza è perturbato da qualcosa che non richiede di mantenere la somma dei quadrati, che è meglio per precisione numerica.

È possibile calcolare la media e la varianza una volta all'inizio con un algoritmo corretto (Welford's method). Dopo di che, ogni volta che si deve sostituire un valore nella finestra x[0] da un altro x[n] si aggiorna la media e la varianza in questo modo:

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

Grazie per questo. L'ho usato come base per un'implementazione in C# per il CLR. Ho scoperto che, in pratica, è possibile aggiornare in modo tale che 'new_Var' è un numero negativo _very_ piccolo, e lo sqrt fallisce. Ho introdotto un 'se' per limitare il valore a zero per questo caso. Non un'idea, ma stabile. Ciò si è verificato quando ogni valore nella mia finestra aveva lo stesso valore (ho usato una dimensione della finestra di 20 e il valore in questione era 0,5, nel caso qualcuno volesse provare a riprodurlo.) –

0

Informazioni Principali è già stato dato sopra --- ma forse questo è ancora di interesse generale.

Una piccola libreria Java per calcolare lo spostamento media e deviazione standard è disponibile qui: https://github.com/tools4j/meanvar

L'implementazione è basata su una variante del metodo di Welford di cui sopra. Sono stati ricavati metodi per rimuovere e sostituire i valori che possono essere utilizzati per spostare le finestre dei valori.