English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Implementazione degli indicatori come classi mediante esempi di Zigzag e ATR

Implementazione degli indicatori come classi mediante esempi di Zigzag e ATR

MetaTrader 5Indicatori | 17 dicembre 2021, 15:13
73 0
Aleksandr Chugunov
Aleksandr Chugunov

A cosa ci serve?

MetaQuotes Software Corp. ha rivisto il concetto di lavoro tramite indicatori personalizzati nella nuova quinta versione del client terminal MetaTrader. Ad oggi vengono eseguiti molto più velocemente; esiste un solo esempio per ciascun indicatore con parametri di input univoci, quindi viene calcolato una sola volta indipendentemente dall'utilizzo delle sue copie, anche su dieci grafici di un simbolo, ecc.

Ma il funzionamento di un algoritmo rimane invariato. Alla perdita della connessione a un server o alla significativa sincronizzazione della cronologia, il valore prev_calculated (o IndicatorCounted() per MetaTrader 4) viene azzerato, il che porta a un ricalcolo completo dell'indicatore per l'intera cronologia (gli sviluppatori l'hanno fatto intenzionalmente per garantire la correttezza dei valori degli indicatori in ogni circostanza). Ci sono diverse cose che possono influenzare la velocità di calcolo degli indicatori:

  • Periodo ampio: rate_total;
  • Calcoli complessi che consumano risorse;
  • Utilizzo di diversi simboli e periodi;
  • Computer debole;

Più elementi sono applicabili alla tua situazione, più attuale è per te il problema del ricalcolo dell'indicatore per l'intera cronologia. Inoltre, la situazione peggiora con un canale mediocre per la trasmissione di informazioni.

Naturalmente, puoi limitare la profondità del calcolo dell'indicatore utilizzando un parametro di input aggiuntivo, ma c'è una nuance quando si utilizzano gli indicatori iCustom. Il numero massimo di barre utilizzate da qualsiasi grafico o indicatore personalizzato è impostato sull'ambito globale per l'intero terminale. La memoria è allocata per ogni buffer di un indicatore personalizzato ed è limitata solo da TERMINAL_MAXBARS.

Tuttavia, c'è un'aggiunta significativa: se limiti il numero massimo di barre calcolate direttamente nell'algoritmo dell'indicatore (ad esempio, utilizzando un parametro di input o direttamente nel codice), la memoria verrà allocata dinamicamente all'arrivo di ogni nuova barra (aumenta gradualmente fino al limite TERMINAL_MAXBARS specificato [o un po' di più: questo algoritmo dipende completamente dagli sviluppatori, i quali possono cambiarlo nelle versioni successive]).


Modi per evitare il ricalcolo dell'indicatore per l'intera cronologia

Per il momento, ecco i seguenti modi per risolvere questo problema:
  1. Chiedi a MetaQuotes di rivedere questo problema a livello di piattaforma
  2. Crea una classe separata per l'implementazione di un analogo di prev_calculated

C'era un'altra variante come presupposto che si potesse costruire proprio nell'indicatore un algoritmo di calcolo di prev_calculated, ma pare che MetaTrader 5, a differenza di MetaTrader 4, "cancelli" tutti i buffer dell'indicatore quando si azzera prev_calculated (cioè esegue forzatamente l'azzeramento di tutti gli array degli indicatori; non è possibile controllarlo, poiché questo comportamento è implementato a livello di piattaforma).

Analizziamo ciascuna variante separatamente.

  • La prima variante dipende solo dagli sviluppatori. Forse lo prenderanno in considerazione dopo la pubblicazione dell'articolo. E, forse, l'implementazione di un meccanismo a tutti gli effetti influenzerà fortemente le prestazioni del blocco di calcolo degli indicatori personalizzati (tuttavia, questo meccanismo può essere implementato in maniera facoltativa) e lasceranno tutto com'è ora.
  • La seconda variante. Creazione di una classe speciale che sarà responsabile dell'implementazione di un analogo di prev_calculated. Possiamo usarla sia in un indicatore personalizzato (solo per ottenere i valori prev_calcolati) sia in un fornitore di dati da utilizzare in un Expert Advisor (o script) insieme a una classe sviluppata separatamente per il calcolo dell'indicatore personalizzato necessario.


Vantaggi e svantaggi della seconda variante di risoluzione del problema

Vantaggi:
  • volume fisso di memoria richiesta tramite singola allocazione di memoria per un array dinamico con organizzazione di un accesso ad anello agli elementi dell'array;
  • sincronizzazione e calcolo dell'indicatore quando si utilizza una classe separata per il suo calcolo su richiesta (senza utilizzare semafori, flag, eventi, ecc.);
  • quando si utilizza una chiamata separata per il calcolo dell'indicatore, il risultato del ricalcolo viene restituito in forma estesa (ad esempio: non sono state apportate modifiche, è stato modificato solo l'ultimo raggio, è stato aggiunto un nuovo raggio, ecc.).
Svantaggi:
  • necessità di memorizzare una propria copia della cronologia dei prezzi, la quale viene utilizzata per il calcolo dei valori di un indicatore;
  • necessità di una sincronizzazione manuale della cronologia con la cronologia del terminale mediante operazioni logiche di confronto dei dati.


Creazione della classe CCustPrevCalculated per l'implementazione di un analogo di prev_calculated

L'implementazione della classe stessa non contiene nulla di interessante da descrivere. L'algoritmo considera sia l'espansione della cronologia su entrambi i lati sia la sua possibile "interruzione" dal lato sinistro. Anche questo algoritmo può elaborare l'inserimento della cronologia all'interno dei dati calcolati (vale per MetaTrader 4, ma su MetaTrader 5 non ci ho ancora avuto a che fare). Il codice sorgente della classe si trova nel file CustPrevCalculated.mqh.

Lascia che ti parli delle cose più importanti.


Creazione di un accesso ad anello agli elementi dell'array

Per creare questa classe, utilizzeremo un metodo non convenzionale: l'accesso ad anello agli elementi dell'array per l'allocazione unica di memoria per l'array e per evitare procedure eccessive di copia degli array. Prendiamolo in considerazione con l'esempio di 5 elementi:


Accesso tramite anello agli elementi dell'array


 
Inizialmente lavoriamo con l'array, la cui numerazione inizia con 0. Ma cosa dobbiamo fare se abbiamo bisogno di aggiungere il valore successivo mantenendo la dimensione dell'array (aggiungere una nuova barra)? Ci sono due modi:
  • copiare le celle di memoria 2-5 rispettivamente nelle celle 1-4; quindi avremo la cella di memoria 5 vuota;
  • modificare l'indicizzazione dell'array senza modificare le informazioni memorizzate in esso (indirizzamento wraparound).

Per implementare la seconda variante, abbiamo bisogno di una variabile che chiamiamo DataStartInd; memorizzerà la posizione dell'indice zero dell'array. Per comodità di ulteriori calcoli, la sua numerazione corrisponderà alla consueta indicizzazione di un array (cioè partirà da zero). Nella variabile BarsLimit andremo a memorizzare il numero di elementi dell'array. Pertanto, l'indirizzo reale dell'elemento dell'array per l'indice virtuale 'I' verrà calcolato utilizzando la seguente formula semplice:

  • (DataStartInd+I) % BarsLimit – per la normale numerazione
  • (DataStartInd+DataBarsCount-1-I) % BarsLimit – per l'indirizzamento come nelle serie temporali
La variabile DataBarsCount memorizza il numero di celle di memoria effettivamente utilizzate (possiamo usare solo 3 celle su 5, ad esempio).


Algoritmi di sincronizzazione della cronologia

Per conto mio, ho selezionato e implementato tre modalità di funzionamento dell'algoritmo di sincronizzazione di una copia della cronologia (cronologia locale) con la cronologia nel client terminal:
  • CPCHSM_NotSynch – la sincronizzazione della cronologia locale non viene eseguita per le barre già formate (a tuo rischio e responsabilità). In realtà, questa modalità può essere utilizzata liberamente per un indicatore dove una deviazione insignificante dei valori dei prezzi non può influenzare fortemente la precisione dei calcoli (MA, ADX, ecc.). Questa modalità può essere fatale per ZigZag, ad esempio, dove un eccesso di un picco rispetto a un altro è significativo.
  • CPCHSM_Normal – la cronologia locale è sincronizzata ad ogni nuova barra dall'algoritmo descritto di seguito.
  • CPCHSM_Paranoid – la cronologia locale viene sincronizzata ad ogni chiamata della funzione di sincronizzazione dati descritta di seguito.

Il meccanismo di sincronizzazione stesso si basa su un altro parametro impostato da un programmatore: HSMinute (memorizzato come HistorySynchSecond). Supponiamo che un Dealer Center possa correggere solo gli ultimi minuti HSMinute della cronologia. Se durante la sincronizzazione di quel periodo non viene rilevata alcuna differenza, la cronologia viene considerata identica e il confronto viene interrotto. Se viene rilevata una differenza, l'intera cronologia viene controllata e corretta.

Inoltre, l'algoritmo consente di controllare solo prezzi/spread/volumi dalla struttura MqlRates specificata durante l'inizializzazione. Ad esempio, per disegnare ZigZag abbiamo bisogno solo dei prezzi High e Low.


Uso pratico della classe CCustPrevCalculated

Per inizializzare la classe CCustPrevCalculated dobbiamo chiamare la funzione InitData(), che restituisce 'true' in caso di successo:
CCustPrevCalculated CustPrevCalculated;
CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
Per sincronizzare la cronologia dobbiamo chiamare la funzione PrepareData():
CPCPrepareDataResultCode resData;
resData = CustPrevCalculated.PrepareData();

Varianti di valori che possono essere restituiti dalla funzione PrepareData():

enum CPCPrepareDataResultCode
  {
   CPCPDRC_NoData,                     // Returned when there is no data for calculation (not prepared by the server)
   CPCPDRC_FullInitialization,         // Full initialization of the array has been performed
   CPCPDRC_Synch,                      // Synchronization with adding new bars has been performed
   CPCPDRC_SynchOnlyLastBar,           // Synchronization of only the last bar has been performed (possible cutting of the history)
   CPCPDRC_NoRecountNotRequired        // Recalculation has not been performed, since the data was not changed
  };


Funzioni della classe CCustPrevCalculated per l'accesso ai dati

Nota: per velocizzare i calcoli sono esclusi i controlli per array overflow. Per essere più precisi, verranno restituiti valori errati se l'indice non è corretto.

Nome
Scopo
 uint GetDataBarsCount()
 Restituisce il numero di barre disponibili
 uint GetDataBarsCalculated()
 Restituisce il numero di barre invariate
 uint GetDataStartInd()
 Restituisce l'indice per l'accesso a tutto tondo (per indicatori personalizzati)
 bool GetDataBarsCuttingLeft()
 Restituisce il risultato del taglio delle barre da sinistra
 double GetDataOpen(int shift, bool AsSeries)
 Restituisce 'Open' per la barra di scorrimento
 double GetDataHigh(int shift, bool AsSeries)
 Restituisce 'High' per la barra di scorrimento
 double GetDataLow(int shift, bool AsSeries)
 Restituisce Low per la barra di spostamento
 double GetDataClose(int shift, bool AsSeries)
 Restituisce 'Close' per la barra di scorrimento
 datetime GetDataTime(int shift, bool AsSeries)
 Restituisce 'Time' per la barra di scorrimento
 long GetDataTick_volume(int shift, bool AsSeries)
 Restituisce 'Tick_volume' per la barra di scorrimento
 long GetDataReal_volume(int shift, bool AsSeries)
 Restituisce 'Real_volume' per la barra di scorrimento
 int GetDataSpread(int shift, bool AsSeries)
 Restituisce 'Spread' per la barra di scorrimento


Esempi di ottimizzazione ulteriore della classe CCustPrevCalculated

  • Rifiuto da parte di MqlRates con il passaggio a più array (determinati da uno specifico scopo; diminuisce i requisiti di memoria, ma aumenta il carico sul numero di chiamate di copia degli array).
  • Dividendo ciascuna funzione di accesso in due indipendenti per un uso definito con un certo tipo di indicizzazione dell’array (rifiutando dal parametro «bool AsSeries»). Il vantaggio è solo nella condizione logica «if (AsSeries)».


Creazione del CCustZigZagPPC per il calcolo dell'indicatore personalizzato ZigZag sulla base dei dati della classe CCustPrevCalculated

Questo algoritmo si basa sull'indicatore personalizzato Professional ZigZag. Il codice sorgente della classe si trova nel file ZigZags.mqh; inoltre, la libreria OutsideBar.mqh viene utilizzata per lavorare con le barre esterne.

Creiamo una struttura separata per la descrizione di una barra del nostro indicatore:

struct ZZBar
  {
   double UP, DN;                      // Buffers of the ZigZag indicator
   OrderFormationBarHighLow OB;       // Buffer for caching of an external bar
  };

Inoltre determiniamo il risultato di restituzione dei calcoli della classe:

enum CPCZZResultCode
  {
   CPCZZRC_NotInitialized,             // Class is no initialized
   CPCZZRC_NoData,                     // Faield to receive data (including the external bar)
   CPCZZRC_NotChanged,                 // No changes of ZZ rays
   CPCZZRC_Changed                     // ZZ rays changed
  };

Per inizializzare la classe CCustZigZagPPC dobbiamo chiamare la funzione Init() una volta; restituisce 'true' in caso di successo:

CCustZigZagPPC ZZ1;
ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);

Per i calcoli dell'indicatore occorre avviare l'aggiornamento dei dati sulla base dei dati precedentemente calcolati della classe CCustPrevCalculated:

CPCPrepareDataResultCode resZZ1;
resZZ1 = ZZ1.PrepareData(resData);

E poi chiamare la procedura Calculate():

if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
   ZZ1.Calculate();

L'esempio completo dell'utilizzo di una classe CCustPrevCalculated insieme a diverse classi CCustZigZagPPC è fornito nel file ScriptSample_CustZigZagPPC.mq5.


Funzione di accesso ai dati della classe CCustZigZagPPC

Nome
Scopo
 uint GetBarsCount()
 Restituisce il numero di barre disponibili
 uint GetBarsCalculated()  Restituisce il numero di barre calcolate
 double GetUP(uint shift, bool AsSeries)
 Restituisce il valore del picco ZigZag per una barra
 double GetDN(uint shift, bool AsSeries)
 Restituisce il valore dello ZigZag basso per una barra
 OrderFormationBarHighLow GetOB(uint shift, bool AsSeries)  Restituisce il valore 'Outside' per una barra


Controllo visivo e del programma

Per il controllo visivo, alleghiamo l'indicatore originale a un grafico e su di esso colleghiamo l'indicatore di prova Indicator_CustZigZag.mq5 appositamente scritto con parametri di input identici (ma devi selezionare altri colori per vedere entrambi gli indicatori); ecco il risultato del suo funzionamento:

Rosso - l’originale e blu - il nostro, calcolato sulle ultime 100 barre.

Allo stesso modo possiamo confrontarli in un Expert Advisor; ci sarà una differenza? I risultati ottenuti da iCustom("AlexSTAL_ZigZagProf") e dalla classe CCustZigZagPPC vengono confrontati ad ogni tick nel test Expert Advisor Expert_CustZigZagPPC_test.mq5. Le informazioni sul calcolo vengono visualizzate nel journal (potrebbero non esserci calcoli alle prime barre, a causa della mancanza di cronologia per l'algoritmo):

(EURUSD,M1)                1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // it is normal
(EURUSD,M1) Tick processed: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 
(EURUSD,M1) Divergence on the bar: 7 

Consideriamo questo Expert Advisor nei dettagli. Determina le variabili globali per lavorare:

#include <ZigZags.mqh>

CCustPrevCalculated CustPrevCalculated;
CCustZigZagPPC ZZ1;
int HandleZZ;

Inizializza la variabile:

int OnInit()
  {
   // Creating new class and initializing it
   CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
   
   // Initializing the class ZZ
   ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
   
   // Receiving handle for the custom indicator
   HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true);
   Print("ZZ_handle = ", HandleZZ, "  error = ", GetLastError());

   return(0);
  }
Elaborazione dei tick nell'Expert Advisor:
void OnTick()
  {
   // Calculation of data
   CPCPrepareDataResultCode resData, resZZ1;
   resData = CustPrevCalculated.PrepareData();
   
   // Start recalculation for each indicator! PrepareData obligatory!
   resZZ1 = ZZ1.PrepareData(resData);
   
   // Расчет данных ZZ1
   if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) )
      return;

   // Получим результаты расчета
   ZZ1.Calculate();

Ora abbiamo le barre ZZ1.GetBarsCalculated() calcolate dal CCustZigZagPPC. Aggiungiamo il codice di confronto dei dati di iCustom("AlexSTAL_ZigZagProf") e la classe CCustZigZagPPC:

   int tmpBars = (int)ZZ1.GetBarsCalculated();
   double zzUP[], zzDN[];
   CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP);
   CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN);
   
   // Perform comparison
   string tmpSt1 = "", tmpSt2 = "";
   for (int i = (tmpBars-1); i >= 0; i--)
     {
      double tmpUP = ZZ1.GetUP(i, false);
      double tmpDN = ZZ1.GetDN(i, false);
      if (tmpUP != zzUP[i])
         Print("Divergence on the bar: ", i);
      if (tmpDN != zzDN[i])
         Print("Divergence on the bar: ", i);
      if (tmpUP != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; ";
      if (tmpDN != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; ";

      if (zzUP[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; ";
      if (zzDN[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; ";
     }
  Print("Tick processed: ", tmpSt1);
  Print("                              ", tmpSt2);
  }

Ecco l’uso pratico e semplice della classe CCustZigZagPPC in un Expert Advisor o script. Le funzioni di accesso diretto GetUP(), GetDN(), GetOB() invece di CopyBuffer().


Spostamento del nostro indicatore in una classe separata (con l'esempio di iATR)

Sulla base del file ZigZags.mqh ho realizzato il template MyIndicator.mqh per lo sviluppo veloce di indicatori personalizzati secondo i principi sopra descritti.

Piano generale:

1. Fase preparatoria.

  • Copia MyIndicator.mqh come file con un altro nome (nel mio esempio sarà ATRsample.mqh) e apri quest'ultimo su MetaEditor 5.
  • Sostituisci il testo "MyInd" con il nome del tuo indicatore ("ATR" nel mio esempio).

2. Scegli i parametri esterni che verranno presi dall'indicatore iniziale (originale) alla classe, dichiarali e inizializzali.

Nel mio esempio, l'indicatore ATR ha un parametro esterno:
input int InpAtrPeriod=14;  // ATR period
  • aggiungiamo questo parametro alla nostra classe e alla funzione di inizializzazione della classe:
class CCustATR
  {
protected:
   ...
   uchar iAtrPeriod;
   ...
public:
   ...
   bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
  • modifica l'intestazione del corpo della funzione Init e inizializza il parametro variabile con il valore di input:
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod)
{
      ...
      BarsLimit = Limit;
      iAtrPeriod = AtrPeriod;
      ...

3. Determina il numero richiesto di buffer nell'indicatore iniziale e dichiarali nella nostra classe. Dichiara anche le funzioni di restituzione dei buffer INDICATOR_DATA.

  • Cambia la struttura
struct ATRBar
  {
   double Val;                          // Indicator buffers
  };

alla nostra struttura:

struct ATRBar
  {
   double ATR;
   double TR;
  };
  • Determinare i valori zero:
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData)
{
   ...
   for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].ATR = EMPTY_VALUE;
      Buf[PInd(i, false)].TR = EMPTY_VALUE;
     }
   ...
  • Modifica e aggiungi la funzione di restituzione dei valori dei buffer INDICATOR_DATA:

modifica (se c'è un solo buffer, puoi saltare la modifica)

class CCustATR
  {
   ...
   double GetVal(uint shift, bool AsSeries);                      // returns the Val value of the buffer for a bar
   ...

in

class CCustATR
  {
   ...
   double GetATR(uint shift, bool AsSeries);                      // Возвращает значение буфера ATR для бара
   ...

e cambia il codice della funzione corrispondente:

double CCustATR::GetATR(uint shift, bool AsSeries)
{
   if ( shift > (DataBarsCount-1) )
      return(EMPTY_VALUE);
   return(Buf[PInd(shift, AsSeries)].ATR);
}
Nota: invece di diverse funzioni di restituzione dei valori del buffer, è possibile utilizzarne solo una che possiede un parametro aggiuntivo: numero o nome del buffer.


4. Copia la logica della funzione OnCalculate() dell'indicatore iniziale nella funzione corrispondente della classe

  • Controlli primari
CPCATRResultCode CCustATR::Calculate()
{
   ...
   // Check if there are enough bars for the calculation
   if (DataBarsCount <= iAtrPeriod)
      return(CPCATRRC_NoData);
   ...
  • Calcoli: al primo tick e il numero di barre per i calcoli ai tick successivi:
   if ( DataBarsCalculated != 0 )
      BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1;
   else
     {
      Buf[PInd(0, false)].TR = 0.0;
      Buf[PInd(0, false)].ATR = 0.0;
      //--- filling out the array of True Range values for each period
      for (uint i = 1; i < DataBarsCount; i++)
         Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                                  MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      //--- first AtrPeriod values of the indicator are not calculated
      double firstValue = 0.0;
      for (uint i = 1; i <= iAtrPeriod; i++)
        {
         Buf[PInd(i, false)].ATR = 0;
         firstValue += Buf[PInd(i, false)].TR;
        }
      //--- calculating the first value of the indicator
      firstValue /= iAtrPeriod;
      Buf[PInd(iAtrPeriod, false)].ATR = firstValue;
      
      BarsForRecalculation = DataBarsCount - iAtrPeriod - 2;
     }
  • Il calcolo ad ogni tick stesso:
   for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                               MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod;
      ...

È tutto. La nostra classe è stata creata. Per il controllo visivo, puoi creare un indicatore di test (nel mio esempio, sarà Indicator_ATRsample.mq5):



Mi è venuta un'idea correggendo l'articolo, ovvero che se usi la classe CCustPrevCalculated insieme a un solo indicatore personalizzato, puoi integrare la creazione, l'inizializzazione e la sincronizzazione di questa classe nell'indicatore personalizzato (nei miei esempi sono CCustZigZagPPC e CCustATR ). Quando si chiama la funzione di inizializzazione degli indicatori personalizzati per questo scopo è necessario utilizzare il puntatore zero sull'oggetto:

   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);

A quel punto, la struttura generale

#include <CustPrevCalculated.mqh>
#include <ATRsample.mqh>
CCustPrevCalculated CustPrevCalculated;
CCustATR ATR;

int OnInit()
  {
   CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30);
   ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData();
   CPCPrepareDataResultCode resATR = ATR.PrepareData(resData);
   if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) )
      ATR.Calculate();
  }

sarà semplificata in:

#include <ATRsample.mqh>
CCustATR ATR;

int OnInit()
  {
   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   ATR.Calculate();
  }
Un esempio pratico è fornito nel file Indicator_ATRsample2.mq5.

Influenza della tecnologia descritta sulle prestazioni nel tester di strategia

Per la verifica, ho realizzato un test Expert Advisor (TestSpeed_IndPrevCalculated.mq5) che riceve il valore dell'indicatore della barra zero ad ogni tick secondo una delle tre varianti:

enum eTestVariant
  {
   BuiltIn,    // Built-in indicator iATR
   Custom,     // Custom indicator iCustom("ATR")
   IndClass    // Calculation in the class
  };

Questo Expert Advisor è stato eseguito 10 volte su 1 agente con i seguenti parametri di ottimizzazione:

  • Simbolo: EURUSD
  • Periodo: tutta la cronologia [1993..2001]
  • Modalità di trading: ogni tick
  • Parametro esterno: FalseParameter [0..9]

Ho misurato il tempo di ottimizzazione utilizzando ciascuna delle tre varianti dell'indicatore. Il risultato del controllo viene mostrato come un istogramma lineare.

Il tempo di ottimizzazione per tre tipi di implementazione dell'indicatore ATR

    Il codice sorgente dell'Expert Advisor utilizzato per misurare il tempo di ottimizzazione:

    //+------------------------------------------------------------------+
    //|                                  TestSpeed_IndPrevCalculated.mq5 |
    //|                                         Copyright 2011, AlexSTAL |
    //|                                           http://www.alexstal.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2011, AlexSTAL"
    #property link      "http://www.alexstal.ru"
    #property version   "1.00"
    //--- connect the include file with the CustATR class
    #include <ATRsample.mqh>
    //--- set the selection of the parameter as an enumeration
    enum eTestVariant
      {
       BuiltIn,    // Built-in indicator iATR
       Custom,     // Custom indicator iCustom("ATR")
       IndClass    // Calculation withing the class
      };
    //--- input variables
    input eTestVariant TestVariant;
    input int          FalseParameter = 0;
    //--- period of the ATR indicator
    const uchar        InpAtrPeriod = 14;
    //--- handle of the built-in or custom indicator
    int                Handle;
    //--- indicator based on the class 
    CCustATR           *ATR;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       //---
       switch(TestVariant)
         {
          case BuiltIn:
             Handle = iATR(_Symbol, _Period, InpAtrPeriod);
             break;
          case Custom:
             Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod);
             break;
          case IndClass:
             ATR = new CCustATR;
             ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod);
             break;
         };
       //---
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
       switch(TestVariant)
         {
          case IndClass:
             delete ATR;
             break;
         };
      }
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       double tmpValue[1];
       switch(TestVariant)
         {
          case BuiltIn:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case Custom:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case IndClass:
             ATR.Calculate();
             tmpValue[0] = ATR.GetATR(0, true);
             break;
         };
      }
    //+------------------------------------------------------------------+

    Come si vede, questa tecnologia non riduce significativamente le prestazioni nel tester di strategia rispetto all'utilizzo di un normale indicatore personalizzato.


    Note per l'uso pratico di questa tecnologia

    • quando si testa un Expert Advisor nel tester di strategia, il valore prev_calculated non può essere azzerato in un indicatore personalizzato, ecco perché la sincronizzazione della cronologia è disabilitata in questa modalità;
    • il calcolo dell'indicatore viene effettuato solo in corrispondenza delle ultime 'n' barre che sono rigorosamente impostate durante l'inizializzazione delle classi;
    • il calcolo implica un vincolo rigoroso a un certo simbolo e periodo della classe inizializzata. Per eseguire calcoli su altri simboli o periodi è necessario creare nuove istanze delle classi.


    Conclusione

    In ogni situazione, un programmatore deve considerare tutti i pro ei contro delle diverse varianti di implementazione dell'attività. L'implementazione suggerita nell'articolo è solo uno dei modi, con i suoi vantaggi e svantaggi.

    P.S. Chi non commette errori, non fa nulla! Se trovate degli errori, vi prego di informarmi.

    Tradotto dal russo da MetaQuotes Ltd.
    Articolo originale: https://www.mql5.com/ru/articles/247

    Random Walk e l'indicatore di tendenza Random Walk e l'indicatore di tendenza
    Random Walk sembra molto simile ai dati di mercato reali, ma ha alcune caratteristiche significative. In questo articolo considereremo le proprietà di Random Walk, simulato utilizzando il gioco del lancio della moneta. Per studiare le proprietà dei dati, viene sviluppato l'indicatore di tendenza.
    Grafici e diagrammi in HTML Grafici e diagrammi in HTML
    Oggi è difficile trovare un computer che non abbia un browser installato. Da tanto tempo i browser si evolvono e migliorano. Questo articolo tratta un modo semplice e sicuro per creare grafici e diagrammi sulla base delle informazioni ottenute dal client terminal MetaTrader 5 per la loro visualizzazione nel browser.
    Esposizione del codice C# in MQL5 utilizzando esportazioni non gestite Esposizione del codice C# in MQL5 utilizzando esportazioni non gestite
    In questo articolo ho presentato diversi metodi di interazione tra il codice MQL5 e il codice gestito C#. Ho anche fornito diversi esempi su come eseguire il marshalling di strutture MQL5 contro C# e come richiamare le funzioni DLL esportate negli script MQL5. Credo che gli esempi forniti possano servire come base per ricerche future sulla scrittura di DLL nel codice gestito. Questo articolo apre anche le porte a MetaTrader per utilizzare le tante librerie che sono già implementate in C#.
    Il lettore di trading basato sulla cronologia delle operazioni Il lettore di trading basato sulla cronologia delle operazioni
    Il lettore di trading. Solo quattro parole, nessuna spiegazione necessaria. Ti viene in mente una piccola scatola con dei pulsanti. Premi un pulsante e inizi a giocare, sposti la leva e la velocità cambia. In realtà, è abbastanza simile. In questo articolo, voglio mostrare ciò che ho sviluppato e che riproduce la cronologia di trading quasi come se fosse in tempo reale. L'articolo tratta alcuni cenni sull’OOP, lavorando con indicatori e gestendo grafici.