English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Approche orientée objet pour créer des panneaux multi-délais et multi-devises

Approche orientée objet pour créer des panneaux multi-délais et multi-devises

MetaTrader 5Exemples | 22 décembre 2021, 16:59
169 0
Marcin Konieczny
Marcin Konieczny

Introduction

Cet article décrit comment la programmation orientée objet peut être utilisée pour créer des panneaux multi-délais et multi-devises pour MetaTrader 5. L'objectif principal est de créer un panneau universel, qui peut être utilisé pour afficher de nombreux types de données, tels que les prix, les changements de prix, les valeurs des indicateurs ou les conditions d'achat/vente personnalisées sans avoir besoin de modifier le code du panneau lui-même. De cette façon, très peu de codage sera nécessaire pour personnaliser le panneau de la manière dont nous avons besoin.

La solution que je vais décrire fonctionne selon deux modes :

  1. Mode multi-délai - permet de voir le contenu du tableau calculé sur un symbole courant, mais sur des délais différents ;
  2. Mode multi-devises - permet de voir le contenu du tableau calculé sur un délai courant, mais sur des symboles différents.

Les images suivantes montrent le panneau dans ces deux modes.

Le premier fonctionne en mode multi-temps et affiche les données suivantes :

  1. Prix courant ;
  2. Changement de prix de la barre courante ;
  3. Changement de prix de la barre courante en pourcentage ;
  4. Changement de prix de la barre courante sous forme de flèche (haut/bas) ;
  5. Valeur de l'indicateur RSI(14) ;
  6. Valeur de l'indicateur RSI(10) ;
  7. État personnalisé : SMA(20) > prix courant.

Figure 1. Mode multi-délai

Figure 1. Mode multi-délai


Le second fonctionne en mode multi-devises et affiche :

  1. Prix courant ;
  2. Changement de prix de la barre courante ;
  3. Changement de prix de la barre courante en pourcentage ;
  4. Le changement de prix de la barre courante sous forme de flèche ;
  5. Valeur de l'indicateur RSI(10) ;
  6. Valeur de l'indicateur RSI(14) ;
  7. État personnalisé : SMA(20) > prix courant.

Figure 2. Mode multi-devises

Figure 2. Mode multi-devises


1. Mise en œuvre

Le diagramme de classes suivant décrit la conception de mise en œuvre du panneau.

Figure 3. Diagramme de classe du panneau

Figure 3. Diagramme de classe du panneau


Permettez-moi de décrire les éléments du diagramme :

  1. CTable. Classe de base du panneau. Elle est chargée de dessiner le panneau et de gérer ses composants.
  2. SpyAgent. C'est un indicateur responsable de l’« espionnage » d'autres symboles (instruments). Chaque agent est créé et envoyé à un symbole différent. L'agent réagit à l'événement OnCalculate lorsqu'un nouveau trait arrive sur le graphique des symboles et envoie l'événement CHARTEVENT_CUSTOM pour informer l'objet CTable qu'il doit procéder à la mise à jour. Toute l'idée derrière cette approche est basée sur l'article « La mise en œuvre d'un mode multi-devises dans MetaTrader 5 ». Vous pouvez y trouver tous les détails techniques.
  3. CRow. Classe de base pour tous les indicateurs et conditions utilisés pour créer le panneau. En étendant cette classe, il est possible de créer tous les composants nécessaires du panneau.
  4. CPriceRow. Extension de la classe simple CRow, qui est utilisé pour afficher le prix de l'offre courante.
  5. CPriceChangeRow. Extension de la classe CRow, utilisée pour afficher le changement de prix de la barre courante. Elle peut afficher les changements de prix, les changements de pourcentage ou les flèches.
  6. CRSIRow. Extension de la classe CRow, utilisée pour afficher la valeur courante de l'indicateur RSI.
  7. CPriceMARow. Extension de la classe CRow, affichant une condition personnalisée : SMA > prix courant.

Les classes CTable et CRow ainsi que l'indicateur SpyAgent constituent les parties principales du panneau. CPriceRow, CPriceChangeRow, CRSIRow et CPriceMARow sont les contenus réels du panneau. La classe CRow est conçue pour être étendue par de nombreuses nouvelles classes afin d'obtenir le résultat souhaité. Les quatre classes dérivées présentées ne sont que de simples exemples de ce qui peut être fait ainsi que la manière de le faire.


2. SpyAgent

Nous allons commencer par l'indicateur SpyAgent. Il n'est utilisé qu'en mode multi-devises et est nécessaire pour bien mettre à jour le panneau, lorsqu'un nouveau trait arrive sur d'autres graphiques. Je n'entrerai pas dans les détails de ce concept. Ils sont décrits dans l'article « La mise en œuvre d'un mode multi-devises dans MetaTrader 5 ».

L'indicateur SpyAgent s'exécute sur le graphique du symbole spécifié et envoie deux événements : l'événement d'initialisation et l'événement du nouveau trait. Les deux événements sont de type CHARTEVENT_CUSTOM. Afin de gérer ces événements, nous devons utiliser le gestionnaire OnChartEvent(...) (cela sera montré plus loin dans l'article).

Examinons le code de SpyAgent :

//+------------------------------------------------------------------+
//|                                                     SpyAgent.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property indicator_chart_window
#property indicator_plots 0

input long   chart_id=0;        // chart id
input ushort custom_event_id=0; // event id
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {

   if(prev_calculated==0)
      EventChartCustom(chart_id,0,0,0.0,_Symbol); // sends initialization event
   else
      EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // sends new tick event

   return(rates_total);
  }
Il est assez simple. La seule chose qu'il fait est de recevoir de nouveaux traits et d'envoyer des événements CHARTEVENT_CUSTOM.


3. CTable

CTable est la classe de base du panneau. Il stocke des informations sur les paramètres du panneau et gère ses composants. Il met à jour (redessine) le panneau, si nécessaire.

Examinons la déclaration de CTable :

//+------------------------------------------------------------------+
//| CTable class                                                     |
//+------------------------------------------------------------------+
class CTable
  {
private:
   int               xDistance;    // distance from right border of the chart
   int               yDistance;    // distance from top of the chart
   int               cellHeight;   // table cell height
   int               cellWidth;    // table cell width
   string            font;         // font name
   int               fontSize;
   color             fontColor;

   CList            *rowList;      // list of row objects
   bool              tfMode;       // is in multi-timeframe mode?

   ENUM_TIMEFRAMES   timeframes[]; // array of timeframes for multi-timeframe mode
   string            symbols[];    // array of currency pairs for multi-currency mode

   //--- private methods
   //--- sets default parameters of the table
   void              Init();
   //--- draws text label in the specified table cell
   void              DrawLabel(int x,int y,string text,string font,color col);
   //--- returns textual representation of given timeframe
   string            PeriodToString(ENUM_TIMEFRAMES period);

public:
   //--- multi-timeframe mode constructor
                     CTable(ENUM_TIMEFRAMES &tfs[]);
   //--- multi-currency mode constructor
                     CTable(string &symb[]);
   //--- destructor
                    ~CTable();
   //--- redraws table
   void              Update();
   //--- methods for setting table parameters
   void              SetDistance(int xDist,int yDist);
   void              SetCellSize(int cellW,int cellH);
   void              SetFont(string fnt,int size,color clr);
   //--- appends CRow object to the of the table
   void              AddRow(CRow *row);
  };

Comme vous pouvez le voir, tous les composants du panneau (lignes) sont stockés sous forme de liste de pointeurs CRow, de sorte que chaque composant que nous souhaitons ajouter au panneau doit étendre la classe CRow. CRow peut être vu comme un contrat entre le panneau et ses composants. CTable ne contient aucun code pour le calcul de ses cellules. Il incombe aux classes d'étendre CRow. CTable n'est qu'une structure pour contenir les composants CRow et les redessiner si nécessaire.

Passons en revue les méthodes de CTable. La classe comporte deux constructeurs. Le premier est utilisé pour le mode multi-délais et il est assez simple. Nous n'avons qu'à fournir un tableau de délais que nous voulons afficher.

//+------------------------------------------------------------------+
//| Multi-timeframe mode constructor                                 |
//+------------------------------------------------------------------+
CTable::CTable(ENUM_TIMEFRAMES &tfs[])
  {
//--- copy all timeframes to own array
   ArrayResize(timeframes,ArraySize(tfs),0);
   ArrayCopy(timeframes,tfs);
   tfMode=true;
   
//--- fill symbols array with current chart symbol
   ArrayResize(symbols,ArraySize(tfs),0);
   for(int i=0; i<ArraySize(tfs); i++)
      symbols[i]=Symbol();

//--- set default parameters
   Init();
  }

Le deuxième constructeur est utilisé pour le mode multi-devises et couvre un tableau de symboles (instruments). Celui-ci envoie également des SpyAgents. Il les rattache un par un aux graphiques appropriés.

//+------------------------------------------------------------------+
//| Multi-currency mode constructor                                  |
//+------------------------------------------------------------------+
CTable::CTable(string &symb[])
  {
//--- copy all symbols to own array
   ArrayResize(symbols,ArraySize(symb),0);
   ArrayCopy(symbols,symb);
   tfMode=false;
   
//--- fill timeframe array with current timeframe
   ArrayResize(timeframes,ArraySize(symb),0);
   ArrayInitialize(timeframes,Period());

//--- set default parameters
   Init();

//--- send SpyAgents to every requested symbol
   for(int x=0; x<ArraySize(symbols); x++)
      if(symbols[x]!=Symbol()) // don't send SpyAgent to own chart
         if(iCustom(symbols[x],0,"SpyAgent",ChartID(),0)==INVALID_HANDLE)
           {
            Print("Error in setting of SpyAgent on "+symbols[x]);
            return;
           }
  }

La méthode Init crée la liste de lignes (sous forme d'objet CList - CList est une liste dynamique de types CObject) et définit les valeurs par défaut pour les variables internes CTable (police, taille de police, couleur, dimension de cellule et distance du coin supérieur droit du graphique ).

//+------------------------------------------------------------------+
//| Sets default parameters of the table                             |
//+------------------------------------------------------------------+
CTable::Init()
  {
//--- create list for storing row objects
   rowList=new CList;

//--- set defaults
   xDistance = 10;
   yDistance = 10;
   cellWidth = 60;
   cellHeight= 20;
   font="Arial";
   fontSize=10;
   fontColor=clrWhite;
  }

Le destructeur est assez simple. Il supprime la liste des lignes et supprime tous les objets de graphique (étiquettes) créés par le panneau.

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTable::~CTable()
  {
   int total=ObjectsTotal(0);

//--- remove all text labels from the chart (all object names starting with nameBase prefix)
   for(int i=total-1; i>=0; i--)
      if(StringFind(ObjectName(0,i),nameBase)!=-1)
         ObjectDelete(0,ObjectName(0,i));

//--- delete list of rows and free memory
   delete(rowList);
  }

La méthode AddRow ajoute la nouvelle ligne à la liste des lignes. Notez que rowList est un objet CList, qui se redimensionne automatiquement. Cette méthode appelle également la méthode Init pour chaque objet CRow ajouté. Il est nécessaire de permettre à l'objet d'initialiser correctement ses variables internes. Par exemple, il peut utiliser l'appel Init pour créer des indicateurs ou des descripteurs de fichiers.

//+------------------------------------------------------------------+
//| Appends new row to the end of the table                          |
//+------------------------------------------------------------------+
CTable::AddRow(CRow *row)
  {
   rowList.Add(row);
   row.Init(symbols,timeframes);
  }

La méthode Update est un peu plus compliquée. Elle est utilisée pour redessiner le panneau.

Fondamentalement, elle se compose de trois parties, qui sont :

  • Dessiner la première colonne (les noms des lignes)
  • Dessiner la première ligne (les noms des plages horaires ou des symboles selon le mode sélectionné)
  • Dessiner les cellules internes (les valeurs des composants)

Il convient de noter que nous demandons à chaque composant de calculer sa propre valeur en fonction du symbole et du délai fournis. Nous laissons également le composant décider de la police et de la couleur à utiliser.

//+------------------------------------------------------------------+
//| Redraws the table                                                |
//+------------------------------------------------------------------+
CTable::Update()
  {
   CRow *row;
   string symbol;
   ENUM_TIMEFRAMES tf;

   int rows=rowList.Total(); // number of rows
   int columns;              // number of columns

   if(tfMode)
      columns=ArraySize(timeframes);
   else
      columns=ArraySize(symbols);

//--- draw first column (names of rows)
   for(int y=0; y<rows; y++)
     {
      row=(CRow*)rowList.GetNodeAtIndex(y);
      //--- note: we ask row object to return its name
      DrawLabel(columns,y+1,row.GetName(),font,fontColor);
     }

//--- draws first row (names of timeframes or currency pairs)
   for(int x=0; x<columns; x++)
     {
      if(tfMode)
         DrawLabel(columns-x-1,0,PeriodToString(timeframes[x]),font,fontColor);
      else
         DrawLabel(columns-x-1,0,symbols[x],font,fontColor);
     }

//--- draws inside table cells
   for(int y=0; y<rows; y++)
      for(int x=0; x<columns; x++)
        {
         row=(CRow*)rowList.GetNodeAtIndex(y);

         if(tfMode)
           {
            //--- in multi-timeframe mode use current symbol and different timeframes
            tf=timeframes[x];
            symbol=_Symbol;
           }
         else
           {
            //--- in multi-currency mode use current timeframe and different symbols
            tf=Period();
            symbol=symbols[x];
           }

         //--- note: we ask row object to return its font, 
         //--- color and current calculated value for given timeframe and symbol
         DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf));
        }

//--- forces chart to redraw
   ChartRedraw();
  }

La méthode DrawLabel est utilisée pour dessiner des étiquettes de texte dans la cellule spécifiée du panneau. Tout d'abord, elle vérifie si une étiquette pour cette cellule existe déjà. Sinon, elle en crée une nouvelle.

Ensuite, elle définit toutes les propriétés d'étiquette nécessaires et son texte.

//+------------------------------------------------------------------+
//| Draws text label in the specified cell of the table              |
//+------------------------------------------------------------------+  
CTable::DrawLabel(int x,int y,string text,string font,color col)
  {
//--- create unique name for this cell
   string name=nameBase+IntegerToString(x)+":"+IntegerToString(y);

//--- create label
   if(ObjectFind(0,name)<0)
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);

//--- set label properties
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight);
   ObjectSetString(0,name,OBJPROP_FONT,font);
   ObjectSetInteger(0,name,OBJPROP_COLOR,col);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize);

//--- set label text
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

D'autres méthodes ne seront pas présentées ici, car elles sont très simples et moins importantes. Le code complet est téléchargeable au bas de l'article.


4. Extension de CRow

CRow est une classe de base pour tous les composants, qui peut être utilisée par le panneau.

Examinons le code de la classe CRow :

//+------------------------------------------------------------------+
//| CRow class                                                       |
//+------------------------------------------------------------------+
//| Base class for creating custom table rows                        |
//| one or more methods of CRow should be overriden                  |
//| when creating own table rows                                     |
//+------------------------------------------------------------------+
class CRow : public CObject
  {
public:
   //--- default initialization method
   virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { }

   //--- default method for obtaining string value to display in the table cell
   virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); }

   //--- default method for obtaining color for table cell
   virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); }
   
   //--- default method for obtaining row name
   virtual string GetName() { return("-"); }

   //--- default method for obtaining font for table cell
   virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); }
  };

Il étend CObject, car seuls les CObjects peuvent être stockés dans une structure CList. Il a cinq méthodes, qui sont presque vides. Pour être plus précis, la plupart d'entre elles ne renvoient que des valeurs par défaut. Ces méthodes sont conçues pour être remplacées lors de l'extension de CRow. Nous n'avons pas besoin de les remplacer toutes, seulement celles que nous voulons.

À titre d'exemple, créons le composant de panneau le plus simple possible - le composant du prix de l'offre courante. Il peut être utilisé en mode multi-devises pour afficher les prix actuels de divers instruments.

Pour y parvenir, nous créons une classe CPriceRow, qui ressemble à ce qui suit :

//+------------------------------------------------------------------+
//| CPriceRow class                                                  |
//+------------------------------------------------------------------+
class CPriceRow : public CRow
  {
public:
   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

  };
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   MqlTick tick;

//--- gets current price
   if(!SymbolInfoTick(symbol,tick)) return("-");

   return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceRow::GetName()
  {
   return("Price");
  }

Les méthodes que nous avons choisi de remplacer ici sont GetValue et GetName. GetName renvoie simplement le nom de cette ligne, qui sera affiché dans la première colonne du panneau. GetValue obtient le dernier trait sur le symbole spécifié et renvoie le dernier cours acheteur. C'est tout ce dont nous avons besoin.

C'était assez simple. Faisons quelque chose de différent. Nous allons maintenant créer un composant qui affiche la valeur RSI courante.

Le code est similaire au précédent :

//+------------------------------------------------------------------+
//| CRSIRow class                                                    |
//+------------------------------------------------------------------+
class CRSIRow : public CRow
  {
private:
   int               rsiPeriod;        // RSI period
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of RSI handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CRSIRow(int period);

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRSIRow::CRSIRow(int period)
  {
   rsiPeriod=period;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets RSI handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];

//--- gets RSI indicator handle
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- gets current RSI value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");

   return(DoubleToString(value[0],2));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CRSIRow::GetName()
  {
   return("RSI("+IntegerToString(rsiPeriod)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
int CRSIRow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

Nous avons quelques nouvelles méthodes ici. Le constructeur permet de fournir la période RSI et la stocke sous forme de variable membre. La méthode Init est utilisée pour créer des descripteurs d'indicateur RSI. Ces descripteurs sont stockés dans le tableau handles[]. La méthode GetValue copie la dernière valeur du tampon RSI et la renvoie. La méthode GetHandle privée est utilisée pour trouver le descripteur d'indicateur approprié dans le tableau handles[]. GetName est explicite.

Comme nous pouvons le voir, la construction de composants de panneaux est assez simple. De la même manière, nous pouvons créer des composants pour presque toutes les conditions personnalisées. Il n'est pas nécessaire que ce soit la valeur de l'indicateur. Ci-dessous, je présente une condition personnalisée basée sur SMA. Elle vérifie si le prix courant est supérieur à la moyenne mobile et affiche « Oui » ou « Non ».

//+------------------------------------------------------------------+
//| CPriceMARow class                                                |
//+------------------------------------------------------------------+
class CPriceMARow : public CRow
  {
private:
   int               maPeriod; // period of moving average
   int               maShift;  // shift of moving average
   ENUM_MA_METHOD    maType;   // SMA, EMA, SMMA or LWMA
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of MA handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CPriceMARow(ENUM_MA_METHOD type,int period,int shift);

   //--- overrides default GetValue(..) method of CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   // overrides default GetName() method CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| CPriceMARow class constructor                                    |
//+------------------------------------------------------------------+
CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift)
  {
   maPeriod= period;
   maShift = shift;
   maType=type;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets MA handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method of CRow                    |
//+------------------------------------------------------------------+
string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];
   MqlTick tick;

//--- obtains MA indicator handle
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- gets the last MA value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");
//--- gets the last price
   if(!SymbolInfoTick(symbol,tick)) return("-");

//--- checking the condition: price > MA
   if(tick.bid>value[0])
      return("Yes");
   else
      return("No");
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method of CRow                       |
//+------------------------------------------------------------------+
string CPriceMARow::GetName()
  {
   string name;

   switch(maType)
     {
      case MODE_SMA: name = "SMA"; break;
      case MODE_EMA: name = "EMA"; break;
      case MODE_SMMA: name = "SMMA"; break;
      case MODE_LWMA: name = "LWMA"; break;
     }

   return("Price>"+name+"("+IntegerToString(maPeriod)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
int CPriceMARow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

Le code est plus long, car la moyenne mobile a trois paramètres : période, décalage et type. GetName est un peu plus compliqué car il construit le nom en fonction du type et de la période MA. GetValue fonctionne presque de la même manière que dans le cas de CRSIRow, mais au lieu de renvoyer la valeur de l'indicateur, il renvoie « Oui » si le prix est supérieur à SMA ou « Non » s'il est inférieur.

Le dernier exemple est un peu plus complexe. C'est la classe CPriceChangeRow, qui montre le changement de prix de la barre courante. Elle fonctionne en trois modes :

  • Affichage des flèches (vert vers le haut ou rouge vers le bas) ;
  • Affichage du changement de prix sous forme de valeur (vert ou rouge) ;
  • Affichage de la variation de prix en pourcentage (vert ou rouge).

Le code ressemble à ceci :

//+------------------------------------------------------------------+
//| CPriceChangeRow class                                            |
//+------------------------------------------------------------------+
class CPriceChangeRow : public CRow
  {
private:
   bool              percentChange;
   bool              useArrows;

public:
   //--- constructor
                     CPriceChangeRow(bool arrows,bool percent=false);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default GetFont() method from CRow
   virtual string    GetFont(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetColor(..) method from CRow
   virtual color     GetColor(string symbol,ENUM_TIMEFRAMES tf);

  };
//+------------------------------------------------------------------+
//| CPriceChangeRow class constructor                                |
//+------------------------------------------------------------------+
CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false)
  {
   percentChange=percent;
   useArrows=arrows;
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
 string CPriceChangeRow::GetName()
  {
   return("PriceChg");
  }
//+------------------------------------------------------------------+
//| Overrides default GetFont() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf)
  {
//--- we use Wingdings font to draw arrows (up/down)
   if(useArrows)
      return("Wingdings");
   else
      return("Arial");
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(" ");
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" ");

//--- current bar price change
   double change=close[0]-open[0];

   if(useArrows)
     {
      if(change > 0) return(CharToString(233)); // returns up arrow code
      if(change < 0) return(CharToString(234)); // returns down arrow code
      return(" ");
        }else{
      if(percentChange)
        {
         //--- calculates percent change
         return(DoubleToString(change/open[0]*100.0,3)+"%");
           }else{
         return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
        }
     }
  }
//+------------------------------------------------------------------+
//| Overrides default GetColor(..) method from CRow                  |
//+------------------------------------------------------------------+
color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(clrWhite);
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(clrWhite);

   if(close[0] > open[0]) return(clrLime);
   if(close[0] < open[0]) return(clrRed);
   return(clrWhite);
  }

Le constructeur a deux paramètres. Le premier décide d'afficher ou non les flèches. S’il est vrai, le deuxième paramètre est ignoré. S'il est faux, le deuxième paramètre décide s'il faut afficher les changements en pourcentage ou simplement les changements de prix.

Pour cette classe, j'ai décidé de remplacer par quatre méthodes de CRow : GetName, GetValue, GetColor et GetFont. GetName est le plus simple et renvoie simplement le nom. GetFont est utilisé, car il donne la possibilité d'afficher des flèches ou d'autres caractères de la police Wingdings. GetColor renvoie la couleur citron lorsque le prix augmente et rouge lorsqu'il baisse. La couleur blanche est renvoyée lorsqu'elle reste en place ou en cas d'erreurs. GetValue obtient les prix d'ouverture et de clôture de la dernière barre, calcule la différence et la renvoie. En mode flèche, il renvoie les codes de caractères Wingdings des flèches haut et bas.


5. Comment utiliser le tout

Pour utiliser le panneau, nous devons créer un nouvel indicateur. Appelons-le TableSample.

Les événements que nous devons gérer sont :

Nous avons également besoin d'un pointeur vers l'objet CTable, qui sera créé dynamiquement dans OnInit(). Tout d'abord, nous devons décider quel mode nous utiliserons (multi-délai ou multi-devises). L'exemple de code ci-dessous montre le mode multi-devises, mais tout ce qui est nécessaire pour le mode multi-délai se trouve également ici dans les commentaires. Pour le mode multi-devises, nous devons créer un tableau de symboles et le transmettre au constructeur CTable. Pour le mode multi-délai, nous créerions un tableau de délai et le transmettrions au deuxième constructeur CTable.

Après cela, nous devons créer tous les composants nécessaires et les ajouter au panneau à l'aide de la méthode AddRow. En option, les paramètres du panneau peuvent être ajustés. Après tout, nous devons dessiner le panneau pour la première fois, nous appelons donc Update à la fin de OnInit(). OnDeinit est simple. La seule chose qu'il fait est de supprimer l'objet CTable, ce qui provoque l'appel du destructeur CTable.

OnCalculate(...) et OnChartEvent(...) sont identiques. Ils appellent uniquement la méthode Update. OnChartEvent(...) n'est nécessaire que si le panneau fonctionne en mode multi-devises. Dans ce mode, il gère les événements déclenchés par les SpyAgents. En mode multi-délai, seul OnCalculate(...) est nécessaire, car nous devons surveiller uniquement le symbole du graphique actuel.

//+------------------------------------------------------------------+
//|                                                  TableSample.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Table.mqh>
#include <PriceRow.mqh>
#include <PriceChangeRow.mqh>
#include <RSIRow.mqh>
#include <PriceMARow.mqh>

CTable *table; // pointer to CTable object
//+------------------------------------------------------------------+
//| Indicator initialization function                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- timeframes used in table (in multi-timeframe mode)
   ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1};

//--- symbols used in table (in multi-currency mode)
   string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" };
//-- CTable object creation 
//   table = new CTable(timeframes); // multi-timeframe mode
   table=new CTable(symbols); // multi-currency mode

//--- adding rows to the table
   table.AddRow(new CPriceRow());               // shows current price
   table.AddRow(new CPriceChangeRow(false));     // shows change of price in the last bar
   table.AddRow(new CPriceChangeRow(false,true)); // shows percent change of price in the last bar
   table.AddRow(new CPriceChangeRow(true));      // shows change of price as arrows
   table.AddRow(new CRSIRow(14));                // shows RSI(14)
   table.AddRow(new CRSIRow(10));                // shows RSI(10)
   table.AddRow(new CPriceMARow(MODE_SMA,20,0));  // shows if SMA(20) > current price

//--- setting table parameters
   table.SetFont("Arial",10,clrYellow);  // font, size, color
   table.SetCellSize(60, 20);           // width, height
   table.SetDistance(10, 10);           // distance from upper right chart corner

   table.Update(); // forces table to redraw

   return(0);
  }
//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- calls table destructor and frees memory
   delete(table);
  }
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- update table: recalculate/repaint
   table.Update();
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnChartEvent handler                                             |
//| Handles CHARTEVENT_CUSTOM events sent by SpyAgent indicators     |
//| nedeed only in multi-currency mode!                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   table.Update(); // update table: recalculate/repaint
  }
//+------------------------------------------------------------------+

Après avoir attaché cet indicateur au graphique, il commence à se mettre à jour et nous pouvons enfin voir le panneau fonctionner.


6. Installation

Tous les fichiers doivent être compilés. SpyAgent et TableSample sont des indicateurs et doivent être copiés dans terminal_data_folder\MQL5\Indicators. Les fichiers restants sont des fichiers include et doivent être placés dans terminal_data_folder\MQL5\Include. Pour exécuter le panneau, attachez l'indicateur TableSample à n'importe quel graphique. Il n'est pas nécessaire de joindre SpyAgent. Ils seront lancés automatiquement.


Conclusion

L'article fournit une implémentation orientée objet du panneau multi-délai et multi-devises pour MetaTrader 5. Il montre comment réaliser un design, qui est facilement extensible et permet de construire des panneaux personnalisés avec peu d'effort.

Tout le code présenté dans cet article peut être téléchargé ci-dessous.


Traduit de l’anglais par MetaQuotes Ltd.
Article original : https://www.mql5.com/en/articles/357

Systèmes de trading simples utilisant des indicateurs de sémaphore Systèmes de trading simples utilisant des indicateurs de sémaphore
Si nous examinons en profondeur tout système de trading complexe, nous verrons qu’il est basé sur un ensemble de signaux de trading simples. Par conséquent, il n’est pas nécessaire que les développeurs novices commencent à écrire des algorithmes complexes immédiatement. Cet article fournit un exemple de système de trading qui utilise des indicateurs de sémaphore pour effectuer des transactions.
Les bases de la programmation orientée objet Les bases de la programmation orientée objet
Vous n'avez pas besoin de savoir ce que sont le polymorphisme, l'encapsulation, etc. pour utiliser la programmation orientée objet (POO)... vous pouvez simplement utiliser ces fonctionnalités. Cet article couvre les bases de la POO avec des exemples pratiques.
Créez votre propre robot de trading en 6 étapes ! Créez votre propre robot de trading en 6 étapes !
Si vous ne savez pas comment les classes de trades se construisent et que vous avez peur des mots tels que « Programmation orientée objet », alors cet article est pour vous. En fait, vous n'avez pas besoin de connaître les détails pour écrire votre propre module de signaux de trading. Suivez simplement quelques règles simples. Tout le reste sera fait par l'assistant MQL5, et vous obtiendrez un robot de trading prêt à l'emploi !
Créez vos propres panneaux graphiques en MQL5 Créez vos propres panneaux graphiques en MQL5
La convivialité du programme MQL5 est déterminée à la fois par sa riche fonctionnalité et par une interface utilisateur graphique élaborée. La perception visuelle est parfois plus importante qu'un fonctionnement rapide et stable. Voici un guide étape par étape pour créer vous-même des panneaux d'affichage sur la base des classes de la bibliothèque standard.