English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Diminuzione del consumo di memoria tramite indicatori ausiliari

Diminuzione del consumo di memoria tramite indicatori ausiliari

MetaTrader 5Indicatori | 11 gennaio 2022, 15:04
151 0
ds2
ds2

1. Il problema

Probabilmente, hai già utilizzato o creato Expert Advisor o indicatori che utilizzano altri indicatori ausiliari per il loro funzionamento.

Ad esempio, il famoso indicatore MACD utilizza due copie dell'indicatore EMA (Exponential Moving Average) calcolando la differenza tra i loro valori:


Un tale indicatore composito è equivalente ad altri diversi e semplici, infatti. Ad esempio, il MACD menzionato in precedenza consuma la memoria e il tempo del processore tre volte più del singolo EMA, poiché deve allocare memoria per i buffer dell'indicatore principale e per i buffer di tutti i suoi indicatori ausiliari.

Oltre al MACD, esistono indicatori più complessi che utilizzano più di due indicatori ausiliari.

Inoltre, un tale dispendio di memoria aumenta notevolmente se:

  • l'indicatore utilizza più timeframe (ad esempio, tiene traccia della concorrenza delle onde su più intervalli di tempo), quindi crea copie separate di indicatori ausiliari per ogni timeframe;
  • l'indicatore è multivaluta;
  • un trader utilizza l'indicatore per fare trading su diverse coppie di valute (conosco persone che fanno trading con più di due decine di coppie contemporaneamente).

Una combinazione di queste condizioni può portare a una mancanza di memoria su un computer (conosco casi reali, quando un client terminal richiede gigabyte di memoria a causa dell'utilizzo di tali indicatori). Ecco un esempio di mancanza di memoria nel client terminal MetaTrader 5:


In una tale situazione, il terminale non è in grado di posizionare l'indicatore su un grafico o di calcolarlo correttamente (se il codice dell'indicatore non gestisce l'errore di allocazione della memoria), oppure può addirittura spegnersi.

Fortunatamente, il terminale può sopperire alla mancanza di memoria utilizzando più memoria virtuale, ovvero memorizzando una parte delle informazioni sull'hard disk. Tutti i programmi funzioneranno, ma molto lentamente...


2. L'indicatore composito di prova

Per continuare la nostra indagine nell'ambito di questo articolo, creiamo un indicatore composito; quello che è più complesso del MACD.

Lasciamo che sia un indicatore che tiene traccia dell'inizio delle tendenze. Riassumerà i segnali di 5 timeframe, ad esempio: H4, H1, M15, M5, M1. Consentirà di fissare la risonanza delle tendenze emergenti grandi e piccole, cosa che dovrebbe aumentare l'affidabilità delle previsioni. Come fonte di segnale su ogni timeframe, utilizzeremo gli indicatori Ichimoku e Price_Channel inclusi su MetaTrader 5:

  • se la linea Tenkan (rossa) di Ichimoku è sopra la linea Kijun (blu), il trend è ascendente; e se è inferiore, il trend è decrescente;


  • se il prezzo è al di sopra della linea mediana di Price_Channel, il trend è ascendente; se è inferiore, il trend è decrescente.


In totale, il nostro indicatore utilizzerà 10 indicatori ausiliari: 5 tempi con 2 indicatori su ciascuno di essi. Chiamiamo il nostro indicatore Trender.

Ecco il suo codice sorgente completo (è anche allegato all'articolo):

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_minimum -1
#property indicator_maximum  1

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  DarkTurquoise

// The only buffer of the indicator
double ExtBuffer[];

// Timeframes of auxiliary indicators
ENUM_TIMEFRAMES TF[5] = {PERIOD_H4, PERIOD_H1, PERIOD_M15, PERIOD_M5, PERIOD_M1};

// Handles of auxiliary indicators for all timeframes
int h_Ichimoku[5], h_Channel[5];

//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0, ExtBuffer);
   ArraySetAsSeries(ExtBuffer, true);
   
   // Create auxiliary indicators
   for (int itf=0; itf<5; itf++)
     {
      h_Ichimoku[itf] = iCustom(Symbol(), TF[itf], 
                                "TestSlaveIndicators\\Ichimoku",
                                9, 26, 52
                               );
      h_Channel [itf] = iCustom(Symbol(), TF[itf],
                                "TestSlaveIndicators\\Price_Channel",
                                22
                               );
     }
  }
//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(time, true);
  
   int limit = prev_calculated ? rates_total - prev_calculated : rates_total -1;

   for (int bar = limit; bar >= 0; bar--)
     {
      // Time of the current bar
      datetime Time  = time [bar];
      
      //--- Gather signals from all timeframes
      double Signal = 0; // total signal
      double bufPrice[1], bufTenkan[1], bufKijun [1], bufMid[1], bufSignal[1];
      for (int itf=0; itf<5; itf++)
        {
         //=== Bar price
         CopyClose(Symbol(), TF[itf], Time, 1, bufPrice);
         double Price = bufPrice[0];

         //=== The Ichimoku indicator         
         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The channel indicator
         CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         double Mid = bufMid[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;
        }
        
      ExtBuffer[bar] = Signal/10;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

Devi usare questo indicatore su un grafico con il timeframe più piccolo tra quelli da cui raccoglie i segnali; è l'unico modo per vedere tutte le piccole tendenze. Nel nostro caso, è il timeframe M1. Ecco come appare l'indicatore:


E ora passiamo alla parte più importante: calcoliamo la quantità di memoria consumata da tale indicatore.

Dai un'occhiata al codice sorgente dell'indicatore Ichimoku (il codice completo è allegato):

#property indicator_buffers 5

e l'indicatore Price_Channel (in allegato il codice completo):

#property indicator_buffers 3

In queste righe, puoi vedere che vengono creati 8 buffer. Moltiplica per 5 intervalli di tempo. E aggiungi 1 buffer dell'indicatore Trender stesso. Abbiamo 41 buffer in totale! Valori così impressionanti sono alla base di questi indicatori apparentemente semplici (su un grafico).

Nelle proprietà predefinite del client terminal, un buffer contiene quasi 100000 valori di tipo double che consumano 8 byte ciascuno. Quindi 41 buffer consumano quasi 31 Mb di memoria. E questi sono solo valori; non so quali informazioni di servizio siano memorizzate nei buffer oltre a loro.

Potresti dire: "31 Mb non sono molti". Ma quando un trader utilizza molte coppie di valute, questo volume diventa un problema. Oltre agli indicatori, il grafico stesso consuma molta memoria. A differenza degli indicatori, ogni barra ha più valori contemporaneamente: OHLC, tempo e volume. Come possiamo inserirlo in un computer?


3. I modi per risolvere il problema

Naturalmente, puoi installare più memoria nel tuo computer. Ma se questa variante non è adatta a te per motivi tecnici, finanziari o di altro tipo o se hai già esaurito la quantità di memoria che può essere installata e non è sufficiente, devi esaminare l'indicatore di consumo e diminuire il loro dispendio.

Per farlo, ricorda la geometria imparata a scuola. Supponiamo che tutti i buffer dei nostri indicatori compositi siano un rettangolo solido:


L'area di questo rettangolo è la memoria consumata. È possibile ridurre l'area diminuendo la larghezza o l'altezza.

In questo caso, la larghezza è il numero di barre su cui sono disegnati gli indicatori. Non ci sono limiti per il numero di buffer dell’indicatore.


4. Diminuire il numero di barre

4.1. Una soluzione semplice

Non devi nemmeno essere un programmatore per regolare le impostazioni di MetaTrader:



Diminuendo il valore delle "Barre massime nel grafico", si riduce la dimensione dei buffer degli indicatori in quelle finestre. È semplice, efficace e disponibile per tutti (se un trader non ha bisogno di una cronologia dei prezzi approfondita durante il trading).

4.2. C'è un'altra soluzione?

I programmatori MQL5 sanno che i buffer degli indicatori sono dichiarati negli indicatori come array dinamici senza pre-impostazioni di dimensioni. Ad esempio, 5 buffer di Ichimoku:

double    ExtTenkanBuffer[];
double    ExtKijunBuffer[];
double    ExtSpanABuffer[];
double    ExtSpanBBuffer[];
double    ExtChinkouBuffer[];

La dimensione degli array non è specificata, perché è impostata per l'intera cronologia disponibile dal client terminal MetaTrader 5 stesso.

Lo stesso vale per la funzione OnCalculate:

int OnCalculate (const int rates_total,      // size of the array price[]
               const int prev_calculated,  // number of bars processed at the previous call
               const int begin,            // the start of reliable data
               const double& price[]       // array for the calculation
   );

In questa funzione, il buffer di prezzo viene passato all'indicatore. La sua memoria è già allocata dal terminale e un programmatore non può influire sulla sua dimensione.

Inoltre, MQL5 consente di utilizzare un buffer di un indicatore come buffer di prezzo per un altro (disegna "un indicatore basato su un altro indicatore"). Ma, anche qui, un programmatore non può porre alcun limite alla dimensione; passano solo l’handle dell'indicatore.

Quindi non ci sono modi in MQL5 per limitare la dimensione dei buffer degli indicatori.


5. Diminuzione del numero di buffer

Un programmatore ha una grande varietà di scelte qui. Ho trovato diversi e semplici metodi teorici per diminuire il numero di buffer di un indicatore composito. Tuttavia, tutti implicano un numero decrescente di buffer di indicatori ausiliari, poiché nell'indicatore principale tutti i buffer sono necessari.

Diamo uno sguardo dettagliato a questi mezzi e controlliamo se funzionano davvero e quali vantaggi e svantaggi hanno.

5.1. Il metodo della "necessità"

Se un indicatore ausiliario contiene molti buffer, potrebbe sembrare che non tutti siano necessari per l'indicatore principale. Quindi possiamo disabilitare gli indicatori inutilizzati per liberare la memoria che consumano. Per farlo, dobbiamo apportare alcune modifiche al codice sorgente di quell'indicatore ausiliario.

Facciamolo con uno dei nostri indicatori ausiliari: Price_Channel. Contiene tre buffer e Trender ne legge solo uno; quindi abbiamo qualcosa di inutile da rimuovere.

L'intero codice degli indicatori Price_Channel (indicatore iniziale) e Price_Channel-Need (completamente rifatto) è allegato all'articolo. Inoltre, descriverò solo le modifiche che sono state apportate.

Innanzitutto, diminuire il contatore dei buffer da 3 a 1:

//#property indicator_buffers 3
  #property indicator_buffers 1
//#property indicator_plots   2
  #property indicator_plots   1

Poi rimuovere due array di buffer non necessari:

//--- indicator buffers
//double    ExtHighBuffer[];
//double    ExtLowBuffer[];
 double    ExtMiddBuffer[];

Ora, se proviamo a compilare questo indicatore, il compilatore mostrerà tutta la riga in cui vengono chiamati quegli array:


Questo metodo consente di individuare rapidamente quello che deve essere modificato. È abbastanza conveniente quando il codice dell'indicatore è enorme.

Nel nostro caso, ci sono 4 righe "identificatore non dichiarato" in totale. Correggiamole.

Come ci si poteva aspettare, due di loro si trovano su OnInit. Ma insieme a loro abbiamo dovuto rimuovere la linea con il necessario ExtMiddBuffer. Al suo posto, ne aggiungiamo uno simile ma con un altro indice del buffer. Poiché l'indicatore non ha più un buffer con indice 2, è possibile solo 0:

//   SetIndexBuffer(0,ExtHighBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtLowBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtMiddBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtMiddBuffer,INDICATOR_DATA);

Se prevedi di utilizzare l'indicatore "cut" in modalità visiva, considera che le impostazioni dell'aspetto devono essere modificate insieme all'indice del buffer. Nel nostro caso:

//#property indicator_type1   DRAW_FILLING
  #property indicator_type1   DRAW_LINE

Se non hai bisogno della visualizzazione, puoi saltare la modifica dell'aspetto - non comporta errori.

Continuiamo a lavorare con l'elenco degli "identificatori non dichiarati". Le ultime due modifiche (anche quelle prevedibili) si trovano su OnCalculate, dove vengono riempiti gli array di buffer dell'indicatore. Poiché l'ExtMiddBuffer richiesto chiama l'ExtHighBuffer e l'ExtLowBuffer rimossi, al loro posto vengono sostituite le variabili intermedie:

   //--- the main loop of calculations
   for(i=limit;i<rates_total;i++)
     {
//      ExtHighBuffer[i]=Highest(High,InpChannelPeriod,i);
        double      high=Highest(High,InpChannelPeriod,i);
//      ExtLowBuffer[i]=Lowest(Low,InpChannelPeriod,i);
        double      low=Lowest(Low,InpChannelPeriod,i);
//      ExtMiddBuffer[i]=(ExtHighBuffer[i]+ExtLowBuffer[i])/2.0;;
        ExtMiddBuffer[i]=(   high         +   low         )/2.0;;
     }

Come puoi vedere, non c'è niente di difficile in tutta questa "operazione chirurgica". Le cose necessarie sono state localizzate rapidamente; sono esclusi diversi "tagli di bisturi" e due buffer. All'interno dell'intero indicatore composito Trender il risparmio totale è di 10 buffer (2*5 intervalli di tempo).

Puoi aprire Price_Channel e Price_Channel-Need uno sotto l'altro e vedere i buffer scomparsi:


Per utilizzare Price_Channel-Need nell'indicatore Trender dobbiamo correggere il nome dell'indicatore ausiliario "Price_Channel" in "Price_Channel-Need" nel codice di Trender. Inoltre, dobbiamo modificare l'indice del buffer richiesto da 2 a 0. Il Trender-Need già pronto è allegato all'articolo.


5.2. Il metodo "aggregato"

Se un indicatore principale legge i dati di più di un buffer di un indicatore ausiliario e quindi esegue un'azione di aggregazione con esso (ad esempio, addizione o confronto), non è necessario eseguire questa azione nell'indicatore principale. Possiamo farlo in un indicatore ausiliario e poi passare il risultato a quello principale. Quindi non è necessario avere più buffer; tutti vengono sostituiti con uno.

Nel nostro caso, tale metodo è applicabile a Ichimoku. Poiché Trender utilizza 2 buffer da esso (0 - Tenkan e 1 - Kijun):

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;

Se aggreghiamo 0 e 1 buffer di Ichimoku a un buffer di segnale, il frammento di Trender mostrato sopra verrà sostituito con il seguente:

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufSignal);
         
         Signal += bufSignal[0];

L'intero Trender-Aggregate è allegato all'articolo.

Ora, diamo un'occhiata alle modifiche chiave che devono essere apportate a Ichimoku.

Inoltre, questo indicatore contiene buffer non utilizzati. Quindi, oltre al metodo "Aggregato", possiamo applicare il metodo "Necessità". Quindi in Ichimoku rimane solo un buffer di 5, quello che aggrega i buffer necessari:

//#property indicator_buffers 5
  #property indicator_buffers 1
//#property indicator_plots   4
  #property indicator_plots   1

Diamo un nuovo nome all'unico buffer:

//--- indicator buffers
//double    ExtTenkanBuffer[];
//double    ExtKijunBuffer[];
//double    ExtSpanABuffer[];
//double    ExtSpanBBuffer[];
//double    ExtChinkouBuffer[];
  double    ExtSignalBuffer[];

Il nuovo nome ha un significato pratico: consente di eliminare dall'indicatore tutti i nomi dei buffer utilizzati in precedenza. Permetterà (usando la compilazione come descritto nel metodo "Necessità") di trovare rapidamente tutte le righe che vanno cambiate.

Se hai intenzione di visualizzare l'indicatore su un grafico, non dimenticare di modificare le impostazioni dell'aspetto. Inoltre devi considerare che, nel nostro caso, il buffer di aggregazione ha un diverso intervallo di valori rispetto a due buffer da esso consumati. Ora non mostra un derivato di prezzo, ma quale dei due buffer è più grande. È più conveniente visualizzare tali risultati in una finestra separata sotto un grafico:

//#property indicator_chart_window
  #property indicator_separate_window

Quindi, apporta le seguenti modifiche su OnInit:

//--- indicator buffers mapping
//   SetIndexBuffer(0,ExtTenkanBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtKijunBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtSpanABuffer,INDICATOR_DATA);
//   SetIndexBuffer(3,ExtSpanBBuffer,INDICATOR_DATA);
//   SetIndexBuffer(4,ExtChinkouBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtSignalBuffer,INDICATOR_DATA);

E la parte più interessante è su OnCalculate. Nota: tre buffer non necessari vengono semplicemente eliminati (poiché utilizziamo il metodo "Need") e i necessari ExtTenkanBuffer ed ExtKijunBuffer vengono sostituiti con le variabili temporanee Tenkan e Kijun. Tali variabili vengono utilizzate a fine ciclo per il calcolo del buffer aggregante ExtSignalBuffer:

   for(int i=limit;i<rates_total;i++)
     {
//     ExtChinkouBuffer[i]=Close[i];
      //--- tenkan sen
      double high=Highest(High,InpTenkan,i);
      double low=Lowest(Low,InpTenkan,i);
//     ExtTenkanBuffer[i]=(high+low)/2.0;
       double  Tenkan    =(high+low)/2.0;
      //--- kijun sen
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
//     ExtKijunBuffer[i]=(high+low)/2.0;
       double  Kijun    =(high+low)/2.0;
      //--- senkou span a
//     ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
      //--- senkou span b
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
//     ExtSpanBBuffer[i]=(high+low)/2.0;

       //--- SIGNAL
       double Signal = 0;
       if (Tenkan > Kijun) Signal++;
       if (Tenkan < Kijun) Signal--;
       ExtSignalBuffer[i] = Signal;
     }

Meno 4 buffer in totale. E se applicassimo solo il metodo "Need" a Ichimoku, avremmo solo 3 buffer in meno.

All'interno dell'intero Trender il risparmio è di 20 buffer in totale (4 * 5 tempi).

Il codice completo di Ichimoku-Aggregate è allegato all'articolo. Per confrontare questo indicatore con quello originale, aprili entrambi su un singolo grafico. Come ricorderai, l'indicatore modificato viene ora visualizzato sotto il grafico in una finestra separata:


5.3. Il metodo "Includi"

Il modo più radicale per ridurre il numero di buffer è eliminare tutti gli indicatori ausiliari. Se lo facciamo, nel nostro indicatore rimarrà solo un buffer, quello che appartiene all'indicatore principale. Non ci possono essere meno buffer.

Lo stesso risultato può essere ottenuto spostando il codice degli indicatori ausiliari sull'indicatore principale. A volte può sembrare una cosa che richiede tempo, ma l'effetto finale vale la pena. Il più difficile è adattare il codice spostato dagli indicatori. Non è destinato a funzionare all'interno del codice di un altro indicatore.

Ecco i principali problemi che sorgono durante:

  • Conflitto di nomi. Gli stessi nomi di variabili, funzioni (in particolare funzioni di sistema come OnCalculate);
  • Assenza di buffer. In alcuni indicatori può essere un ostacolo insormontabile se la logica degli indicatori è strettamente connessa alla memorizzazione/elaborazione dei dati in buffer. Nel nostro caso, la sostituzione dei buffer con semplici array non è una panacea, poiché il nostro obiettivo è diminuire il consumo di memoria. È importante rifiutare di archiviare in memoria dati cronologici giganteschi.

Dimostriamo i metodi che consentono di risolvere efficacemente questi problemi.

Ogni indicatore ausiliario deve essere scritto come una classe. Quindi tutte le variabili e le funzioni degli indicatori avranno sempre (all'interno delle loro classi) nomi univoci e non saranno in conflitto con gli altri indicatori.

Se vengono spostati molti indicatori, puoi pensare alla standardizzazione di quelle classi per evitare confusione quando li usi. A tal fine, crea una classe di indicatori di base ed eredita da essa le classi di tutti gli indicatori ausiliari.

Ho scritto la seguente classe:

class CIndicator
  {
protected:
   string symbol;             // currency pair
   ENUM_TIMEFRAMES timeframe;  // timeframe

   double Open[], High[], Low[], Close[]; // simulation of price buffers
   int BufLen; // necessary depth of filling of price buffers

public:
   //--- Analogs of standard functions of indicators
   void Create(string sym, ENUM_TIMEFRAMES tf) {symbol = sym; timeframe = tf;};
   void Init();
   void Calculate(datetime start_time); // start_time - address of bar that should be calculated
  };

Ora creiamo una classe per l'indicatore Ichimoku sulla base di esso. Prima di tutto, sotto forma di proprietà, scrivi i parametri di input con i nomi originali. Per non modificare nulla nel codice dell'indicatore in seguito:

class CIchimoku: public CIndicator
  {
private:
   // Simulation of input parameters of the indicator
   int InpTenkan;
   int InpKijun;
   int InpSenkou;

Conserva i nomi di tutti i buffer. Sì, hai sentito bene: dichiariamo tutti e 5 i buffer di questo indicatore. Ma sono falsi. Sono composti da una sola barra ciascuno:

public:   
   // Simulation of indicator buffers
   double ExtTenkanBuffer [1];
   double ExtKijunBuffer  [1];
   double ExtSpanABuffer  [1];
   double ExtSpanBBuffer  [1];
   double ExtChinkouBuffer[1];   

Perché l'abbiamo fatto? Per diminuire la quantità di ulteriori modifiche al codice. Lo vedrai. Ridefinisci il metodo ereditato CIchimoku.Calculate riempiendolo con il codice della funzione OnCalculate, preso da Ichimoku.

Nota che il ciclo delle barre della cronologia è stato rimosso durante lo spostamento di questa funzione. Ora viene calcolata solo una barra con un tempo specificato. E il codice principale dei calcoli rimane lo stesso. Ecco perché abbiamo conservato con tanta cura tutti i nomi dei buffer e i parametri dell'indicatore.

È inoltre necessario prestare attenzione al fatto che i buffer dei prezzi sono riempiti con valori all'inizio del metodo Calculate. Ci sono tanti valori quanti sono necessari per il calcolo di una barra.

   void Calculate(datetime start_time)
     {
      CopyHigh (symbol,timeframe,start_time,BufLen,High);
      CopyLow  (symbol,timeframe,start_time,BufLen,Low );
      CopyClose(symbol,timeframe,start_time,1     ,Close);

//    int limit;
      //---
//    if(prev_calculated==0) limit=0;
//    else                   limit=prev_calculated-1;
      //---
//    for(int i=limit;i<rates_total;i++)
      int i=0;
        {
         ExtChinkouBuffer[i]=Close[i];
         //--- tenkan sen
         double high=Highest(High,InpTenkan,i);
         double low=Lowest(Low,InpTenkan,i);
         ExtTenkanBuffer[i]=(high+low)/2.0;
         //--- kijun sen
         high=Highest(High,InpKijun,i);
         low=Lowest(Low,InpKijun,i);
         ExtKijunBuffer[i]=(high+low)/2.0;
         //--- senkou span a
         ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
         //--- senkou span b
         high=Highest(High,InpSenkou,i);
         low=Lowest(Low,InpSenkou,i);
         ExtSpanBBuffer[i]=(high+low)/2.0;
        }
      //--- done
//    return(rates_total);     
     };

Ovviamente possiamo saltarli mantenendo il codice originale. Ma in questo caso bisognerebbe riscriverne gran parte, il che richiede di comprendere la logica del suo funzionamento. Nel nostro caso, l'indicatore è semplice e sarebbe facile capirlo. Ma cosa succede se l'indicatore è complesso? Ti ho mostrato il metodo che può aiutare in questo caso.

Ora riempiamo il metodo CIchimoku.Init; qui è tutto semplice:

   void Init(int Tenkan = 9, int Kijun = 26, int Senkou = 52)
     {
      InpTenkan = Tenkan; InpKijun = Kijun; InpSenkou = Senkou;
      BufLen = MathMax(MathMax(InpTenkan, InpKijun), InpSenkou);
     };

L'Ichimoku contiene altre due funzioni che devono essere passate nella classe CIchimoku: Più alto e più basso. Cercano i valori massimi e minimi in una parte specificata dei buffer di prezzo.

I nostri buffer di prezzo non sono reali; hanno dimensioni molto ridotte (hai visto il loro riempimento nel metodo Calculate sopra). Ecco perché dobbiamo cambiare leggermente la logica di funzionamento delle funzioni Highest e Lowest.

In questa situazione, ho anche seguito il principio di apportare modifiche minime. Tutte le modifiche consistono nell'aggiungere una riga che cambia l'indicizzazione delle barre nel buffer da quella globale (quando la lunghezza del buffer è l'intera storia disponibile) a quella locale (in quanto ora i buffer dei prezzi contengono solo i valori necessari per il calcolo di una barra indicatrice):

   double Highest(const double&array[],int range,int fromIndex)
     {
       fromIndex=MathMax(ArraySize(array)-1, 0);
      double res=0;
   //---
      res=array[fromIndex];
      for(int i=fromIndex;i>fromIndex-range && i>=0;i--)
        {
         if(res<array[i]) res=array[i];
        }
   //---
      return(res);
     }

Il metodo Lowest viene modificato allo stesso modo.

Modifiche simili vengono apportate all'indicatore Price_Channel, ma verrà rappresentata la classe denominata CChannel. Il codice completo di entrambe le classi si trova nel file Trender-Include allegato all'articolo.

Ho descritto l'aspetto principale dello spostamento del codice. Penso che questi metodi siano sufficienti per la maggior parte degli indicatori.

Indicatori con impostazioni non standard possono causare ulteriori difficoltà. Ad esempio, Price_Channel contiene righe insignificanti:

   PlotIndexSetInteger(0,PLOT_SHIFT,1);
   PlotIndexSetInteger(1,PLOT_SHIFT,1);

indicano che il grafico dell'indicatore è spostato di 1 barra. Nel nostro caso, crea una situazione in cui le funzioni CopyBuffer e CopyHigh utilizzano due barre diverse nonostante nei loro parametri siano impostate le stesse coordinate della barra (tempo).

Questo problema è risolto in Trender-Include ("quelli" vengono aggiunti nelle parti necessarie della classe CChannel distinto dalla classe CIchimoku, dove il problema non esiste). Quindi, se hai bisogno di un indicatore così "furbo", sai dove cercarlo.

Bene, abbiamo finito con lo spostamento ed entrambi gli indicatori sono ora scritti come due classi all'interno dell'indicatore Trender-Include. Resta da cambiare il modo di chiamare quegli indicatori. Su Trender avevamo gli array di handle e su Trender-Include sono sostituiti con gli array di oggetti:

// Handles of auxiliary indicator for all timeframes
//int h_Ichimoku[5], h_Channel[5];
// Instances of embedded auxiliary indicators
CIchimoku o_Ichimoku[5]; CChannel o_Channel[5];

La creazione degli indicatori ausiliari su OnInit ora appare come segue:

   for (int itf=0; itf<5; itf++)
     {
      o_Ichimoku[itf].Create(Symbol(), TF[itf]);
      o_Ichimoku[itf].Init(9, 26, 52);
      o_Channel [itf].Create(Symbol(), TF[itf]);
      o_Channel [itf].Init(22);
     }

E CopyBuffer su OnCalculate viene sostituito con una chiamata diretta alle proprietà degli oggetti:

         //=== The Ichimoku indicator
         o_Ichimoku[itf].Calculate(Time);

         //CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         //double Tenkan = bufTenkan[0];
         double Tenkan = o_Ichimoku[itf].ExtTenkanBuffer[0];

         //CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         //double Kijun  = bufKijun [0];
         double Kijun  = o_Ichimoku[itf].ExtKijunBuffer [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The Channel indicator
         o_Channel[itf].Calculate(Time);

         //CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         //double Mid = bufMid[0];
         double Mid = o_Channel[itf].ExtMiddBuffer[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;

Meno 40 buffer. Vale la pena.

Dopo ogni modifica di Trender secondo i metodi "Need" e "Aggregate" descritti in precedenza, ho testato gli indicatori ottenuti in modalità visiva.

Conduciamo subito un simile test: apriamo l'indicatore iniziale (Trender) e quello rifatto (Trender-Include) su un grafico. Possiamo dire che tutto è stato realizzato correttamente, poiché le linee di entrambi gli indicatori concordano accuratamente tra loro:


5.4. Possiamo farlo uno per uno?

Abbiamo già considerato 3 modi per diminuire il numero di buffer degli indicatori ausiliari. Ma cosa succede se proviamo a cambiare radicalmente l'approccio - se proviamo a diminuire il numero di buffer che sono simultaneamente tenuti in memoria invece di diminuire il loro numero totale? In altre parole, caricheremo gli indicatori in memoria uno per uno invece di caricare tutti gli indicatori contemporaneamente. Dobbiamo organizzare una "rotonda": creare un indicatore ausiliario, leggere i suoi dati, eliminarlo, crearne uno successivo, ecc., fino a superare tutti gli intervalli di tempo. L'indicatore Ichimoku ha il maggior numero di buffer - 5. Quindi, teoricamente, possono essere mantenuti in memoria un massimo di 5 buffer contemporaneamente (più 1 buffer dell'indicatore principale) e il risparmio totale è di 35 buffer!

È possibile? In MQL5 c'è una funzione speciale per l'eliminazione degli indicatori - IndicatorRelease.

Tuttavia, non è così facile come sembra. MetaTrader 5 si preoccupa dell'elevata velocità di funzionamento dei programmi MQL5, ecco perché tutte le serie temporali chiamate vengono conservate nella cache nel caso in cui un altro EA, indicatore o script ne abbia bisogno. E solo se non vengono richiamati per lungo tempo, vengono scaricati per liberare la memoria. Questo timeout può durare fino a 30 minuti.

Quindi una costante creazione e cancellazione di indicatori non consentirà di ottenere un grande risparmio di memoria istantaneamente. Tuttavia, può rallentare notevolmente il computer poiché, ogni volta che viene creato un indicatore, viene calcolato per l'intera cronologia dei prezzi. Pensa a quanto è ragionevole eseguire una tale operazione ad ogni barra dell'indicatore principale...

Tuttavia, l'idea della "rotonda di indicatore" era piuttosto interessante per un "brainstorming". Se ti viene in mente un'altra idea originale di ottimizzazione della memoria dell'indicatore, scrivi i tuoi commenti all'articolo. Forse verranno utilizzati per un uso teorico o pratico in uno dei prossimi articoli su questo argomento.


6. Misurazione del consumo effettivo di memoria

Ebbene, nei capitoli precedenti abbiamo implementato 3 metodi di lavoro per diminuire il numero di buffer degli indicatori ausiliari. Ora, analizziamo come diminuisce il consumo effettivo di memoria.

Misureremo la dimensione della memoria consumata dal terminale utilizzando il "Task Manager" su MS Windows. Nella scheda "Processi" puoi vedere la dimensione della RAM e della memoria virtuale consumata dal client terminal. Per esempio:


Le misure vengono effettuate secondo il seguente algoritmo, il quale consente di vedere il consumo minimo di memoria da parte del terminale (che sarà vicino al consumo di memoria tramite indicatori):

  1. Scarica un’ampia cronologia dei prezzi dal server MetaQuotes-Demo (è sufficiente eseguire un test su un simbolo per scaricare automaticamente la sua cronologia);
  2. Imposta il terminale per una misura successiva (apri i grafici e gli indicatori richiesti) e riavvia per cancellare la memoria dalle informazioni non necessarie;
  3. Attendi che il terminale riavviato completi il calcolo di tutti gli indicatori. Lo vedrai dal caricamento zero del processore;
  4. Riduci a icona il terminale nella barra delle applicazioni (facendo clic sul pulsante standard "Riduci a icona" nell'angolo in alto a destra del terminale). Verrà liberata la memoria che al momento non viene utilizzata per i calcoli (nello screenshot sopra puoi vedere l'esempio del consumo di memoria nello stato ancora ridotto al minimo - puoi vedere che viene consumata molta meno RAM della memoria virtuale);
  5. Sul "Task Manager" riassumi le colonne "Mem Usage" (RAM) e "VM size" (memoria virtuale). Questo è il modo in cui vengono chiamati su Windows XP. I nomi possono essere leggermente diversi in altre versioni del sistema operativo.

Parametri delle misure:

  • Affinché le misure siano più accurate, utilizzeremo tutte le coppie di valute disponibili sul conto demo MetaQuotes invece di un grafico dei prezzi, ovvero 22 grafici M1. Quindi calcoleremo i valori medi;
  • L'opzione "Barre massime nel grafico" (descritta nel capitolo 4.1) ha un valore standard - 100000;
  • Sistema operativo: Windows XP, 32 bit.

Cosa ci si aspetta dal risultato delle misure? Ci sono due forme:

  1. Anche se l'indicatore Trender utilizza 41 buffer, ciò non significa che consuma 41*100000 barre. Il motivo è che i buffer sono distribuiti tra cinque timeframe e quello più grande contiene meno barre rispetto ai timeframe più piccoli. Ad esempio, la cronologia M1 di EURUSD contiene quasi 4 milioni di barre e la cronologia H1 è composta da appena 70000 barre (4000000/60). Ecco perché non devi aspettarti la stessa diminuzione del consumo di memoria dopo aver ridotto il numero di buffer su Trender;
  2. La memoria viene consumata non solo dall'indicatore stesso, ma dalla serie di prezzi utilizzata dall'indicatore. Trender utilizza cinque tempi. Quindi, se riduciamo il numero di buffer di diverse volte, il consumo totale di memoria non diminuirà così tanto. Perché verranno utilizzate tutte quelle cinque serie di prezzi in memoria.

Durante la misurazione del consumo, potresti dover affrontare alcuni altri fattori che influenzano il consumo di memoria. Questo è il motivo per cui conduciamo queste misure pratiche: vedi l'economia reale come risultato dell'ottimizzazione dell'indicatore.

Di seguito puoi trovare la tabella con il risultato di tutte le misure. Prima di tutto, ho misurato la dimensione della memoria consumata dal terminale vuoto. Sottraendo quel valore dalla misura successiva, possiamo calcolare la dimensione della memoria consumata da un grafico. Sottraendo la memoria consumata dal terminale e un grafico dalle misure successive, otteniamo la dimensione della memoria consumata da ciascun indicatore.

Consumatore di memoria
Buffer Indicatore
Intervalli di tempo
Consumo di memoria
Il client terminal
0
0
38 Mb per il terminale
Grafico
0
1
12 Mb per un grafico vuoto
L'indicatore Trender
41
5
46 Mb per un indicatore
L'indicatore Trender-Need
31
5
42 Mb per un indicatore
L'indicatore Trender-Aggregate 21
5
37 Mb per un indicatore
L'indicatore Trender-Include 1
5
38 Mb per un indicatore


La conclusione fatta sulla base dei risultati delle misure:

  • La diminuzione del numero di buffer indicatori non porta a un'uguale diminuzione della memoria utilizzata dall'indicatore.
L'origine di questo effetto è stata descritta in questo capitolo. Forse, se l'indicatore utilizzasse meno intervalli di tempo, l'effetto della diminuzione del numero di buffer sarebbe più significativo.
  • Spostare il codice degli indicatori ausiliari sull'indicatore principale non sempre porta al miglior risultato.

Allora perché il metodo Include non è così efficace come Aggregate? Per determinare il motivo, dobbiamo ricordare le principali differenze di codice di questi indicatori. Su Aggregate, le serie di prezzi necessarie per i calcoli vengono passate dal terminale come array di input su OnCalculate. Su Include, tutti i dati (per tutti i tempi) vengono richiesti attivamente per ogni barra utilizzando CopyHigh, CopyLow e CopyClose. Probabilmente, questo è ciò che porta a un consumo aggiuntivo di memoria, causato dalle peculiarità della memorizzazione nella cache delle serie temporali dei prezzi quando vengono utilizzate tali funzioni.


Conclusione

Quindi, questo articolo parla di 3 metodi di lavoro per ridurre il consumo di memoria sugli indicatori ausiliari e un metodo di salvataggio della memoria regolando il client terminal.

Un metodo che deve essere applicato in base all'accettabilità e alla correttezza nella tua situazione. Il numero di buffer e megabyte salvati dipende dagli indicatori con cui lavori: in alcuni sarai in grado di "tagliare" molto e in altri non sarai in grado di fare nulla.

Il salvataggio della memoria consentirà di aumentare il numero di coppie di valute utilizzate contemporaneamente nel terminale. Aumenta l'affidabilità del tuo portafoglio. Una così semplice cura delle risorse tecniche del tuo computer può trasformarsi in risorse monetarie sul tuo deposito.


Allegati

In allegato, gli indicatori descritti nell'articolo. Affinché tutto funzioni, salvali nella cartella "MQL5\Indicators\TestSlaveIndicators", perché tutte le versioni dell'indicatore Trender (eccetto Trender-Include) cercano i loro indicatori ausiliari lì.


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

File allegati |
ichimoku.mq5 (4.97 KB)
price_channel.mq5 (4.34 KB)
trender.mq5 (2.94 KB)
trender-need.mq5 (2.94 KB)
L'implementazione dell'analisi automatica delle onde di Elliott in MQL5 L'implementazione dell'analisi automatica delle onde di Elliott in MQL5
Uno dei metodi più popolari di analisi di mercato è il principio dell'onda di Elliott. Tuttavia, questo processo è piuttosto complicato, il che ci porta all'uso di strumenti aggiuntivi. Uno di questi strumenti è il marcatore automatico. Questo articolo descrive la creazione di un analizzatore automatico delle onde di Elliott nel linguaggio MQL5.
Crea il tuo Expert Advisor nel Wizard MQL5 Crea il tuo Expert Advisor nel Wizard MQL5
La conoscenza dei linguaggi di programmazione non è più un prerequisito per la creazione di robot di trading. La precedente mancanza di competenze di programmazione era un ostacolo invalicabile all'implementazione delle proprie strategie di trading ma, con l'emergere del Wizard MQL5, la situazione è cambiata radicalmente. I trader alle prime armi possono smettere di preoccuparsi della mancanza di esperienza di programmazione: con il nuovo Wizard, il quale consente di generare il codice Expert Advisor, non è necessario.
Utilizzo di WinInet in MQL5.  Parte 2:  Richieste e file POST Utilizzo di WinInet in MQL5. Parte 2: Richieste e file POST
In questo articolo, continuiamo a studiare i principi del lavoro con Internet utilizzando le richieste HTTP e lo scambio di informazioni con il server. Descrive nuove funzionalità della classe CMqlNet, modalità di invio di informazioni da form e invio di file tramite richieste POST nonché autorizzazione sui siti web con il tuo nome utente tramite Cookie.
Come ordinare un Expert Advisor e ottenere il risultato desiderato Come ordinare un Expert Advisor e ottenere il risultato desiderato
Come scrivere correttamente le specifiche dei requisiti? Cosa ci si dovrebbe e non ci si dovrebbe aspettare da un programmatore quando ordina un Expert Advisor o un indicatore? Come mantenere un dialogo, a quali momenti prestare particolare attenzione? Questo articolo fornisce le risposte a queste e a molte altre domande, le quali spesso non sembrano ovvie a molte persone.