Teoria e implementazione di indicatori adattivi avanzati con MQL5
Introduzione
Questo articolo è basato su due eccellenti libri di John F. Ehlers: "Rocket Science for Traders" e "Сybernetic Analysis for Stock and Futures". L'approccio insolito all'analisi di mercato che utilizza metodi di elaborazione del segnale digitale e l'adozione di numeri complessi per il riconoscimento del ciclo di mercato mi ha fatto approfondire questo argomento e successivamente implementare con MQL5 tre indicatori adattivi presentati da J.F.Ehlers.
Questo articolo descriverà la teoria alla base degli indicatori adattivi e la loro implementazione con MQL5. Gli indicatori adattivi saranno confrontati con le loro controparti non adattive.
Numeri complessi e fasori per misurare i cicli di mercato
La nozione di teoria dei numeri complessi può essere abbastanza sconcertante per i lettori che hanno un background non ingegneristico, quindi consiglio di approfondire la teoria su wiki e guardare un tutorial sulle operazioni con i numeri complessi prima di leggere questo articolo.
Fasore
Il fasore o vettore di fase è un vettore che mostra ampiezza e fase di un ciclo. Secondo la formula di Eulero, un'onda sinusoidale può essere rappresentata come una somma di due componenti numeriche complesse. Si prega di osservare il fasore rotante raffigurante un ciclo di onde sinusoidale di seguito.
Vedendo questa animazione per la prima volta potresti essere perplesso su come leggere correttamente la relazione di fasore a un ciclo. Devi capire che bisogna cambiare idea per riconoscere un ciclo non come una normale forma d'onda visibile nella parte sinistra dell'animazione, ma come il fasore rotante sulla destra.
All'inizio può essere difficile da immaginare, ma ho trovato un modo per pensarla in questa maniera: la rotazione completa di un fasore è di 360 gradi o radiante, lo stesso vale per un ciclo completo. L'angolo corrente di un fasore indica in quale parte del ciclo (fase) ci troviamo. L'asse Y rappresenta l'ampiezza di un ciclo in una data fase.
Il fasore può essere suddiviso in due componenti: Componente InPhase (coseno) e componente Quadratura (seno). Una spiegazione dettagliata della derivazione di tali componenti è disponibile nel Capitolo 6 "Trasformazioni di Hilbert" del libro "Rocket Science for Traders". Se qualcuno è interessato, è invitato a seguire attentamente questo capitolo.
Per ora è sufficiente concentrarsi sul fatto che per il calcolo degli indicatori adattivi dobbiamo convertire il segnale analitico (forma d'onda) in un segnale complesso composto da due componenti. Come possiamo raggiungere questo obiettivo? Ho menzionato la trasformata di Hilbert? Sì, certo. La trasformata di Hilbert è capace proprio di questo.
Periodo del ciclo di misurazione
Al fine di rendere la trasformata di Hilbert più pratica per i trader, John Ehlers ha troncato nel suo libro la serie della trasformata di Hilbert in quattro elementi.
L'equazione per la componente Quadratura è:
e l'equazione per il componente InPhase è ritardata di prezzo di tre barre:
Dopo aver calcolato le componenti InPhase e Quadratura è possibile ricavare il calcolo della fase differenziale dall'angolo di fase misurato per la barra corrente e dall'angolo di fase misurato una barra fa. La fase per la barra corrente è e la fase per la barra precedente è . Utilizzo dell'identità trigonometrica:
otteniamo l'equazione per la fase differenziale chiamata DeltaFase.
Ehlers ha posto ulteriori vincoli sulla variabile DeltaFase: il risultato non può essere negativo e DeltaFase è limitato a <0.1, 1.1> radianti (ovvero un ciclo tra 6 e 63 bar). Sembra che il DeltaPhase misurato su dati reali sia molto rumoroso, quindi ha bisogno di essere livellato.
Il miglior metodo di livellamento sui dati appuntiti è il filtro mediano, quindi una mediana di cinque campioni di DeltaFase forma la variabile MedianDelta. MedianDelta diviso viene utilizzato per calcolare il ciclo dominante, il ciclo di mercato che stiamo cercando.
Durante i test di sviluppo si è scoperto che c'è una distorsione di circa 0,5 nella misurazione che deve essere rimossa e che è stato aggiunto un termine di compensazione per rimuovere tale pregiudizio. Infine, il Ciclo Dominante viene appianato due volte dall'EMA con valori alfa pari rispettivamente a 0,33 e 0,15. Consiglio vivamente di leggere il libro per vedere la robustezza dell'algoritmo applicato a un'onda sinusoidale il cui periodo di ciclo è aumentato gradualmente da 6 a 40.
Adesso che possiedi le conoscenze teoriche, siamo pronti a implementare l'indicatore CyclePeriod con MQL5.
Indicatore del periodo di ciclo
L'indicatore è composto da due linee: la linea del ciclo che mostra il periodo del ciclo e una linea di innesco, che è fondamentalmente una linea di ciclo ritardata di una barra. Se si segue la descrizione nella sezione "Periodo del ciclo di misurazione" e il codice sorgente nella funzione OnCalculate(), si sarà facilmente in grado di correlare quali linee sono responsabili della misurazione del periodo di ciclo.
//+------------------------------------------------------------------+ //| CyclePeriod.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "CyclePeriod indicator - described by John F. Ehlers" #property description "in \"Cybernetic Analysis for Stocks and Futures\"" #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; //double Price[]; double Q1[]; // Quadrature component double I1[]; // InPhase component double DeltaPhase[]; double InstPeriod[]; double CyclePeriod[]; input double InpAlpha=0.07; // alpha //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping ArraySetAsSeries(Cycle,true); ArraySetAsSeries(CyclePeriod,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); //ArraySetAsSeries(Price,true); SetIndexBuffer(0,CyclePeriod,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- long tickCnt[1]; int i; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); double DC, MedianDelta; Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- last counted bar will be recounted int nLimit=rates_total-prev_calculated-1; // start index for calculations ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); //ArrayResize(Price,Bars(_Symbol,_Period)); ArrayResize(CyclePeriod,Bars(_Symbol,_Period)); ArrayResize(InstPeriod,Bars(_Symbol,_Period)); ArrayResize(Q1,Bars(_Symbol,_Period)); ArrayResize(I1,Bars(_Symbol,_Period)); ArrayResize(DeltaPhase,Bars(_Symbol,_Period)); if (nLimit>rates_total-7) // adjust for last bars nLimit=rates_total-7; for(i=nLimit;i>=0 && !IsStopped();i--) { Smooth[i] = (Price(i)+2*Price(i+1)+2*Price(i+2)+Price(i+3))/6.0; if (i<rates_total-7) { Cycle[i] = (1.0-0.5*InpAlpha) * (1.0-0.5*InpAlpha) * (Smooth[i]-2.0*Smooth[i+1]+Smooth[i+2]) +2.0*(1.0-InpAlpha)*Cycle[i+1]-(1.0-InpAlpha)*(1.0-InpAlpha)*Cycle[i+2]; } else { Cycle[i]=(Price(i)-2.0*Price(i+1)+Price(i+2))/4.0; } Q1[i] = (0.0962*Cycle[i]+0.5769*Cycle[i+2]-0.5769*Cycle[i+4]-0.0962*Cycle[i+6])*(0.5+0.08*InstPeriod[i+1]); I1[i] = Cycle[i+3]; if (Q1[i]!=0.0 && Q1[i+1]!=0.0) DeltaPhase[i] = (I1[i]/Q1[i]-I1[i+1]/Q1[i+1])/(1.0+I1[i]*I1[i+1]/(Q1[i]*Q1[i+1])); if (DeltaPhase[i] < 0.1) DeltaPhase[i] = 0.1; if (DeltaPhase[i] > 0.9) DeltaPhase[i] = 0.9; MedianDelta = Median(DeltaPhase, i, 5); if (MedianDelta == 0.0) DC = 15.0; else DC = (6.28318/MedianDelta) + 0.5; InstPeriod[i] = 0.33 * DC + 0.67 * InstPeriod[i+1]; CyclePeriod[i] = 0.15 * InstPeriod[i] + 0.85 * CyclePeriod[i+1]; Trigger[i] = CyclePeriod[i+1]; } } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ double Median(double& arr[], int idx, int m_len) { double MedianArr[]; int copied; double result = 0.0; ArraySetAsSeries(MedianArr, true); ArrayResize(MedianArr, m_len); copied = ArrayCopy(MedianArr, arr, 0, idx, m_len); if (copied == m_len) { ArraySort(MedianArr); if (m_len %2 == 0) result = (MedianArr[m_len/2] + MedianArr[(m_len/2)+1])/2.0; else result = MedianArr[m_len / 2]; } else Print(__FILE__+__FUNCTION__+"median error - wrong number of elements copied."); return result; }
Possiamo testarlo collegandolo a qualsiasi grafico: funzionerà per qualsiasi sicurezza e per qualsiasi periodo di tempo.
Guarda lo screenshot qui sotto.
Con questo indicatore nel nostro spazio di lavoro siamo in grado di implementare una nuova generazione di indicatori adattivi, ovvero degli indicatori che si adattano a un periodo di ciclo corrente del mercato.
Indicatore Cyber Cycle
L'indicatore Cyber Cycle è un filtro passa-alto tratto da "Сybernetic analysis for stocks and futures". Questo filtro lascia solo il componente della modalità ciclo dalle serie temporali.
Inoltre, i componenti del ciclo a due e tre barre vengono estratti dal risultato livellandolo con un filtro passa-basso a risposta di impulso finito.
Il codice dell'indicatore MQL5 per questo e altri indicatori nell'articolo adattato dal linguaggio EFL (Tradestation) viene descritto nel libro.
//+------------------------------------------------------------------+ //| CyberCycle.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "CyberCycle indicator - described by John F. Ehlers" #property description "in \"Cybernetic Analysis for Stocks and Futures\"" #property description "This indicator is available for free download." #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; input double InpAlpha=0.07; // alpha //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- long tickCnt[1]; int i; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- last counted bar will be recounted int nLimit=rates_total-prev_calculated-1; // start index for calculations ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); if(nLimit>rates_total-4) // adjust for last bars nLimit=rates_total-4; for(i=nLimit;i>=0 && !IsStopped();i--) { Smooth[i]=(Price(i)+2*Price(i+1)+2*Price(i+2)+Price(i+3))/6.0; if(i<rates_total-5) { Cycle[i]=(1.0-0.5*InpAlpha) *(1.0-0.5*InpAlpha) *(Smooth[i]-2.0*Smooth[i+1]+Smooth[i+2]) +2.0*(1.0-InpAlpha)*Cycle[i+1]-(1.0-InpAlpha)*(1.0-InpAlpha)*Cycle[i+2]; } else { Cycle[i]=(Price(i)-2.0*Price(i+1)+Price(i+2))/4.0; } //Print(__FILE__+__FUNCTION__+" received values: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Uno screenshot dell'indicatore è incollato di seguito.
Come puoi notare, tutti gli indicatori in questo articolo avranno un aspetto simile, ma implementano algoritmi molto diversi.
Il metodo di trading originale per questo indicatore è semplice: acquista quando la linea del ciclo passa sopra la linea di innesco. Vendi quando la linea del ciclo passa sotto la linea di innesco. Se lo vuoi, sei anche incoraggiato a implementare la tua strategia e un modulo di segnali di trading utilizzando questo indicatore.
Indicatore adattivo Cyber Cycle
Lo scopo di questo articolo è presentare un modo per rendere gli indicatori adattivi, cioè come calcolarli con input dinamici del periodo di ciclo invece di un'impostazione statica. Per raggiungere questo obiettivo, dobbiamo connetterci all'indicatore CyclePeriod per leggere il periodo corrente e, successivamente, utilizzare questa lettura nella funzione OnCalculate().
All'inizio abbiamo bisogno di ottenere l’handle dell'indicatore:
hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("CyclePeriod indicator not available!"); return(-1); }
e poi leggerlo all'interno della funzione OnCalculate():
int copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } alpha1 = 2.0/(CyclePeriod[0]+1.0);
L'alfa mobile esponenziale è correlato alla lunghezza di una media mobile semplice dall'equazione . Nell'indicatore adattivo Cyber Cycle, Ehlers ha usato il periodo del ciclo dominante come lunghezza nel calcolo del coefficiente alfa1.
Il codice sorgente completo è disponibile di seguito:
//+------------------------------------------------------------------+ //| AdaptiveCyberCycle.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "Adaptive CyberCycle indicator - described by John F. Ehlers" #property description "in \"Cybernetic Analysis for Stocks and Futures\"" #property description "This indicator is available for free download." #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; int hCyclePeriod; input double InpAlpha=0.07; // alpha for Cycle Period //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("CyclePeriod indicator not available!"); return(-1); } return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- long tickCnt[1]; int i; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); double CyclePeriod[1],alpha1; Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- last counted bar will be recounted int nLimit=rates_total-prev_calculated-1; // start index for calculations ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); if(nLimit>rates_total-4) // adjust for last bars nLimit=rates_total-4; for(i=nLimit;i>=0 && !IsStopped();i--) { Smooth[i]=(Price(i)+2*Price(i+1)+2*Price(i+2)+Price(i+3))/6.0; int copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } alpha1 = 2.0/(CyclePeriod[0]+1.0); //Print(alpha1); //Print(CyclePeriod[0]); if(i>=0) { Cycle[i]=(1.0-0.5*alpha1) *(1.0-0.5*alpha1) *(Smooth[i]-2.0*Smooth[i+1]+Smooth[i+2]) +2.0*(1.0-alpha1)*Cycle[i+1]-(1.0-alpha1)*(1.0-alpha1)*Cycle[i+2]; //Print("Smooth["+IntegerToString(i)+"]="+DoubleToString(Smooth[i])+" Cycle["+IntegerToString(i)+"]="+DoubleToString(Cycle[i])); } else { Cycle[i]=(Price(i)-2.0*Price(i+1)+Price(i+2))/4.0; } //Print(__FILE__+__FUNCTION__+" received values: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Guarda l'indicatore sullo screenshot allegato.
Il nostro primo indicatore adattivo è pronto. Secondo il libro, deve essere più reattivo rispetto alla versione non adattiva.
I segnali di acquisto e vendita devono verificarsi spesso una barra prima rispetto alla versione non adattiva.
Possiamo procedere con altri due esempi di indicatori: dovrebbero essere sufficienti per capire lo schema sulla creazione di indicatori adattivi.
Indicatore del baricentro
Quando si parla di baricentro per qualsiasi oggetto fisico si intende il suo punto di equilibrio. L'idea di introdurre questo concetto nel trading è nata dall'osservazione di come i ritardi dei vari filtri fossero correlati ai coefficienti del filtro.
Per la SMA , media mobile semplice, tutti i coefficienti sono uguali e il baricentro è nel mezzo.
Per la WMA, media mobile ponderata, i prezzi più recenti sono più importanti di quelli più vecchi. Per essere precisi, i coefficienti di WMA descrivono il contorno del triangolo. Il baricentro del triangolo è in un terzo della lunghezza della base del triangolo. L'equazione più generica derivata per il calcolo del baricentro su una data finestra di osservazione è la seguente:
La posizione del punto di equilibrio è la somma del prodotto di posizione all'interno della finestra nelle volte in cui il prezzo in questa posizione (+1 nell'equazione è stato introdotto perché contiamo da 0 a N e non da 1 a N) è diviso per la somma dei prezzi all'interno della finestra.
La caratteristica principale del CG è che diminuisce e aumenta insieme alle oscillazioni dei prezzi ed essenzialmente è un oscillatore a zero lag.
Di seguito è riportato il codice sorgente:
//+------------------------------------------------------------------+ //| CenterOfGravity.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "CG indicator - described by John F. Ehlers" #property description "in \"Cybernetic Analysis for Stocks and Futures\"" #property description "This indicator is available for free download." #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; input double InpAlpha=0.07; // alpha input int InpCGLength=10; //CG window size //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- long tickCnt[1]; int i; double Num, Denom; // Numerator and Denominator for CG int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- last counted bar will be recounted int nLimit=rates_total-prev_calculated-1; // start index for calculations ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); if(nLimit>rates_total-InpCGLength) // adjust for last bars nLimit=rates_total-InpCGLength; for(i=nLimit;i>=0 && !IsStopped();i--) { Num = 0.0; Denom = 0.0; for (int count=0; count<InpCGLength; count++) { Num += (1.0+count)*Price(i+count); Denom += Price(i+count); } if (Denom != 0.0) Cycle[i] = -Num/Denom+(InpCGLength+1.0)/2.0; else Cycle[i] = 0.0; //Print(__FILE__+__FUNCTION__+" received values: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Uno screenshot qui sotto. Da notare il piccolo lag.
Indicatore adattivo del baricentro
L'oscillatore CG trova il centro di gravità su una finestra temporale di lunghezza fissa. L'oscillatore adattivo CG utilizza metà del periodo del ciclo dominante misurato come lunghezza della finestra dinamica. Per estrarre metà del periodo di ciclo dominante misurato è stato utilizzato il seguente codice.
copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } CG_len = floor(CyclePeriod[0]/2.0);
Di seguito è riportato il codice sorgente completo dell'indicatore. Puoi confrontarlo con la sua versione non adattiva e con l'indicatore Adaptive Cyber Cycle per vedere le somiglianze.
//+------------------------------------------------------------------+ //| AdaptiveCenterOfGravity.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "Adaptive CG indicator - described by John F. Ehlers" #property description "in \"Cybernetic Analysis for Stocks and Futures\"" #property description "This indicator is available for free download." #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; int hCyclePeriod; input double InpAlpha=0.07; // alpha //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("CyclePeriod indicator not available!"); return(-1); } return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- long tickCnt[1]; int i, copied; double Num,Denom; // Numerator and Denominator for CG double CG_len; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); double CyclePeriod[1]; Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- last counted bar will be recounted int nLimit=rates_total-prev_calculated-1; // start index for calculations ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); copied=CopyBuffer(hCyclePeriod,0,0,1,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } if(nLimit>rates_total-int(CyclePeriod[0])-2) // adjust for last bars nLimit=rates_total-int(CyclePeriod[0])-2; for(i=nLimit;i>=0 && !IsStopped();i--) { copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } CG_len = floor(CyclePeriod[0]/2.0); //Print("CG_len="+DoubleToString(CG_len)); Num=0.0; Denom=0.0; for(int count=0; count<int(CG_len); count++) { Num+=(1.0+count)*Price(i+count); Denom+=Price(i+count); } if(Denom!=0.0) Cycle[i]=-Num/Denom+(CG_len+1.0)/2.0; else Cycle[i]=0.0; //Print(__FILE__+__FUNCTION__+" received values: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Osserva lo screenshot dell'indicatore adattivo CG incollato di seguito.
Indicatore RVI
RVI è l'acronimo di Relative Vigor Index. La teoria alla base di questo indicatore è che i prezzi tendono ad avere un prezzo di chiusura superiore al prezzo aperto nei mercati rialzisti e un prezzo di chiusura inferiore al prezzo aperto nei mercati ribassisti.
Il vigore della mossa è misurato dalla differenza tra il prezzo di chiusura e il prezzo aperto rispetto all'intervallo di trading giornaliero.
Questo è un indicatore abbastanza noto per molti utenti di MetaTrader in quanto ora è incorporato nell'installazione di MetaTrader 5.
Sto ancora incollando il codice sorgente come riferimento:
//+------------------------------------------------------------------+ //| RVI.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property description "Relative Vigor Index" //--- indicator settings #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 2 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "RVI" #property indicator_label2 "Signal" //--- input parameters input int InpRVIPeriod=10; // Period //--- indicator buffers double ExtRVIBuffer[]; double ExtSignalBuffer[]; //--- #define TRIANGLE_PERIOD 3 #define AVERAGE_PERIOD (TRIANGLE_PERIOD*2) //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ void OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,ExtRVIBuffer,INDICATOR_DATA); SetIndexBuffer(1,ExtSignalBuffer,INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS,3); //--- sets first bar from what index will be drawn PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+TRIANGLE_PERIOD); PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+AVERAGE_PERIOD); //--- name for DataWindow and indicator subwindow label IndicatorSetString(INDICATOR_SHORTNAME,"RVI("+string(InpRVIPeriod)+")"); PlotIndexSetString(0,PLOT_LABEL,"RVI("+string(InpRVIPeriod)+")"); PlotIndexSetString(1,PLOT_LABEL,"Signal("+string(InpRVIPeriod)+")"); //--- initialization done } //+------------------------------------------------------------------+ //| Relative Vigor Index | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[]) { int i,j,nLimit; double dValueUp,dValueDown,dNum,dDeNum; //--- check for bars count if(rates_total<=InpRVIPeriod+AVERAGE_PERIOD+2) return(0); // exit with zero result //--- check for possible errors if(prev_calculated<0) return(0); // exit with zero result //--- last counted bar will be recounted nLimit=InpRVIPeriod+2; if(prev_calculated>InpRVIPeriod+TRIANGLE_PERIOD+2) nLimit=prev_calculated-1; //--- set empty value for uncalculated bars if(prev_calculated==0) { for(i=0;i<InpRVIPeriod+TRIANGLE_PERIOD;i++) ExtRVIBuffer[i]=0.0; for(i=0;i<InpRVIPeriod+AVERAGE_PERIOD;i++) ExtSignalBuffer[i]=0.0; } //--- RVI counted in the 1-st buffer for(i=nLimit;i<rates_total && !IsStopped();i++) { dNum=0.0; dDeNum=0.0; for(j=i;j>i-InpRVIPeriod;j--) { dValueUp=Close[j]-Open[j]+2*(Close[j-1]-Open[j-1])+2*(Close[j-2]-Open[j-2])+Close[j-3]-Open[j-3]; dValueDown=High[j]-Low[j]+2*(High[j-1]-Low[j-1])+2*(High[j-2]-Low[j-2])+High[j-3]-Low[j-3]; dNum+=dValueUp; dDeNum+=dValueDown; } if(dDeNum!=0.0) ExtRVIBuffer[i]=dNum/dDeNum; else ExtRVIBuffer[i]=dNum; } //--- signal line counted in the 2-nd buffer nLimit=InpRVIPeriod+TRIANGLE_PERIOD+2; if(prev_calculated>InpRVIPeriod+AVERAGE_PERIOD+2) nLimit=prev_calculated-1; for(i=nLimit;i<rates_total && !IsStopped();i++) ExtSignalBuffer[i]=(ExtRVIBuffer[i]+2*ExtRVIBuffer[i-1]+2*ExtRVIBuffer[i-2]+ExtRVIBuffer[i-3])/AVERAGE_PERIOD; //--- OnCalculate done. Return new prev_calculated. return(rates_total); } //+------------------------------------------------------------------+
Di seguito viene incollato uno screenshot dell'indicatore RVI standard con il punto impostato su 10 predefinito.
Indicatore RVI adattivo
Come con i due indicatori adattivi precedenti, dobbiamo estrarre la misurazione del ciclo dominante dall'indicatore CyclePeriod e applicarla al periodo RVI. La variabile "Length" viene calcolata come media mobile ponderata di quattro barre del periodo:
copied=CopyBuffer(hCyclePeriod,0,0,4,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } AdaptiveRVIPeriod = int(floor((4*CyclePeriod[0]+3*CyclePeriod[1]+2*CyclePeriod[2]+CyclePeriod[3])/20.0));
Di seguito è riportato il codice sorgente completo dell'indicatore adattivo RVI.
//+------------------------------------------------------------------+ //| Adaptive RVI.mq5 | //| Based on RVI by MetaQuotes Software Corp. | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property copyright "2011, Adaptive version Investeo.pl" #property link "https://www.mql5.com" #property description "Adaptive Relative Vigor Index" //--- indicator settings #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 2 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "AdaptiveRVI" #property indicator_label2 "Signal" #define Price(i) ((high[i]+low[i])/2.0) //--- input parameters input int InpRVIPeriod=10; // Initial RVI Period //--- indicator buffers double ExtRVIBuffer[]; double ExtSignalBuffer[]; //--- int hCyclePeriod; input double InpAlpha=0.07; // alpha for Cycle Period int AdaptiveRVIPeriod; #define TRIANGLE_PERIOD 3 #define AVERAGE_PERIOD (TRIANGLE_PERIOD*2) //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,ExtRVIBuffer,INDICATOR_DATA); SetIndexBuffer(1,ExtSignalBuffer,INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS,3); hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("CyclePeriod indicator not available!"); return(-1); } //--- sets first bar from what index will be drawn PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+TRIANGLE_PERIOD); PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+AVERAGE_PERIOD); //--- name for DataWindow and indicator subwindow label IndicatorSetString(INDICATOR_SHORTNAME,"AdaptiveRVI"); PlotIndexSetString(0,PLOT_LABEL,"AdaptiveRVI"); PlotIndexSetString(1,PLOT_LABEL,"Signal"); //--- initialization done return 0; } //+------------------------------------------------------------------+ //| Relative Vigor Index | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[]) { int i,j,nLimit; double dValueUp,dValueDown,dNum,dDeNum; double CyclePeriod[4]; int copied; copied=CopyBuffer(hCyclePeriod,0,0,4,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } AdaptiveRVIPeriod = int(floor((4*CyclePeriod[0]+3*CyclePeriod[1]+2*CyclePeriod[2]+CyclePeriod[3])/20.0)); //--- check for bars count if(rates_total<=AdaptiveRVIPeriod+AVERAGE_PERIOD+2) return(0); // exit with zero result //--- check for possible errors if(prev_calculated<0) return(0); // exit with zero result //--- last counted bar will be recounted nLimit=AdaptiveRVIPeriod+2; if(prev_calculated>AdaptiveRVIPeriod+TRIANGLE_PERIOD+2) nLimit=prev_calculated-1; //--- set empty value for uncalculated bars if(prev_calculated==0) { for(i=0;i<AdaptiveRVIPeriod+TRIANGLE_PERIOD;i++) ExtRVIBuffer[i]=0.0; for(i=0;i<AdaptiveRVIPeriod+AVERAGE_PERIOD;i++) ExtSignalBuffer[i]=0.0; } //--- RVI counted in the 1-st buffer for(i=nLimit;i<rates_total && !IsStopped();i++) { copied=CopyBuffer(hCyclePeriod,0,rates_total-i-1,4,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } AdaptiveRVIPeriod = int(floor((4*CyclePeriod[0]+3*CyclePeriod[1]+2*CyclePeriod[2]+CyclePeriod[3])/20.0)); dNum=0.0; dDeNum=0.0; for(j=i;j>MathMax(i-AdaptiveRVIPeriod, 3);j--) { //Print("rates_total="+IntegerToString(rates_total)+" nLimit="+IntegerToString(nLimit)+ // " AdaptiveRVIPeriod="+IntegerToString(AdaptiveRVIPeriod)+" j="+IntegerToString(j)); dValueUp=Close[j]-Open[j]+2*(Close[j-1]-Open[j-1])+2*(Close[j-2]-Open[j-2])+Close[j-3]-Open[j-3]; dValueDown=High[j]-Low[j]+2*(High[j-1]-Low[j-1])+2*(High[j-2]-Low[j-2])+High[j-3]-Low[j-3]; dNum+=dValueUp; dDeNum+=dValueDown; } if(dDeNum!=0.0) ExtRVIBuffer[i]=dNum/dDeNum; else ExtRVIBuffer[i]=dNum; } //--- signal line counted in the 2-nd buffer nLimit=AdaptiveRVIPeriod+TRIANGLE_PERIOD+2; if(prev_calculated>AdaptiveRVIPeriod+AVERAGE_PERIOD+2) nLimit=prev_calculated-1; for(i=nLimit;i<rates_total && !IsStopped();i++) ExtSignalBuffer[i]=(ExtRVIBuffer[i]+2*ExtRVIBuffer[i-1]+2*ExtRVIBuffer[i-2]+ExtRVIBuffer[i-3])/AVERAGE_PERIOD; //--- OnCalculate done. Return new prev_calculated. return(rates_total); } //+------------------------------------------------------------------+
Uno screenshot dell'indicatore adattivo RVI con lunghezza della finestra dinamica:
Conclusione
Questo articolo ha presentato tre indicatori tecnici adattivi di comportamento e l'implementazione con MQL5.
Il meccanismo per l'implementazione degli indicatori adattivi dovrebbe essere chiaramente comprensibile dopo aver letto l'articolo. Tutti gli indicatori descritti sono disponibili negli allegati.
L'autore incoraggia a sperimentare e a costruire altri indicatori adattivi rispetto a quelli già disponibili.
Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/288
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso