English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Création d'un panneau d'information à l'aide des classes de bibliothèque standard et de l'API Google Chart

Création d'un panneau d'information à l'aide des classes de bibliothèque standard et de l'API Google Chart

MetaTrader 5Exemples | 22 décembre 2021, 16:25
128 0
Евгений
Евгений


Introduction

Pour faciliter la vie des programmeurs du langage MQL5, les concepteurs ont créé une Bibliothèque standard, qui couvre presque toutes les fonctions de l'API MQL5 et rend leur utilisation beaucoup plus facile et pratique. Cet article tentera de créer un panneau d'information, avec un nombre maximum de classes utilisées par la bibliothèque standard.


1. Présentation des classes de la bibliothèque standard

Alors, quelle est exactement cette bibliothèque ? La section Documentation du site Internet précise qu'elle est composée de :

Les fichiers, contenant les codes de toutes les classes, sont situés dans le dossier MQL5/Include. Lors de la visualisation du code de la bibliothèque, vous remarquerez qu'il ne fournit que les classes, mais pas les fonctions. Par conséquent, pour l'utiliser, vous devez avoir des connaissances en programmation orientée objet (POO). 

Toutes les classes de la bibliothèque (à l'exception des classes de trading) proviennent de la classe de base CObject. Pour le montrer, nous allons essayer de construire un Diagramme de classe, puisque nous avons tout ce que cela nécessite - la classe de base et ses héritiers. Puisque le langage MQL5 est fondamentalement un sous-ensemble de C++, utilisons l'instrument IBM Rational Rose, qui fournit des outils de rétro-ingénierie des projets C++, pour la construction automatique du diagramme.

 

Figure 1. Diagramme des classes de la bibliothèque standard

Nous ne montrerons pas les propriétés et les méthodes de la classe, en raison des diagrammes encombrants que nous obtiendrions. Nous omettrons également les agrégations, car elles n'ont aucune importance pour nous. En conséquence, il ne nous reste que des généralisations (héritages), qui nous permettent de savoir quelles propriétés et méthodes les classes obtiennent.

Comme on peut le voir sur le diagramme, chaque composant de bibliothèque qui fonctionne avec des lignes, des fichiers, des graphiques, des objets graphiques et des tableaux a sa propre classe de base (CString, CFile, CChart, CChartObject et CArray, respectivement), héritée de CObject. La classe de base pour travailler avec les indicateurs CIndicator et sa classe auxiliaire CIndicators est héritée de CArrayObj, tandis que l'accès à la classe de tampon d'indicateur CIndicatorBuffer est héritée de CArrayDouble.

La couleur pourpre dans le diagramme marque l'inexistant dans les classes, les indicateurs, les tableaux et les ChartObjects - ce sont des ensembles, qui incluent des classes pour travailler avec des indicateurs, des tableaux et des objets graphiques. Comme il y en a un grand nombre, et qu'ils sont hérités d'un seul parent, j'ai permis une certaine simplification, afin de ne pas encombrer le diagramme. Par exemple, l'indicateur comprend CiDEMA, CiStdDev, etc.

Il convient également de noter que le diagramme de classes peut également être construit en utilisant la création automatique du système de documentation Doxygen. Il est un peu plus facile de le faire dans ce système que dans Rational Rose. Pour en savoir plus sur Doxygen, consultez l'article Documentation générée automatiquement pour le code MQL5.


2. Le problème

Essayons de créer une table d'informations avec le nombre maximum de classes de la bibliothèque standard.

Qu'est-ce que le tableau affichera ? Quelque chose comme un rapport détaillé de MetaTrader 5, c'est-à-dire :

Figure 2. L'apparence du rapport détaillé

Figure 2. L'apparence du rapport détaillé

Comme nous pouvons le voir, le rapport affiche un graphique des soldes et quelques chiffres de trading. Vous trouverez plus d'informations sur les méthodes de calcul de ces indicateurs dans l'article La signification des chiffres du rapport de test de l’Expert Advisor.

Le tableau étant utilisé à des fins purement informatives et n'effectuant aucune opération de trading, il sera préférable de l'implémenter en tant qu'indicateur, dans une fenêtre séparée, afin d'éviter de fermer le graphique proprement dit. De plus, le placer dans une sous-fenêtre permet une graduation facile et même la fermeture du tableau d'un seul mouvement de souris.

Vous pouvez également compléter le rapport par un graphique à secteurs, qui affichera le nombre de transactions effectuées sur l'instrument, par rapport au nombre total de transactions.


3. Conception de l'interface 

Nous avons défini nos objectifs - nous avons besoin d'un rapport détaillé dans la sous-fenêtre du graphique principal.

Nous mettons en œuvre notre panneau d'information en tant que classe. Commençons :

//+------------------------------------------------------------------+
///The Board class
//+------------------------------------------------------------------+
class Board
  {
//protected data
protected:
///number of the sub-window where the board will be stored
   int               wnd;             
///array with the deals data   
   CArrayObj        *Data;
///array with the balance data   
   CArrayDouble      ChartData;       
///array with elements of the interface   
   CChartObjectEdit  cells[10][6];    
///object for working with the chart   
   CChart            Chart;           
///object for working with the balance chart
   CChartObjectBmpLabel BalanceChart; 
///object for working with the pie chart
   CChartObjectBmpLabel PieChart;     
///data for the pie chart
   PieData          *pie_data;
//private data and methods
private:
   double            net_profit;      //these variables will store the calculated characteristics
   double            gross_profit;
   double            gross_loss;
   double            profit_factor;
   double            expected_payoff;
   double            absolute_drawdown;
   double            maximal_drawdown;
   double            maximal_drawdown_pp;
   double            relative_drawdown;
   double            relative_drawdown_pp;
   int               total;
   int               short_positions;
   double            short_positions_won;
   int               long_positions;
   double            long_positions_won;
   int               profit_trades;
   double            profit_trades_pp;
   int               loss_trades;
   double            loss_trades_pp;
   double            largest_profit_trade;
   double            largest_loss_trade;
   double            average_profit_trade;
   double            average_loss_trade;
   int               maximum_consecutive_wins;
   double            maximum_consecutive_wins_usd;
   int               maximum_consecutive_losses;
   double            maximum_consecutive_losses_usd;
   int               maximum_consecutive_profit;
   double            maximum_consecutive_profit_usd;
   int               maximum_consecutive_loss;
   double            maximum_consecutive_loss_usd;
   int               average_consecutive_wins;
   int               average_consecutive_losses;

   ///method of obtaining data about the deals and the balance
   void              GetData();

   ///method of calculating the characteristics
   void              Calculate();
   
   ///method of chart construction
   void              GetChart(int X_size,int Y_size,string request,string file_name);
   
   ///method of request to Google Charts API
   string            CreateGoogleRequest(int X_size,int Y_size,bool type);
   
  ///method of obtaining the optimum font size
   int               GetFontSize(int x,int y);
   string            colors[12];  //array with text presentation of colors
//public methods
public:
///constructor
   void              Board();    
///destructor         
   void             ~Board();    
///method for board update
   void              Refresh();  
///method for creating interface elements   
   void              CreateInterface(); 
  };

Les données de classe protégées sont les éléments d'interface et les données de transaction, de solde et de camembert (la classe PieData sera discutée ci-dessous). Les indicateurs de trading et certaines méthodes sont privés. Ils sont privés car l'utilisateur ne doit pas y accéder directement, ils sont calculés au sein de la classe et ne peuvent être comptés qu'en appelant la méthode publique appropriée.

De plus, les méthodes de création d'interface et de calcul d'indicateurs sont privées, car ici vous devez subir une séquence rigoureuse d'appels de méthodes. Par exemple, il est impossible de calculer les indicateurs sans avoir les données pour le calcul, ou de mettre à jour l'interface, sans avoir à la créer au préalable. Ainsi, nous ne permettrons pas à l'utilisateur de « se tirer une balle dans le pied ». 

Traitons tout de suite des constructeurs et des destructeurs d'une classe, nous n'avons donc pas à y revenir plus tard :

//+------------------------------------------------------------------+
///Constructor
//+------------------------------------------------------------------+
void Board::Board()
  {
   Chart.Attach();                               //attach the current chart to the class instance
   wnd=ChartWindowFind(Chart.ChartId(),"IT");    //find the indicator window
   Data = new CArrayObj;                         //creating the CArrayObj class instance
   pie_data=new PieData;                         //creating the PieData class instance
   //fill colors array
   colors[0]="003366"; colors[1]="00FF66"; colors[2]="990066";
   colors[3]="FFFF33"; colors[4]="FF0099"; colors[5]="CC00FF";
   colors[6]="990000"; colors[7]="3300CC"; colors[8]="000033";
   colors[9]="FFCCFF"; colors[10]="CC6633"; colors[11]="FF0000";
  }
//+------------------------------------------------------------------+
///Destructor
//+------------------------------------------------------------------+
void Board::~Board()
  {
   if(CheckPointer(Data)!=POINTER_INVALID) delete Data;   //delete the deals data
   if(CheckPointer(pie_data)!=POINTER_INVALID) delete pie_data;
   ChartData.Shutdown();    //and balance data
   Chart.Detach();          //detach from the chart
   for(int i=0;i<10;i++)    //delete all interface elements
      for(int j=0;j<6;j++)
         cells[i][j].Delete();
   BalanceChart.Delete();   //delete the balance chart
   PieChart.Delete();       //and pie chart
  }

Dans le constructeur, nous allons lier un objet de type CChart au graphique courant à l'aide de sa méthode Attach(). La méthode Detach(), appelée dans le destructeur, détachera le graphique de l'objet. L'objet de données, qui est un pointeur vers un objet de type CArrayObj, a reçu l'adresse de l'objet, créé dynamiquement à l'aide de la nouvelle opération et supprimé dans le destructeur à l'aide de l'opération Supprimer. N'oubliez pas de vérifier la présence de l'objet à l'aide du CheckPointer() avant de supprimer, sinon une erreur se produira.

Plus d'informations sur la classe CArrayObj seront fournies plus loin. La méthode Shutdown() de la classe CArrayDouble comme toute autre classe est héritée de la classe CArray (voir diagramme des classes) effacera et libérera la mémoire occupée par l'objet. La méthode Delete() des héritiers de la classe CChartObject supprime l'objet du graphique.

Ainsi, le constructeur alloue la mémoire et le destructeur la libère, et supprime les objets graphiques, créés par la classe. 

Parlons maintenant de l'interface. Comme indiqué ci-dessus, la méthode CreateInterface() crée une interface de la carte :

//+------------------------------------------------------------------+
///CreateInterface function
//+------------------------------------------------------------------+
void Board::CreateInterface()
  {
   //retrieve the width
   int x_size=Chart.WidthInPixels();
   //and the height of the indicator window
   int y_size=Chart.GetInteger(CHART_HEIGHT_IN_PIXELS,wnd);
   
    //calculate, how much space will the balance chart take up
   double chart_border=y_size*(1.0-(Chart_ratio/100.0));

   if(Chart_ratio<100)//if the balance chart is taking up the entire table
     {
      for(int i=0;i<10;i++)//create columns
        {
         for(int j=0;j<6;j++)//and rows
           {
            cells[i][j].Create(Chart.ChartId(),"InfBoard "+IntegerToString(i)+" "+IntegerToString(j),
                               wnd,j*(x_size/6.0),i*(chart_border/10.0),x_size/6.0,chart_border/10.0);
            //set selectable property to false
            cells[i][j].Selectable(false);
            //set text as read only
            cells[i][j].ReadOnly(true);
            //set font size
            cells[i][j].FontSize(GetFontSize(x_size/6.0, chart_border/10.0));
            cells[i][j].Font("Arial");    //font name
            cells[i][j].Color(text_color);//font color
           }
        }
     }

   if(Chart_ratio>0)//if the balance chart is required
     {
      //create a balance chart
      BalanceChart.Create(Chart.ChartId(), "InfBoard chart", wnd, 0, chart_border);
      //set selectable property to false
      BalanceChart.Selectable(false);
      //create a pie chart
      PieChart.Create(Chart.ChartId(), "InfBoard pie_chart", wnd, x_size*0.75, chart_border);
      PieChart.Selectable(false);//set selectable property to false
     }

   Refresh();//refresh the board
  }

Pour un arrangement compact de tous les éléments, tout d'abord, à l'aide des méthodes WidthInPixels() et GetInteger() de la classe CChart, découvrez la longueur et la largeur de la sous-fenêtre de l'indicateur, dans laquelle sera localisé la carte. Ensuite, nous créons les cellules, qui comprendront les valeurs des indicateurs, en utilisant la méthode Create() de la classe CChartObjectEdit (qui crée le « champ de saisie »), tous les héritiers disposent de cette méthode de CChartObject.

Notez à quel point il est pratique d'utiliser la Bibliothèque standard pour des opérations de ce type. Sans cela, nous devrions créer chaque objet, en utilisant la fonction ObjectCreate, et configurer les propriétés des objets, en utilisant des fonctions telles que ObjectSet, ce qui entraînerait une redondance du code. Et quand plus tard on voudrait changer les propriétés des objets, il faudrait contrôler soigneusement les noms des objets afin d'éviter toute confusion. Maintenant, nous pouvons simplement créer un tableau d'objets graphiques et le parcourir à notre guise.

De plus, nous pouvons obtenir/configurer les propriétés des objets à l'aide d'une fonction, si elle était surchargée par les créateurs de la classe, telle que la méthode Color() de la classe CChartObject. Lorsqu'elle est appelée avec les paramètres, elle les configure, sans paramètres - elle renvoie la couleur de l'objet. Placez le camembert à côté du graphique des soldes, il occupera un quart de la largeur totale de l'écran.

Refresh method() met à jour la carte. En quoi consiste cette mise à jour ? Il faut compter les indicateurs, les saisir dans les objets graphiques et redimensionner le tableau, si la taille de la fenêtre dans laquelle il se trouve a été modifiée. La carte doit occuper tout l'espace libre de la fenêtre.

//+------------------------------------------------------------------+
///Function of the board updating
//+------------------------------------------------------------------+
void Board::Refresh()
  {
   //check the server connection status
   if(!TerminalInfoInteger(TERMINAL_CONNECTED)) {Alert("No connection with the trading server!"); return;}
   //check the permission for importing functions from DLL
   if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Alert("DLLs are prohibited!"); return;}
   //calculate the characteristics
   Calculate();
   //retrieve the width
   int x_size=Chart.WidthInPixels();
   //and the height of the indicator window
   int y_size=Chart.GetInteger(CHART_HEIGHT_IN_PIXELS,wnd);
   //calculate how much space the balance chart will take up
   double chart_border=y_size*(1.0-(Chart_ratio/100.0));

   string captions[10][6]= //array with signatures of interface elements
     {
        {"Total Net Profit:"," ","Gross Profit:"," ","Gross Loss:"," "},
        {"Profit Factor:"," ","Expected Payoff:"," ","",""},
        {"Absolute Drawdown:"," ","Maximal Drawdown:"," ","Relative Drawdown:"," "},
        {"Total Trades:"," ","Short Positions (won %):"," ","Long Positions (won %):"," "},
        {"","","Profit Trades (% of total):"," ","Loss trades (% of total):"," "},
        {"Largest","","profit trade:"," ","loss trade:"," "},
        {"Average","","profit trade:"," ","loss trade:"," "},
        {"Maximum","","consecutive wins ($):"," ","consecutive losses ($):"," "},
        {"Maximal","","consecutive profit (count):"," ","consecutive loss (count):"," "},
        {"Average","","consecutive wins:"," ","consecutive losses:"," "}
     };

   //put the calculated characteristics into the array
   captions[0][1]=DoubleToString(net_profit, 2);
   captions[0][3]=DoubleToString(gross_profit, 2);
   captions[0][5]=DoubleToString(gross_loss, 2);

   captions[1][1]=DoubleToString(profit_factor, 2);
   captions[1][3]=DoubleToString(expected_payoff, 2);

   captions[2][1]=DoubleToString(absolute_drawdown, 2);
   captions[2][3]=DoubleToString(maximal_drawdown, 2)+"("+DoubleToString(maximal_drawdown_pp, 2)+"%)";
   captions[2][5]=DoubleToString(relative_drawdown_pp, 2)+"%("+DoubleToString(relative_drawdown, 2)+")";

   captions[3][1]=IntegerToString(total);
   captions[3][3]=IntegerToString(short_positions)+"("+DoubleToString(short_positions_won, 2)+"%)";
   captions[3][5]=IntegerToString(long_positions)+"("+DoubleToString(long_positions_won, 2)+"%)";

   captions[4][3]=IntegerToString(profit_trades)+"("+DoubleToString(profit_trades_pp, 2)+"%)";
   captions[4][5]=IntegerToString(loss_trades)+"("+DoubleToString(loss_trades_pp, 2)+"%)";

   captions[5][3]=DoubleToString(largest_profit_trade, 2);
   captions[5][5]=DoubleToString(largest_loss_trade, 2);

   captions[6][3]=DoubleToString(average_profit_trade, 2);
   captions[6][5]=DoubleToString(average_loss_trade, 2);

   captions[7][3]=IntegerToString(maximum_consecutive_wins)+"("+DoubleToString(maximum_consecutive_wins_usd, 2)+")";
   captions[7][5]=IntegerToString(maximum_consecutive_losses)+"("+DoubleToString(maximum_consecutive_losses_usd, 2)+")";

   captions[8][3]=DoubleToString(maximum_consecutive_profit_usd, 2)+"("+IntegerToString(maximum_consecutive_profit)+")";
   captions[8][5]=DoubleToString(maximum_consecutive_loss_usd, 2)+"("+IntegerToString(maximum_consecutive_loss)+")";

   captions[9][3]=IntegerToString(average_consecutive_wins);
   captions[9][5]=IntegerToString(average_consecutive_losses);

   if(Chart_ratio<100) //if the balance chart doesn't take up the entire table
     {
      for(int i=0;i<10;i++) //go through the interface elements
        {
         for(int j=0;j<6;j++)
           {
            //specify the position
            cells[i][j].X_Distance(j*(x_size/6.0));
            cells[i][j].Y_Distance(i*(chart_border/10.0));
            //the size
            cells[i][j].X_Size(x_size/6.0);
            cells[i][j].Y_Size(chart_border/10.0);
            //the text
            cells[i][j].SetString(OBJPROP_TEXT,captions[i][j]);
            //and font size
            cells[i][j].FontSize(GetFontSize(x_size/6.0,chart_border/10.0));
           }
        }
     }

   if(Chart_ratio>0)//if the balance chart is required
     {
      //refresh the balance chart
      int X=x_size*0.75,Y=y_size-chart_border;
      //get the chart
      GetChart(X,Y,CreateGoogleRequest(X,Y,true),"board_balance_chart");
      //set its position
      BalanceChart.Y_Distance(chart_border);
      //specify file names
      BalanceChart.BmpFileOn("board_balance_chart.bmp");
      BalanceChart.BmpFileOff("board_balance_chart.bmp");
      //refresh the pie chart
      X=x_size*0.25;
      //get the chart
      GetChart(X,Y,CreateGoogleRequest(X,Y,false),"pie_chart");
      //set its new position
      PieChart.Y_Distance(chart_border);
      PieChart.X_Distance(x_size*0.75);
      //specify file names
      PieChart.BmpFileOn("pie_chart.bmp");
      PieChart.BmpFileOff("pie_chart.bmp");
     }

   ChartRedraw(); //redraw the chart
  }

Il existe de nombreux codes, analogues à la méthode CreateInterface(), d'abord la fonction Calculate() calcule les indicateurs, puis ils sont entrés dans les objets graphiques, et simultanément les tailles des objets sont ajustées aux tailles des fenêtres, en utilisant les méthodes X_Size() et Y_Size(). Les méthodes X_Distance et Y_Distance modifient la position de l'objet.

Faites attention à la fonction GetFontSize(), elle sélectionne une taille de police, qui ne fera pas « déborder » le texte des bordures du conteneur après avoir été redimensionné, et, à l'inverse, ne deviendra pas trop petit.

Considérons cette fonction de plus près :

//import DLL function for string metrics
#import "String_Metrics.dll" 
void GetStringMetrics(int font_size,int &X,int &Y);
#import

//+------------------------------------------------------------------+
///Function of determining the optimum font size
//+------------------------------------------------------------------+
int Board::GetFontSize(int x,int y)
  {
   int res=8;
   for(int i=15;i>=1;i--)//go through the different font sizes
     {
      int X,Y; //here we input the line metrics
      //determine the metrics
      GetStringMetrics(i,X,Y);
      //if the line fits the set borders - return the font size
      if(X<=x && Y<=y) return i;
     }
   return res;
  }

La fonction GetStringMetrics() est importée de la DLL, décrite ci-dessus, dont le code se trouve dans l'archive DLL_Sources.zip et peut être modifié si nécessaire. Je pense que cela pourrait être utile si vous choisissiez de concevoir votre propre interface dans le projet.

Nous avons terminé avec l'interface utilisateur, passons au calcul des indicateurs de trading.


4. Calcul des indicateurs de trading

La méthode Calculate() effectue les calculs.

Mais nous avons aussi besoin de la méthode GetData(), qui reçoit les données nécessaires :

//+------------------------------------------------------------------+
///Function of receiving the deals and balance data
//+------------------------------------------------------------------+
void Board::GetData()
  {
   //delete old data
   Data.Shutdown();
   ChartData.Shutdown();
   pie_data.Shutdown();
   //prepare all the deals history
   HistorySelect(0,TimeCurrent()); 
   CAccountInfo acc_inf;   //object for work with account
   //calculate the balance
   double balance=acc_inf.Balance();
   double store=0; //balance
   long_positions=0;
   short_positions=0;
   long_positions_won=0;
   short_positions_won=0;
   for(int i=0;i<HistoryDealsTotal();i++) //go through all of the deals in the history

     {
      CDealInfo deal;  //the information about the deals will be stored here
      deal.Ticket(HistoryDealGetTicket(i));//get deal ticket
      //if the trade had a financial result (exit of the market)
      if(deal.Ticket()>=0 && deal.Entry()==DEAL_ENTRY_OUT)
        {
         pie_data.Add(deal.Symbol()); //add data for the pie chart
         //check for the symbol 
         if(!For_all_symbols && deal.Symbol()!=Symbol()) continue;
         double profit=deal.Profit(); //retrieve the trade profit
         profit+=deal.Swap();         //swap
         profit+=deal.Commission();   //commission
         store+=profit;               //cumulative profit
         Data.Add(new CArrayDouble);  //add new element to the array
         ((CArrayDouble *)Data.At(Data.Total()-1)).Add(profit);  //and data
         ((CArrayDouble *)Data.At(Data.Total()-1)).Add(deal.Type());
        }
     }

   //calculate the initial deposit
   double initial_deposit=(balance-store);
   for(int i=0;i<Data.Total();i++) //go through the prepared trades
     {
      //calculate the balance value
      initial_deposit+=((CArrayDouble *)Data.At(i)).At(0);
      ChartData.Add(initial_deposit); //and put it to the array
     }
  }

Tout d'abord, considérons la méthode de stockage des données. La bibliothèque standard fournit les classes de structures de données, ce qui vous permet de vous abstenir d'utiliser des tableaux. Nous avons besoin d'un tableau à deux dimensions, dans lequel nous stockerons des données sur les bénéfices et les types de transactions dans l'historique. Mais la bibliothèque standard ne fournit pas de classes explicites pour organiser des tableaux à deux dimensions, cependant il existe des classes CArrayDouble (tableau de type de données double) et CArrayObj (tableau dynamique de pointeurs vers les instances de classe CObject et ses héritiers). C'est-à-dire que nous pouvons créer un tableau de tableaux de type double, ce qui est exactement ce qui est fait. 

Bien sûr, les instructions comme ((CArrayDouble *) Data.At (Data.Total () - 1 )). Add (profit) n'a pas l'air aussi soigné que data [i] [j] = profit, mais ce n'est qu'à première vue. Après tout, en déclarant simplement un tableau, sans utiliser les classes de bibliothèque standard, nous sommes privés d'avantages tels qu'un gestionnaire de mémoire intégré, la possibilité d'insérer un tableau différent, de comparer des tableaux, de trouver des éléments, etc. Ainsi, l'utilisation de classes d'organisation mémoire nous libère de la nécessité de contrôler le débordement du tableau, et nous fournit de nombreux instruments utiles. 

La méthode Total() de la classe CArray (voir Fig. 1.) renvoie le nombre d'éléments dans le tableau, la méthode Add() les ajoute, la méthode At() renvoie les éléments.

Puisque nous avons décidé de créer un camembert, afin d'afficher le nombre de transactions pour les symboles, nous devons collecter les données nécessaires.

Nous écrirons une classe auxiliaire, destinée à collecter ces données :

//+------------------------------------------------------------------+
///The Pie chart class
//+------------------------------------------------------------------+
class PieData
  {
protected:
///number of deals per symbol
   CArrayInt         val;   
///symbols
   CArrayString      symb;  
public:
///delete the data
   bool Shutdown()          
     {
      bool res=true;
      res&=val.Shutdown();
      res&=symb.Shutdown();
      return res;
     }
///search for a sting in the array
   int Search(string str)   
     {  //check all array elements
      for(int i=0;i<symb.Total();i++)
         if(symb.At(i)==str) return i;
      return -1;
     }
///add new data
   void Add(string str)    
     {
      int symb_pos=Search(str);//determine symbol position in the array
      if(symb_pos>-1)
         val.Update(symb_pos,val.At(symb_pos)+1);//update the deals data
      else //if there isn't such a symbol yet
        {
         symb.Add(str); //add it
         val.Add(1);
        }
     }

   int Total() const {return symb.Total();}
   int Get_val(int pos) const {return val.At(pos);}
   string Get_symb(int pos) const {return symb.At(pos);}
  };

Ce n'est pas toujours que les classes de bibliothèque standard seront en mesure de nous fournir les méthodes de travail nécessaires. Dans cet exemple, la méthode Search() de la classe CArrayString ne convient pas, car pour l'appliquer, il faut d'abord trier le tableau, ce qui viole la structure des données. Nous avons donc dû écrire notre propre méthode.

Le calcul des caractéristiques de trade est implémenté dans la méthode Calculate() :

//+------------------------------------------------------------------+
///Calculation of characteristics
//+------------------------------------------------------------------+
void Board::Calculate()
  {
   //get the data
   GetData();
   //zero all characteristics
   gross_profit=0;
   gross_loss=0;
   net_profit=0;
   profit_factor=0;
   expected_payoff=0;
   absolute_drawdown=0;
   maximal_drawdown_pp=0;
   maximal_drawdown=0;
   relative_drawdown=0;
   relative_drawdown_pp=0;
   total=Data.Total();
   long_positions=0;
   long_positions_won=0;
   short_positions=0;
   short_positions_won=0;
   profit_trades=0;
   profit_trades_pp=0;
   loss_trades=0;
   loss_trades_pp=0;
   largest_profit_trade=0;
   largest_loss_trade=0;
   average_profit_trade=0;
   average_loss_trade=0;
   maximum_consecutive_wins=0;
   maximum_consecutive_wins_usd=0;
   maximum_consecutive_losses=0;
   maximum_consecutive_losses_usd=0;
   maximum_consecutive_profit=0;
   maximum_consecutive_profit_usd=0;
   maximum_consecutive_loss=0;
   maximum_consecutive_loss_usd=0;
   average_consecutive_wins=0;
   average_consecutive_losses=0;

   if(total==0) return; //there isn't deals - return from the function
   double max_peak=0,min_peak=0,tmp_balance=0;
   int max_peak_pos=0,min_peak_pos=0;
   int max_cons_wins=0,max_cons_losses=0;
   double max_cons_wins_usd=0,max_cons_losses_usd=0;
   int avg_win=0,avg_loss=0,avg_win_cnt=0,avg_loss_cnt=0;

   for(int i=0; i<total; i++)
     {
      double profit=((CArrayDouble *)Data.At(i)).At(0); //get profit
      int deal_type=((CArrayDouble *)Data.At(i)).At(1); //and deal type
      switch(deal_type) //check deal type
        {
         //and calculate number of long and short positions
         case DEAL_TYPE_BUY: {long_positions++; if(profit>=0) long_positions_won++; break;}
         case DEAL_TYPE_SELL: {short_positions++; if(profit>=0) short_positions_won++; break;}
        }

      if(profit>=0)//the deal is profitable
        {
         gross_profit+=profit; //gross profit
         profit_trades++;      //number of profit deals
         //the largest profitable trade and the largest profitable series
         if(profit>largest_profit_trade) largest_profit_trade=profit;

         if(maximum_consecutive_losses<max_cons_losses || 
            (maximum_consecutive_losses==max_cons_losses && maximum_consecutive_losses_usd>max_cons_losses_usd))
           {
            maximum_consecutive_losses=max_cons_losses;
            maximum_consecutive_losses_usd=max_cons_losses_usd;
           }
         if(maximum_consecutive_loss_usd>max_cons_losses_usd || 
            (maximum_consecutive_loss_usd==max_cons_losses_usd && maximum_consecutive_losses<max_cons_losses))
           {
            maximum_consecutive_loss=max_cons_losses;
            maximum_consecutive_loss_usd=max_cons_losses_usd;
           }
         //average profit per deal
         if(max_cons_losses>0) {avg_loss+=max_cons_losses; avg_loss_cnt++;}
         max_cons_losses=0;
         max_cons_losses_usd=0;
         max_cons_wins++;
         max_cons_wins_usd+=profit;
        }
      else //deal is losing
        {
         gross_loss-=profit; //cumulative profit
         loss_trades++;      //number of losing deals
         //the most unprofitable deal and the most unprofitable series
         if(profit<largest_loss_trade) largest_loss_trade=profit;
         if(maximum_consecutive_wins<max_cons_wins || 
            (maximum_consecutive_wins==max_cons_wins && maximum_consecutive_wins_usd<max_cons_wins_usd))
           {
            maximum_consecutive_wins=max_cons_wins;
            maximum_consecutive_wins_usd=max_cons_wins_usd;
           }
         if(maximum_consecutive_profit_usd<max_cons_wins_usd || 
            (maximum_consecutive_profit_usd==max_cons_wins_usd && maximum_consecutive_profit<max_cons_wins))
           {
            maximum_consecutive_profit=max_cons_wins;
            maximum_consecutive_profit_usd=max_cons_wins_usd;
           }
         //average lose per deal
         if(max_cons_wins>0) {avg_win+=max_cons_wins; avg_win_cnt++;}
         max_cons_wins=0;
         max_cons_wins_usd=0;
         max_cons_losses++;
         max_cons_losses_usd+=profit;
        }

      tmp_balance+=profit; //absolute drawdown calculation
      if(tmp_balance>max_peak) {max_peak=tmp_balance; max_peak_pos=i;}
      if(tmp_balance<min_peak) {min_peak=tmp_balance; min_peak_pos=i;}
      if((max_peak-min_peak)>maximal_drawdown && min_peak_pos>max_peak_pos) maximal_drawdown=max_peak-min_peak;
     }
   //maximal drawdown calculation
   double min_peak_rel=max_peak;
   tmp_balance=0;
   for(int i=max_peak_pos;i<total;i++)
     {
      double profit=((CArrayDouble *)Data.At(i)).At(0);
      tmp_balance+=profit;
      if(tmp_balance<min_peak_rel) min_peak_rel=tmp_balance;
     }
   //relative drawdown calculation
   relative_drawdown=max_peak-min_peak_rel;
   //net profit
   net_profit=gross_profit-gross_loss;
   //profit factor
   profit_factor=(gross_loss!=0) ?  gross_profit/gross_loss : gross_profit;
   //expected payoff
   expected_payoff=net_profit/total;
   double initial_deposit=AccountInfoDouble(ACCOUNT_BALANCE)-net_profit;
   absolute_drawdown=MathAbs(min_peak); 
   //drawdowns
   maximal_drawdown_pp=(initial_deposit!=0) ?(maximal_drawdown/initial_deposit)*100.0 : 0;
   relative_drawdown_pp=((max_peak+initial_deposit)!=0) ?(relative_drawdown/(max_peak+initial_deposit))*100.0 : 0;
   
   //profit and losing trade percentage
   profit_trades_pp=((double)profit_trades/total)*100.0;
   loss_trades_pp=((double)loss_trades/total)*100.0;
   
   //average profitable and losing deals
   average_profit_trade=(profit_trades>0) ? gross_profit/profit_trades : 0;
   average_loss_trade=(loss_trades>0) ? gross_loss/loss_trades : 0;
   
   //maximum consecutive losses
   if(maximum_consecutive_losses<max_cons_losses || 
      (maximum_consecutive_losses==max_cons_losses && maximum_consecutive_losses_usd>max_cons_losses_usd))
     {
      maximum_consecutive_losses=max_cons_losses;
      maximum_consecutive_losses_usd=max_cons_losses_usd;
     }
   if(maximum_consecutive_loss_usd>max_cons_losses_usd || 
      (maximum_consecutive_loss_usd==max_cons_losses_usd && maximum_consecutive_losses<max_cons_losses))
     {
      maximum_consecutive_loss=max_cons_losses;
      maximum_consecutive_loss_usd=max_cons_losses_usd;
     }

   if(maximum_consecutive_wins<max_cons_wins || 
      (maximum_consecutive_wins==max_cons_wins && maximum_consecutive_wins_usd<max_cons_wins_usd))
     {
      maximum_consecutive_wins=max_cons_wins;
      maximum_consecutive_wins_usd=max_cons_wins_usd;
     }
   if(maximum_consecutive_profit_usd<max_cons_wins_usd || 
      (maximum_consecutive_profit_usd==max_cons_wins_usd && maximum_consecutive_profit<max_cons_wins))
     {
      maximum_consecutive_profit=max_cons_wins;
      maximum_consecutive_profit_usd=max_cons_wins_usd;
     }
   //average loss and profit
   if(max_cons_losses>0) {avg_loss+=max_cons_losses; avg_loss_cnt++;}
   if(max_cons_wins>0) {avg_win+=max_cons_wins; avg_win_cnt++;}
   average_consecutive_wins=(avg_win_cnt>0) ? round((double)avg_win/avg_win_cnt) : 0;
   average_consecutive_losses=(avg_loss_cnt>0) ? round((double)avg_loss/avg_loss_cnt) : 0;
   
   //number of profitable long and short positions
   long_positions_won=(long_positions>0) ?((double)long_positions_won/long_positions)*100.0 : 0;
   short_positions_won=(short_positions>0) ?((double)short_positions_won/short_positions)*100.0 : 0;
  }

5. Utilisation de l'API Google Chart pour création d’un graphique de solde

Google Chart API permet aux développeurs de créer instantanément des diagrammes de différents types. L'API Google Chart est stockée au niveau du lien vers la ressource (URL) sur les serveurs Web de Google et lors de la réception d'un lien correctement formaté (URL), renvoie le diagramme sous forme d'image.

Les caractéristiques du diagramme (couleurs, en-têtes, axes, points sur le graphique, etc.) sont spécifiées par le lien (URL). L'image qui en résulte peut être stockée dans un système de fichiers ou une base de données. L'aspect le plus agréable est que l'API Google Chart est gratuite et ne nécessite pas d'avoir un compte ou de passer par le processus d'enregistrement. 

La méthode GetChart() reçoit le graphique de Google et l'enregistre sur le disque :

#import "PNG_to_BMP.dll"//import of DLL with the function of conversion of PNG images to BMP
bool Convert_PNG(string src,string dst);
#import

#import "wininet.dll"//import DLL with the function for working with the internet
int InternetAttemptConnect(int x);
int InternetOpenW(string sAgent,int lAccessType,
                  string sProxyName="",string sProxyBypass="",
                  int lFlags=0);
int InternetOpenUrlW(int hInternetSession,string sUrl,
                     string sHeaders="",int lHeadersLength=0,
                     int lFlags=0,int lContext=0);
int InternetReadFile(int hFile,char &sBuffer[],int lNumBytesToRead,
                     int &lNumberOfBytesRead[]);
int InternetCloseHandle(int hInet);
#import

//+------------------------------------------------------------------+
///Function of creating a balance chart
//+------------------------------------------------------------------+
void Board::GetChart(int X_size,int Y_size,string request,string file_name)
  {
   if(X_size<1 || Y_size<1) return; //too small
   //try to create connection
   int rv=InternetAttemptConnect(0);
   if(rv!=0) {Alert("Error in call of the InternetAttemptConnect()"); return;}
   //initialize the structures
   int hInternetSession=InternetOpenW("Microsoft Internet Explorer", 0, "", "", 0);
   if(hInternetSession<=0) {Alert("Error in call of the InternetOpenW()"); return;}
   //send request
   int hURL=InternetOpenUrlW(hInternetSession, request, "", 0, 0, 0);
   if(hURL<=0) Alert("Error in call of the InternetOpenUrlW()");
   //file with the result
   CFileBin chart_file;
   //let's create it
   chart_file.Open(file_name+".png",FILE_BIN|FILE_WRITE);
   int dwBytesRead[1]; //number of data read
   char readed[1000];  //the data 
   //read the data, returned by server after the request
   while(InternetReadFile(hURL,readed,1000,dwBytesRead))
     {
      if(dwBytesRead[0]<=0) break; //no data - exit
      chart_file.WriteCharArray(readed,0,dwBytesRead[0]); //write data to file
     }
   InternetCloseHandle(hInternetSession);//close connection
   chart_file.Close();//close file
   //******************************
   //prepare the paths for the converter
   CString src;
   src.Assign(TerminalInfoString(TERMINAL_PATH));
   src.Append("\MQL5\Files\\"+file_name+".png");
   src.Replace("\\","\\\\");
   CString dst;
   dst.Assign(TerminalInfoString(TERMINAL_PATH));
   dst.Append("\MQL5\Images\\"+file_name+".bmp");
   dst.Replace("\\","\\\\");
   //convert the file
   if(!Convert_PNG(src.Str(),dst.Str())) Alert("Error in call of the Convert_PNG()");
  }
  

Vous pouvez obtenir des détails sur l'utilisation des outils en ligne d'API Windows et MQL5 dans l'article Utilisation de WinInet.dll pour l'échange de données entre terminaux via Internet. Par conséquent, je ne passerai pas de temps là-dessus. J’ai écrit la fonction importée Convert_PNG() pour convertir des images PNG en BMP.

C'est nécessaire car Google Chart renvoie des graphiques au format PNG ou GIF, et l'objet « étiquette graphique » n'accepte que les images BMP. Le code des fonctions de la bibliothèque PNG_to_BMP.dll correspondantes se trouve dans l'archive DLL_Sources.zip.

Cette fonction montre également quelques exemples de travail avec des lignes et des fichiers, en utilisant la bibliothèque standard. Les méthodes de la classe CString permettent d'effectuer les mêmes opérations que les fonctions String. La classe CFile est la base des classes CFileBin et CFileTxt. Avec leur aide, nous pouvons produire la lecture et l'enregistrement de fichiers binaires et texte, respectivement. Les méthodes sont similaires aux fonctions pour travailler avec des fichiers.

Enfin, nous décrirons la fonction CreateGoogleRequest () - elle crée des demandes à partir des données sur le solde :

//+------------------------------------------------------------------+
///Function for creating a request for the Google Charts server
//+------------------------------------------------------------------+
string Board::CreateGoogleRequest(int X_size,int Y_size,bool type)
  {
   if(X_size>1000) X_size=1000; //check the chart size
   if(Y_size>1000) Y_size=300;  //to make sure it is not too large
   if(X_size<1) X_size=1;       //and small//s18>
   if(Y_size<1) Y_size=1;
   if(X_size*Y_size>300000) {X_size=1000; Y_size=300;}//and fit the area
   CString res; //string with results
   if(type) //create request for the balance chart
     {
      //prepare the request
      res.Assign("http://chart.apis.google.com/chart?cht=lc&chs=");
      res.Append(IntegerToString(X_size));
      res.Append("x");
      res.Append(IntegerToString(Y_size));
      res.Append("&chd=t:");
      for(int i=0;i<ChartData.Total();i++)
         res.Append(DoubleToString(ChartData.At(i),2)+",");
      res.TrimRight(",");
      //sort array
      ChartData.Sort();
      res.Append("&chxt=x,r&chxr=0,0,");
      res.Append(IntegerToString(ChartData.Total()));
      res.Append("|1,");
      res.Append(DoubleToString(ChartData.At(0),2)+",");
      res.Append(DoubleToString(ChartData.At(ChartData.Total()-1),2));
      res.Append("&chg=10,10&chds=");
      res.Append(DoubleToString(ChartData.At(0),2)+",");
      res.Append(DoubleToString(ChartData.At(ChartData.Total()-1),2));
     }
   else //create request for the pie chart
     {
      //prepare the request
      res.Assign("http://chart.apis.google.com/chart?cht=p3&chs=");
      res.Append(IntegerToString(X_size));
      res.Append("x");
      res.Append(IntegerToString(Y_size));
      res.Append("&chd=t:");
      for(int i=0;i<pie_data.Total();i++)
         res.Append(IntegerToString(pie_data.Get_val(i))+",");
      res.TrimRight(",");
      res.Append("&chdl=");
      for(int i=0;i<pie_data.Total();i++)
         res.Append(pie_data.Get_symb(i)+"|");
      res.TrimRight("|");
      res.Append("&chco=");
      int cnt=0;
      for(int i=0;i<pie_data.Total();i++)
        {
         if(cnt>11) cnt=0;
         res.Append(colors[cnt]+"|");
         cnt++;
        }
      res.TrimRight("|");
     }
   return res.Str(); //return the result
  }

Notez que les demandes de graphique des soldes et de camembert sont collectées séparément. La méthode Append() ajoute une autre ligne à la fin de la ligne existante et la méthode TrimRight() vous permet de supprimer des caractères supplémentaires, affichés à la fin de la ligne.


6. Assemblage final et tests

La classe est prête, testons-la. On commence par l'indicateur OnInit() :

Board *tablo;   //pointer to the board object
int prev_x_size=0,prev_y_size=0,prev_deals=0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   //set indicator short name
   IndicatorSetString(INDICATOR_SHORTNAME,"IT");
   //launch the timer
   EventSetTimer(1); 
   //create object instance
   tablo=new Board;
   //and the interface
   tablo.CreateInterface(); 
   prev_deals=HistoryDealsTotal(); //number of deals
   //current sizes of the window
   prev_x_size=ChartGetInteger(0,CHART_WIDTH_IN_PIXELS); 
   prev_y_size=ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
//---
   return(0);
  }

Ici, nous créons dynamiquement l'instance de la classe Board, lançons le minuteur, initialisons les variables auxiliaires. 

Tout de suite nous plaçons la fonction OnDeinit(), là nous allons supprimer l'objet (qui invoque automatiquement le destructeur), et arrêter le minuteur :

void OnDeinit(const int reason)
{
   EventKillTimer(); //stop the timer
   delete table;    //and board
}

La fonction OnCalculate () surveillera le flux de nouvelles transactions, trait par trait, et mettra à jour l'affichage, si cela se produit :

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[])
  {
//---
   //prepare the history
   HistorySelect(0,TimeCurrent());
   int deals=HistoryDealsTotal();
   //update the board if number of deals has changed
   if(deals!=prev_deals) tablo.Refresh();
   prev_deals=deals;
//--- return value of prev_calculated for next call
   return(rates_total);
  }

La fonction OnTimer() surveille les changements de taille de la fenêtre et, si nécessaire, personnalise la taille d'affichage, elle surveille également les transactions tout comme OnCalculate(), au cas où les traits seraient plus rares que 1 par seconde.

void OnTimer()
  {
   int x_size=ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int y_size=ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   //update the board if window size has changed
   if(x_size!=prev_x_size || y_size!=prev_y_size) tablo.Refresh();
   prev_x_size=x_size;
   prev_y_size=y_size;
   //update the board if number of deals has changed
   HistorySelect(0,TimeCurrent());
   int deals=HistoryDealsTotal();
   if(deals!=prev_deals) tablo.Refresh();
   prev_deals=deals;
  }

Compilez et exécutez l'indicateur :

Figure 3. L’affichage final du tableau

Figure 3. L’affichage final du tableau

Conclusion

Cher lecteur, j'espère qu'en lisant cet article, vous trouverez quelque chose de nouveau pour vous-même. J'ai essayé d'ouvrir devant vous toutes les potentialités d'un instrument aussi merveilleux que la Bibliothèque standard, car il offre aisance, rapidité et performance de haute qualité. Bien sûr, vous devez avoir une certaine connaissance de la POO.

Bonne chance. 

Pour commencer, décompressez l'archive MQL5.rar dans le dossier du terminal et autorisez l'utilisation de la DLL. L'archive DLL_Sources.zip contient les codes sources des bibliothèques String_Metrics.dll PNG_to_BMP.dll, ils ont été écrits par mes soins dans l'environnement Borland C++ Builder avec un GDI installé. 

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

Fichiers joints |
infoboard.zip (200 KB)
dll_sources.zip (1.62 KB)
infoboard-doc-en.zip (1836.47 KB)
Fonctions de gestion monétaire dans un Expert Advisor Fonctions de gestion monétaire dans un Expert Advisor
Le développement de stratégies de trading se concentre principalement sur la recherche de modèles d’entrée et de sortie du marché, ainsi que sur le maintien des positions. Si nous sommes en mesure de formaliser certains modèles dans des règles de trading automatisé, alors le trader est confronté à la question de calculer le volume des positions, la taille des marges, ainsi que de maintenir un niveau sûr de fonds hypothécaires pour assurer des positions ouvertes en mode automatisé. Dans cet article, nous utiliserons le langage MQL5 pour construire des exemples simples de réalisation de ces calculs.
Guide étape par étape pour rédiger un conseiller expert en MQL5 pour les débutants Guide étape par étape pour rédiger un conseiller expert en MQL5 pour les débutants
La programmation des Expert Advisors dans MQL5 est simple et vous pouvez l'apprendre facilement. Dans ce guide étape par étape, vous verrez les étapes de base nécessaires à la rédaction d'un simple Expert Advisor basé sur une stratégie de trading développée. La structure d'un Expert Advisor, l'utilisation d'indicateurs techniques et de fonctions de trading intégrés, les détails du mode Debug et l'utilisation du Testeur de stratégie sont ici présentés.
Comment écrire un indicateur à partir d'un autre indicateur Comment écrire un indicateur à partir d'un autre indicateur
Dans MQL5, vous pouvez écrire un indicateur à la fois à partir de zéro et à partir d'un autre indicateur déjà existant, intégré au terminal client ou personnalisé. Et ici, vous avez également deux manières - d'améliorer un indicateur en lui ajoutant de nouveaux calculs et styles graphiques, ou d'utiliser un indicateur intégré au terminal client ou un indicateur personnalisé via les fonctions iCustom() ou IndicatorCreate().
Nouvelles opportunités avec MetaTrader 5 Nouvelles opportunités avec MetaTrader 5
MetaTrader 4 a gagné en popularité auprès des traders du monde entier, et il semblait que rien de plus ne pouvait être souhaité. Avec sa vitesse de traitement élevée, sa stabilité, son large éventail de possibilités d'écriture d'indicateurs, d'Expert Advisors et de systèmes de trading informatifs, et la possibilité de choisir parmi plus d'une centaine de courtiers différents, le terminal s'est grandement distingué des autres. Mais le temps ne s'arrête pas et nous nous trouvons face à un choix de MetaTrade 4 ou MetaTrade 5. Dans cet article, nous décrirons les principales différences du terminal de 5ème génération par rapport à notre actuel favori.