English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Diminution de la consommation de mémoire grâce aux indicateurs auxiliaires

Diminution de la consommation de mémoire grâce aux indicateurs auxiliaires

MetaTrader 5Indicateurs | 12 janvier 2022, 16:58
224 0
ds2
ds2

1. Le problème

Vous avez probablement déjà utilisé ou créé des Expert Advisors ou des indicateurs qui utilisent d'autres indicateurs auxiliaires pour leur fonctionnement.

Par exemple, le célèbre indicateur MACD utilise deux exemplaires de l'indicateur EMA (Exponential Moving Average) en calculant la différence entre leurs valeurs :


Un tel indicateur composite équivaut en fait à plusieurs indicateurs simples. Par exemple, le MACD mentionné précédemment consomme trois fois plus de temps de mémoire et de processeur qu'un seul EMA, car il doit allouer de la mémoire aux tampons de l'indicateur principal et aux tampons de tous ses indicateurs auxiliaires.

Outre le MACD, il existe des indicateurs plus complexes qui utilisent plus de deux indicateurs auxiliaires.

De plus, ces dépenses de mémoire augmentent fortement si :

  • l'indicateur utilise plusieurs périodes (par exemple, il suit la simultanéité des vagues sur plusieurs périodes), il crée donc des copies distinctes des indicateurs auxiliaires pour chaque période ;
  • l'indicateur est multi-devises ;
  • un trader utilise l'indicateur pour trader sur plusieurs paires de devises (je connais des gens qui tradent plus de deux dizaines de paires simultanément).

Une combinaison de ces conditions peut conduire à un simple manque de mémoire sur un ordinateur (je connais des cas réels, où un terminal client nécessitait des gigaoctets de mémoire en raison de l'utilisation de tels indicateurs). Voici un exemple de manque de mémoire dans le terminal client MetaTrader 5 :


Dans une telle situation, le terminal n'est pas en mesure de placer l'indicateur sur un graphique ou de le calculer correctement (si le code de l'indicateur ne gère pas l'erreur d'allocation de mémoire), ou il peut même s'arrêter.

Heureusement, le terminal peut pallier un manque de mémoire en utilisant davantage de mémoire virtuelle, c'est-à-dire en stockant une partie des informations sur le disque dur. Tous les programmes vont fonctionner, mais très lentement...


2. L'indicateur composite de test

Pour poursuivre notre enquête dans le cadre de cet article, créons un indicateur composite ; celui qui est plus complexe que MACD.

Soit un indicateur qui suit l'initiation des tendances. Il résumera les signaux de 5 périodes, par exemple : H4, H1, M15, M5, M1. Cela permettra de fixer la résonance des grandes et petites tendances émergentes, ce qui devrait accroître la fiabilité des prévisions. Comme source de signal sur chaque période, nous allons utiliser les indicateurs Ichimoku et Price_Channel inclus dans la livraison MetaTrader 5 :

  • si la ligne Tenkan (rouge) d'Ichimoku est au-dessus de la ligne Kijun (bleue), la tendance est ascendante ; et si elle est inférieure, la tendance est à la baisse ;


  • si le prix est au-dessus de la ligne médiane de Price_Channel, la tendance est à la hausse ; si elle est en dessous, la tendance est à la baisse.


Au total, notre indicateur utilisera 10 indicateurs auxiliaires : 5 périodes avec 2 indicateurs sur chacun. Appelons notre indicateur Trender.

Voici son code source complet (il est également joint à l'article) :

#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);
  }
//+------------------------------------------------------------------+

Vous devez utiliser cet indicateur sur un graphique avec la plus petite période parmi celles dont il collecte les signaux ; c'est la seule façon de voir toutes les petites tendances. Dans notre cas, il s'agit de la période M1. Voici à quoi ressemble l'indicateur :


Et maintenant, passons à la partie la plus importante : calculons la quantité de mémoire consommée par un tel indicateur.

Jetez un œil au code source de l'indicateur Ichimoku (le code complet est joint) :

#property indicator_buffers 5

et l'indicateur Price_Channel (le code complet est joint) :

#property indicator_buffers 3

Dans ces lignes, vous pouvez voir que 8 tampons sont créés. Multipliez-le par 5 périodes. Et ajoutez 1 tampon de l'indicateur Trender lui-même. Nous avons 41 tampons au total ! Des valeurs aussi impressionnantes se cachent derrière ces indicateurs apparemment simples (sur un graphique).

Avec les propriétés par défaut du terminal client, un tampon contient près de 100 000 valeurs de type double qui consomment 8 octets chacune. Ainsi 41 tampons consomment près de 31 Mo de mémoire. Et il ne s'agit que des valeurs elles-mêmes ; je ne sais pas quelles informations de service sont stockées dans les tampons en plus de celles-ci.

Vous pouvez dire : « 31 Mo, ce n'est pas beaucoup ». Mais lorsqu'un trader utilise un grand nombre de paires de devises, ce volume devient un problème. En plus des indicateurs, les graphiques eux-mêmes consomment beaucoup de mémoire. Contrairement aux indicateurs, chaque barre présente plusieurs valeurs à la fois : OHLC, temps et volume. Comment pouvons-nous l'intégrer dans un ordinateur ?


3. Les moyens de résoudre le problème

Bien sûr, vous pouvez installer plus de mémoire sur votre ordinateur. Mais si cette variante ne vous convient pas pour des raisons techniques, financières ou autres, ou si vous avez déjà épuisé la quantité de mémoire qui peut être installée et qu'elle n'est pas suffisante, vous devriez examiner ces indicateurs de consommation et diminuer leur appétit.

Pour ce faire...rappelez-vous votre géométrie scolaire. Supposons que tous les tampons de nos indicateurs composites forment un rectangle solide :


L'aire de ce rectangle est la mémoire consommée. Vous pouvez réduire la surface en diminuant la largeur ou la hauteur.

Dans ce cas, la largeur est le nombre de barres sur lesquelles les indicateurs sont dessinés. La hauteur est le nombre de tampons indicateurs.


4. Diminuer le nombre de barres

4.1. La solution simple

Il n'est même pas nécessaire d'être un programmeur pour régler les paramètres de MetaTrader :



En diminuant la valeur de « Max bars in chart », vous diminuez la taille des tampons des indicateurs dans ces fenêtres. C'est une méthode simple, efficace et accessible à tous (si un trader n'a pas besoin d'un historique détaillé des prix pour trader).

4.2. Existe-t-il une autre solution ?

Les programmeurs MQL5 savent que les tampons d'indicateurs sont déclarés dans les indicateurs sous forme de tableaux dynamiques sans préréglage de taille. Par exemple, 5 tampons d'Ichimoku :

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

La taille des tableaux n'est pas spécifiée, car elle est définie pour l'ensemble de l'historique disponible par le terminal client MetaTrader 5 lui-même.

Il en va de même pour la fonction 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
   );

Dans cette fonction, le tampon de prix est transmis à l'indicateur. La mémoire est déjà allouée par le terminal, et le programmeur ne peut pas modifier sa taille.

De plus, MQL5 permet d'utiliser le tampon d'un indicateur comme tampon de prix pour un autre indicateur (dessiner « un indicateur basé sur un autre indicateur »). Mais même dans ce cas, le programmeur ne peut pas fixer de limite à la taille ; il se contente de transmettre la poignée de l'indicateur.

Il n'y a donc aucun moyen dans MQL5 de limiter la taille des tampons d'indicateurs.


5. Diminution du nombre de tampons

Le programmeur dispose ici d'une grande variété de choix. J'ai trouvé plusieurs moyens théoriques simples de diminuer le nombre de tampons d'un indicateur composite. Cependant, elles impliquent toutes une diminution du nombre de tampons des indicateurs auxiliaires, puisque tous les tampons sont nécessaires dans l'indicateur principal.

Examinons en détail ces moyens et vérifions s'ils fonctionnent réellement et quels sont leurs avantages et inconvénients.

5.1. La méthode « Need »

Si un indicateur auxiliaire contient de nombreux tampons, il peut sembler que tous ne soient pas nécessaires pour l'indicateur principal. Ainsi, nous pouvons désactiver les indicateurs inutilisés pour libérer la mémoire qu'ils consomment. Pour ce faire, nous devons apporter quelques modifications au code source de cet indicateur auxiliaire.

Faisons-le avec l'un de nos indicateurs auxiliaires - Price_Channel. Il contient trois tampons et Trender n'en lit qu'un ; nous avons donc des choses inutiles à supprimer.

L'intégralité du code des indicateurs Price_Channel (indicateur initial) et Price_Channel-Need (entièrement refait) est joint à l'article. De plus, je ne vais décrire que les changements qui y ont été apportés.

Tout d'abord, diminuez le compteur de tampons de 3 à 1 :

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

Supprimez ensuite deux tableaux de tampons inutiles :

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

Maintenant, si nous essayons de compiler cet indicateur, le compilateur affichera toute les lignes où ces tableaux sont appelés :


Cette méthode permet de localiser rapidement les éléments qui doivent être modifiés. C'est assez pratique lorsque le code de l'indicateur est énorme.

Dans notre cas, il y a 4 lignes « identifiant non déclaré » au total. Corrigeons-les.

Comme on pouvait s'y attendre, deux d'entre eux sont situés dans OnInit. Mais avec eux, nous avons dû supprimer la ligne avec le nécessaire ExtMiddBuffer. Au lieu de cela, nous en ajoutons un similaire mais avec un autre index du tampon. Comme l'indicateur n'a plus de tampon avec indice 2, seul 0 est possible :

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

Si vous envisagez d'utiliser l'indicateur de « coupe » en mode visuel, considérez que les paramètres d'apparence doivent être modifiés en même temps que l'indice de tampon. Dans notre cas :

//#property indicator_type1   DRAW_FILLING
  #property indicator_type1   DRAW_LINE

Si vous n'avez pas besoin de visualisation, vous pouvez ignorer la modification de l'apparence - cela n'entraîne pas d'erreurs.

Continuons à travailler avec la liste « identifiant non déclaré ». Les deux derniers changements (qui sont également prévisibles) se trouvent dans OnCalculate, où les tableaux tampons des indicateurs sont remplis. Étant donné que l'ExtMiddBuffer requis appelle les ExtHighBuffer et ExtLowBuffer supprimés, des variables intermédiaires sont substituées à leur place :

   //--- 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;;
     }

Comme vous pouvez le voir, il n'y a rien de difficile dans toute cette « opération chirurgicale ». Les éléments nécessaires ont été localisés rapidement ; plusieurs « coupes au scalpel » et deux tampons sont exclus. Dans l'ensemble de l'indicateur composite Trender, l'économie totale est de 10 tampons (2*5 périodes).

Vous pouvez ouvrir Price_Channel et Price_Channel-Need l'un sous l'autre et voir les tampons disparus :


Pour utiliser Price_Channel-Need dans l'indicateur Trender, nous devons corriger le nom de l'indicateur auxiliaire « Price_Channel » en « Price_Channel-Need » dans le code de Trender. De plus, nous devons changer l'index du tampon requis de 2 à 0. Le Trender-Need prêt à l'emploi est joint à l'article.


5.2. La méthode « Aggregate »

Si un indicateur principal lit les données de plus d'un tampon d'un indicateur auxiliaire et qu'il effectue ensuite une action d'agrégation avec celles-ci (par exemple, addition ou comparaison), il n'est pas nécessaire d'effectuer cette action dans l'indicateur principal. Nous pouvons le faire dans un indicateur auxiliaire et ensuite transmettre le résultat à l'indicateur principal. Ainsi il n'est pas nécessaire d'avoir plusieurs tampons ; tous sont remplacés par un seul.

Dans notre cas, une telle méthode est applicable à Ichimoku. Comme Trender en utilise 2 tampons (0 - Tenkan et 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--;

Si nous agrégeons les tampons 0 et 1 d'Ichimoku à un tampon de signal, alors le fragment de Trender montré ci-dessus sera remplacé par le suivant :

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

La version intégrale de Trender-Aggregate est jointe à l'article.

Maintenant, examinons les changements clés qui devraient être apportés à Ichimoku.

De plus, cet indicateur contient des tampons inutilisés. Ainsi, en plus de la méthode « Aggregate », nous pouvons appliquer la méthode des « Need ». Il ne reste donc qu'un seul tampon de 5 dans Ichimoku - celui qui agrège les tampons nécessaires :

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

Donnons un nouveau nom à l'unique tampon :

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

Le nouveau nom a une signification pratique - il permet de supprimer de l'indicateur tous les noms des tampons précédemment utilisés. Cela permettra (en utilisant la compilation comme décrit dans la méthode « Need ») de trouver rapidement toutes les lignes qui doivent être modifiées.

Si vous envisagez de visualiser l'indicateur sur un graphique, n'oubliez pas de modifier les paramètres d'apparence. Vous devez également considérer que dans notre cas, le tampon d'agrégation a une intervalle de valeurs différente de celle des deux tampons qu'il consomme. Maintenant, il n'affiche pas de dérivé de prix, mais lequel des deux tampons est le plus grand. Il est plus pratique d'afficher ces résultats dans une fenêtre séparée sous un graphique :

//#property indicator_chart_window
  #property indicator_separate_window

Donc, faites les changements suivants dans 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);

Et la partie la plus intéressante se trouve dans OnCalculate. Remarque : trois tampons inutiles sont simplement supprimés (car nous utilisons la méthode « Need »), et les ExtTenkanBuffer et ExtKijunBuffer nécessaires sont remplacés par les variables temporaires Tenkan et Kijun. Ces variables sont utilisées en fin de cycle pour le calcul du tampon d'agrégation 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;
     }

Moins 4 tampons au total. Et si nous appliquions uniquement la méthode « Need » à l'Ichimoku, nous n'aurions que 3 tampons de moins.

Dans l'ensemble de Trender, l'économie est de 20 tampons au total (4 * 5 périodes).

Le code complet d'Ichimoku-Aggregate est joint à l'article. Pour comparer cet indicateur à l'original, ouvrez-les tous les deux sur un même graphique. Comme vous vous en souvenez, l'indicateur modifié est désormais affiché sous le graphique dans une fenêtre distincte :


5.3. La méthode « Include »

La façon la plus radicale de diminuer le nombre de tampons est de se débarrasser de tous les indicateurs auxiliaires. Si nous le faisons, il ne restera qu'un seul tampon dans notre indicateur - celui qui appartient à l'indicateur principal. Il ne peut pas y avoir moins de tampons.

Le même résultat peut être obtenu en déplaçant le code des indicateurs auxiliaires vers l'indicateur principal. Parfois, cela peut sembler prendre du temps, mais l'effet final en vaut la peine. Le plus difficile est d'adapter le code déplacé à partir des indicateurs. Il n'est pas destiné à fonctionner dans le code d'un autre indicateur.

Voici les principaux problèmes qui se posent à cette occasion :

  • Conflit de noms. Les mêmes noms de variables, de fonctions (notamment les fonctions système comme OnCalculate) ;
  • Absence de tampons. Dans certains indicateurs, cela peut être un obstacle insurmontable si la logique des indicateurs est étroitement liée au stockage/traitement des données dans des tampons. Dans notre cas, le remplacement des tampons par de simples tableaux n'est pas une panacée, puisque notre objectif est de diminuer la consommation de mémoire. Il est important de refuser le stockage en mémoire d'un gigantesque historique de données.

Montrons les méthodes qui permettent de résoudre efficacement ces problèmes.

Chaque indicateur auxiliaire doit être écrit en tant que classe. Alors toutes les variables et fonctions des indicateurs auront toujours (au sein de leurs classes) des noms uniques et ne seront pas en conflit avec les autres indicateurs.

Si de nombreux indicateurs sont déplacés, vous pouvez penser à une standardisation de ces classes pour éviter toute confusion lors de leur utilisation. Pour cela, créez une classe d'indicateurs de base et héritez des classes de tous les indicateurs auxiliaires.

J'ai écrit la classe suivante :

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
  };

Créons maintenant une classe pour l'indicateur Ichimoku sur la base de celui-ci. Tout d'abord, sous forme de propriétés, écrivez les paramètres d'entrée avec les noms originaux. Pour ne rien changer au code de l'indicateur par la suite :

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

Conservez les noms de tous les tampons. Oui, c'est bien ce que vous avez entendu - nous déclarons les 5 tampons de cet indicateur. Mais ils sont faux. Ils sont constitués d'une seule barre chacun :

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

Pourquoi l'avons-nous fait ? Pour réduire le nombre de modifications ultérieures du code. Vous allez le voir. Redéfinissez la méthode héritée CIchimoku.Calculate en la remplissant avec le code de la fonction OnCalculate issu d'Ichimoku.

Attention, la boucle des barres d'historique a été supprimée lors du déplacement de cette fonction. Désormais, une seule barre avec un temps spécifié y est calculée. Et le code principal des calculs reste le même. C'est pourquoi nous avons conservé avec soin tous les noms des tampons et des paramètres de l'indicateur.

Vous devez également faire attention au fait que les tampons de prix sont remplis de valeurs au tout début de la méthode Calculate. Il y a autant de valeurs que nécessaire pour le calcul d'une barre.

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

Bien sûr, nous pourrions nous passer de conserver le code original. Mais dans ce cas, il faudrait en réécrire une grande partie, ce qui nécessite de comprendre la logique de son fonctionnement. Dans notre cas, l'indicateur est simple et il serait facile de le comprendre. Mais que faire si l'indicateur est complexe ? Je vous ai montré la méthode qui peut vous aider dans ce cas.

Remplissons maintenant la méthode CIchimoku.Init ; tout est simple ici :

   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 contient deux autres fonctions qui doivent être portées à la classe CIchimoku : Le plus haut et le plus bas. Ils recherchent des valeurs maximales et minimales dans une partie spécifiée des tampons de prix.

Nos tampons de prix ne sont pas réels ; ils ont une très petite taille (vous avez vu leur remplissage dans la méthode Calculate ci-dessus). C'est pourquoi nous devons légèrement modifier la logique de fonctionnement des fonctions Highest et Lowest.

Dans cette situation, j'ai également suivi le principe de procéder à un minimum de changements. Toutes les modifications consistent à ajouter une ligne qui change l'indexation des barres dans le tampon de l'index global (lorsque la longueur du tampon est l'historique complet disponible) à l'index local (puisque maintenant les tampons de prix contiennent seulement les valeurs qui sont nécessaires pour le calcul d'une barre d'indicateur) :

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

La méthode Lowest est modifiée de la même manière.

Des modifications similaires sont apportées à l'indicateur Price_Channel, mais il sera représenté par la classe nommée CChannel. Le code complet des deux classes se trouve dans le fichier Trender-Include joint à l'article.

J'ai décrit l'aspect principal du déplacement du code. Je pense que ces méthodes sont suffisantes pour la plupart des indicateurs.

Les indicateurs avec des paramètres non standard peuvent causer des difficultés supplémentaires. Par exemple, Price_Channel contient des lignes banales :

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

Ils signifient que l'indicateur graphique est décalé sur 1 barre. Dans notre cas, cela conduit à la situation où les fonctions CopyBuffer et CopyHigh utilisent deux barres différentes bien que les mêmes coordonnées de barre (temps) soient définies dans leurs paramètres.

Ce problème est résolu dans Trender-Include (« ones » sont ajoutés dans les parties nécessaires de la classe CChannel à la différence de la classe CIchimoku où le problème n'existe pas). Donc, si vous avez besoin d'un tel indicateur « astucieux », vous savez où le trouver.

Eh bien, nous en avons fini avec le déplacement, et les deux indicateurs sont maintenant écrits comme deux classes à l'intérieur de l'indicateur Trender-Include. Il reste à changer la façon d'appeler ces indicateurs. Dans Trender, nous avions les tableaux de poignées, et dans Trender-Include, ils sont remplacés par les tableaux d'objets :

// 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 création des indicateurs auxiliaires dans OnInit se présente maintenant comme suit :

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

Et CopyBuffer dans OnCalculate est remplacé par un appel direct aux propriétés des objets :

         //=== 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--;

Moins 40 tampons. Cela vaut la peine de faire des efforts.

Après chaque modification de Trender selon les méthodes « Need » et « Aggregate » décrites précédemment, j'ai testé les indicateurs obtenus en mode visuel.

Effectuons un tel test dès maintenant : ouvrez l'indicateur initial (Trender) et celui qui a été refait (Trender-Include) sur un graphique. Nous pouvons dire que tout a été fait correctement, puisque les lignes des deux indicateurs concordent exactement l'une avec l'autre :


5.4. Peut-on le faire un par un ?

Nous avons déjà considéré 3 façons de diminuer le nombre de tampons d'indicateurs auxiliaires. Mais que se passe-t-il si nous essayons de changer radicalement l'approche - si nous essayons de diminuer le nombre de tampons qui sont simultanément conservés dans la mémoire au lieu de diminuer leur nombre total ? En d'autres termes, nous allons charger les indicateurs dans la mémoire un par un, au lieu de charger tous les indicateurs en une seule fois. Il faut organiser un « rond-point » : créer un indicateur auxiliaire, lire ses données, le supprimer, en créer un suivant, etc., jusqu'à parcourir toute les périodes. L'indicateur Ichimoku possède le plus grand nombre de tampons - 5. Ainsi, théoriquement, un maximum de 5 tampons peuvent être conservés simultanément en mémoire (plus 1 tampon de l'indicateur principal), et l'économie totale est de 35 tampons !

Est-ce possible ? Dans MQL5, il existe une fonction spéciale pour supprimer les indicateurs - IndicatorRelease.

Cependant, ce n'est pas aussi facile qu'il n'y paraît. MetaTrader 5 se soucie de la vitesse de fonctionnement élevée des programmes MQL5, c'est pourquoi toutes les séries temporelles appelées sont conservées en cache - au cas où un autre EA, indicateur ou script en aurait besoin. Et seulement s'ils ne sont pas appelés pendant une longue période, ils sont déchargés pour libérer la mémoire. Ce délai peut durer jusqu'à 30 minutes.

Ainsi, la création et la suppression constantes d'indicateurs ne permettront pas de réaliser une grande économie de mémoire instantanément. Cependant, il peut ralentir considérablement l'ordinateur, car un indicateur est calculé pour l'ensemble de l'historique des prix à chaque fois qu'il est créé. Pensez à quel point il est raisonnable d'effectuer une telle opération à chaque barre de l'indicateur principal...

Néanmoins, l'idée d'un « rond-point indicateur » était assez intéressante pour un « brainstorming ». Si vous avez une autre idée originale d'optimisation de la mémoire des indicateurs, ajoutez vos commentaires à l'article. Peut-être seront-ils utilisés de manière théorique ou pratique dans l'un des prochains articles sur ce sujet.


6. Mesurer la consommation réelle de mémoire

Eh bien, dans les chapitres précédents, nous avons mis en œuvre 3 méthodes de travail pour diminuer le nombre de tampons d'indicateurs auxiliaires. Maintenant, analysons comment cela diminue la consommation réelle de mémoire.

Nous allons mesurer la taille de la mémoire consommée par le terminal en utilisant le « Gestionnaire des tâches » de MS Windows. Dans l'onglet « Processus », vous pouvez voir la taille de la RAM et de la mémoire virtuelle consommée par le terminal client. Par exemple :


Les mesures sont effectuées selon l'algorithme suivant qui permet de voir la consommation minimale de mémoire par le terminal (qui sera proche de la consommation de mémoire par les indicateurs) :

  1. Téléchargez un historique détaillé des prix depuis le serveur MetaQuotes-Demo (il suffit de lancer un test sur un symbole pour que son historique soit automatiquement téléchargé) ;
  2. Configurez le terminal pour une mesure suivante (ouvrez les graphiques et indicateurs requis) et redémarrez-le pour vider la mémoire des informations inutiles ;
  3. Attendez que le terminal redémarré ait terminé le calcul de tous les indicateurs. Vous le verrez par le chargement nul du processeur ;
  4. Réduisez le terminal dans la barre des tâches (en cliquant sur le bouton standard « Réduire » dans le coin supérieur droit du terminal). Cela libérera de la mémoire qui n'est pas utilisée pour les calculs pour le moment (dans la capture d'écran ci-dessus, vous pouvez voir l'exemple de la consommation de mémoire dans l'état encore minimisé - vous pouvez voir que beaucoup moins de RAM que la mémoire virtuelle est consommée) ;
  5. Dans le « Gestionnaire des tâches », résumez les colonnes « Mem Usage » (RAM) et « VM size » (mémoire virtuelle). Voici comment ils sont appelés dans Windows XP, les noms peuvent être légèrement différents dans d'autres versions du système d'exploitation.

Paramètres des mesures :

  • Pour que les mesures soient plus précises, nous allons utiliser toutes les paires de devises disponibles sur le compte de démonstration MetaQuotes au lieu d'un seul graphique de prix, c'est-à-dire 22 graphiques M1. Ensuite, nous calculerons les valeurs moyennes ;
  • L'option « Max bars in chart » (décrite dans le chapitre 4.1) a une valeur standard - 100000 ;
  • OS - Windows XP, 32 bit.

Qu'attend-on du résultat des mesures ? Il y a deux formes :

  1. Même si l'indicateur Trender utilise 41 tampons, cela ne signifie pas qu'il consomme 41*100000 barres. La raison en est que les tampons sont répartis sur cinq périodes, et la plus grande contient moins de barres que les plus petites. Par exemple, l'historique M1 de l'EURUSD contient près de 4 millions de barres et l'historique H1 ne compte que 70 000 barres (4 000 000/60). C'est pourquoi vous ne devriez pas vous attendre à la même diminution de la consommation de mémoire après avoir diminué le nombre de tampons dans Trender ;
  2. La mémoire est consommée non seulement par l'indicateur lui-même, mais par la série de prix utilisée par l'indicateur. Trender utilise cinq périodes. Ainsi, si l'on diminue le nombre de tampons de plusieurs fois, la consommation totale de mémoire ne diminuera pas autant. Parce que toutes ces cinq séries de prix dans la mémoire seront utilisées.

Lorsque vous mesurez la consommation, vous pouvez être confronté à d'autres facteurs qui affectent la consommation de mémoire. C'est la raison pour laquelle nous menons ces mesures pratiques - pour voir l'économie réelle à la suite de l'optimisation de l'indicateur.

Vous trouverez ci-dessous le tableau avec le résultat de toutes les mesures. Tout d'abord, j'ai mesuré la taille de la mémoire consommée par le terminal vide. En soustrayant cette valeur de la mesure suivante, nous pouvons calculer la taille de la mémoire consommée par un graphique. En soustrayant la mémoire consommée par le terminal et un graphique des mesures suivantes, on obtient la taille de la mémoire consommée par chaque indicateur.

Consommateur de mémoire
Tampons indicateurs
Périodes
Consommation de mémoire
Le terminal client
0
0
38 Mo pour le terminal
Graphique
0
1
12 Mo pour un graphique vide
L'indicateur Trender
41
5
46 Mo pour un indicateur
L'indicateur Trender-Need
31
5
42 Mo pour un indicateur
L'indicateur Trender-Aggregate 21
5
37 Mo pour un indicateur
L'indicateur Trender-Include 1
5
38 Mo pour un indicateur


La conclusion faite sur la base des résultats des mesures :

  • La diminution du nombre de tampons de l'indicateur n'entraîne pas une diminution équivalente de la mémoire utilisée par l'indicateur.
L'origine de cet effet est décrite plus haut dans ce chapitre. Peut-être que si l'indicateur utilisait moins de périodes, l'effet de la diminution du nombre de tampons serait plus important.
  • Déplacer le code des indicateurs auxiliaires vers l'indicateur principal ne conduit pas toujours au meilleur résultat.

Alors pourquoi la méthode Include n'est-elle pas aussi efficace que la méthode Aggregate ? Pour en déterminer la raison, nous devons nous rappeler les principales différences de code de ces indicateurs. Dans Aggregate, les séries de prix nécessaires aux calculs sont transmises par le terminal comme tableaux d'entrée dans OnCalculate. Dans Inclure, toutes les données (pour toutes les périodes) sont activement demandées pour chaque barre à l'aide de CopyHigh, CopyLow et CopyClose. C'est probablement ce qui entraîne une consommation supplémentaire de mémoire, causée par les particularités de la mise en cache des séries chronologiques de prix lorsque ces fonctions sont utilisées.


Conclusion

Ainsi, cet article présente 3 méthodes de travail pour diminuer la consommation de mémoire sur les indicateurs auxiliaires et 1 méthode pour économiser de la mémoire en ajustant le terminal client.

La méthode à appliquer dépend de son acceptabilité et de sa pertinence dans votre situation. Le nombre de tampons et de mégaoctets enregistrés dépend des indicateurs avec lesquels vous travaillez : dans certains d'entre eux, vous pourrez « couper » beaucoup, et dans d'autres, vous ne pourrez rien faire.

L'économie de mémoire permet d'augmenter le nombre de paires de devises utilisées simultanément dans le terminal. Il augmente la fiabilité de votre portefeuille de trading. Un simple souci des ressources techniques de votre ordinateur peut se transformer en ressources financières à votre disposition.


Pièces jointes

Les indicateurs décrits dans l'article sont joints. Pour que tout fonctionne, enregistrez-les dans le dossier « MQL5\Indicators\TestSlaveIndicators », car toutes les versions de l'indicateur Trender (sauf Trender-Include) y recherchent leurs indicateurs auxiliaires.


Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/259

Fichiers joints |
ichimoku.mq5 (4.97 KB)
price_channel.mq5 (4.34 KB)
trender.mq5 (2.94 KB)
trender-need.mq5 (2.94 KB)
La mise en œuvre de l'analyse automatique des vagues d'Elliott dans MQL5 La mise en œuvre de l'analyse automatique des vagues d'Elliott dans MQL5
L'une des méthodes les plus populaires d'analyse du marché est le principe des vagues d'Elliott. Toutefois, ce processus est assez compliqué, ce qui nous amène à utiliser des outils supplémentaires. L’un de ces instruments est le marqueur automatique. Cet article décrit la création d'un analyseur automatique de vagues d'Elliott en langage MQL5.
Créez votre propre Expert Advisor dans l'assistant MQL5 Créez votre propre Expert Advisor dans l'assistant MQL5
La connaissance des langages de programmation n'est plus un prérequis pour créer des robots de trading. Auparavant, le manque de compétences en programmation était un obstacle infranchissable à la mise en œuvre de ses propres stratégies de trading, mais avec l'apparition de l'assistant MQL5, la situation a radicalement changé. Les traders débutants peuvent cesser de s'inquiéter en raison du manque d'expérience en programmation - avec le nouvel assistant, qui vous permet de générer le code Expert Advisor, ce n'est plus nécessaire.
Utilisation de WinInet dans MQL5.  Partie 2 :  Requêtes et fichiers POST Utilisation de WinInet dans MQL5. Partie 2 : Requêtes et fichiers POST
Dans cet article, nous continuons à étudier les principes du travail avec Internet en utilisant les requêtes HTTP et l'échange d'informations avec le serveur. Il décrit les nouvelles fonctions de la classe CMqlNet, les méthodes d'envoi d'informations à partir de formulaires et l'envoi de fichiers à l'aide de requêtes POST ainsi que l'autorisation sur les sites web sous votre identifiant à l'aide de cookies.
Comment commander un Expert Advisor et obtenir le résultat escompté Comment commander un Expert Advisor et obtenir le résultat escompté
Comment rédiger correctement les spécifications des exigences ? Que doit-on et ne doit-on pas attendre d'un programmeur lorsqu'il commande un Expert Advisor ou un indicateur ? Comment maintenir un dialogue, quels sont les moments auxquels il faut prêter une attention particulière ? Cet article donne les réponses à ces questions, ainsi qu'à de nombreuses autres questions, qui souvent ne semblent pas évidentes à beaucoup de gens.