English Русский 中文 Español Deutsch 日本語 Português Italiano Türkçe
preview
Multi-bot dans MetaTrader : Lancement de plusieurs robots à partir d'un seul graphique

Multi-bot dans MetaTrader : Lancement de plusieurs robots à partir d'un seul graphique

MetaTrader 5Exemples | 30 septembre 2024, 08:46
140 0
Evgeniy Ilin
Evgeniy Ilin

Sommaire


Introduction

Dans le monde des marchés financiers, les systèmes de trading automatisés font désormais partie intégrante du processus de prise de décision. Ces systèmes peuvent être configurés pour analyser le marché, prendre des décisions d'entrée et de sortie et exécuter des transactions à l'aide de règles et d'algorithmes prédéfinis. Mais la mise en place et l'exécution de robots sur plusieurs graphiques peut prendre beaucoup de temps. Chaque robot doit être configuré individuellement pour chaque graphique, ce qui demande un effort supplémentaire.

Dans cet article, je vais vous montrer ma mise en œuvre d'un modèle simple qui vous permet de créer un robot universel pour plusieurs graphiques dans MetaTrader 4 et dans MetaTrader 5. Notre modèle vous permettra d'attacher le robot à un graphique, alors que les autres graphiques seront traités dans l'EA. Notre modèle simplifie ainsi grandement le processus de mise en place et d'exécution des robots sur plusieurs graphiques, ce qui permet aux traders d'économiser du temps et des efforts. Dans cet article, je vais examiner en détail le processus de création d'un robot de ce type dans MQL5, de l'idée au test.


Énoncé du problème et limites d'application

Cette idée m'est venue il n'y a pas si longtemps, même si j'observe depuis longtemps des décisions similaires de la part de vendeurs professionnels. En d'autres termes, je ne suis ni le premier ni le dernier à avoir une idée dans ce domaine. Mais comme toujours, certaines conditions doivent être réunies pour que le programmeur puisse commencer à prendre de telles décisions. La raison principale du développement de ces Expert Advisors dans la boutique MQL5 est le désir de confort de l'utilisateur. Mais dans mon cas, la motivation était légèrement différente. Ma motivation était que je devais d'abord tester soit plusieurs stratégies simultanément pour plusieurs instruments, soit la même stratégie, mais afin de voir ses caractéristiques multidevises.

Un facteur très important lors du test d'une stratégie dans le testeur, en particulier en mode multidevises, est également une courbe générale de rentabilité, qui est la base de toute évaluation des systèmes de trading automatique lors du backtesting sur des données historiques. Lorsque l'on teste des systèmes de trading séparément sur un instrument, il est assez difficile de combiner ces rapports par la suite. Je n'ai pas connaissance de tels outils, du moins pour MetaTrader 5. En ce qui concerne la quatrième version du terminal, il existe un outil non officiel pour ce genre de manipulations. Je l'ai utilisé dans au moins un article, mais il va de soi qu'une telle approche n'est pas préférable.

En plus du processus de test, il existe un processus tout aussi important de trading automatique proprement dite et de synchronisation d'EA similaires qui fonctionnent indépendamment, chacun sur son propre graphique. Si ces graphiques sont trop nombreux, ils peuvent nécessiter des ressources informatiques supplémentaires, ce qui ralentit ou détériore les performances de trading et entraîne des erreurs inattendues et d'autres incidents désagréables qui peuvent avoir un effet préjudiciable sur le résultat final du trading. Pour chaque EA, nous devons trouver des identifiants d'ordre uniques, une protection contre les demandes de serveur à haute fréquence, ainsi que beaucoup d'autres choses qui ne sont pas évidentes à première vue.

Le traitement de la partie graphique de l'EA est une question distincte et très sensible. Aujourd'hui, tous les créateurs d'EA plus ou moins habiles font au moins une version minimale de certaines indications sur le graphique auquel l'EA est rattaché. De cette façon, l'EA semble plus sérieux et inspire plus de confiance, et également, presque toujours, l'affichage de certaines informations sur le graphique pour un utilisateur permet parfois un contrôle plus efficace sur le processus de trading de l'EA. Il est également possible d'ajouter des éléments de contrôle manuel si nécessaire. Tout cela s'appelle une interface utilisateur. Lors de la distribution de ces EA sur des graphiques, la charge liée à la mise à jour des informations graphiques, textuelles et numériques dans les interfaces augmente de manière exponentielle. Bien entendu, lorsque l'on utilise un modèle multiple, on dispose d'une interface qui nécessite un minimum de ressources de la part du terminal.

Ce type de modèle ne résout bien sûr pas tous les problèmes, mais il m'aide beaucoup dans mes projets. J'utilise différents robots et, en général, toutes les approches ont le droit d'exister. Mais je pense que de nombreux programmeurs novices peuvent trouver ce modèle utile. Il n'est pas nécessaire de le copier intégralement. Et si vous le souhaitez, vous pouvez facilement l'adapter à vos besoins. Mon but n'est pas de vous donner quelque chose d'extraordinaire, mais de montrer et d'expliquer l’une des options permettant de résoudre un tel problème.


Différences entre les terminaux MetaTrader 4 et MetaTrader 5 en termes d'utilisation d'un multi-bot

Ce que j'apprécie dans la dernière version de MetaTrader 5, c'est la puissance de son testeur, qui vous offre toutes les fonctionnalités dont vous avez besoin pour tester plusieurs instruments en même temps, à condition que vous utilisiez l'approche de développement d'EA décrite ci-dessus. Le testeur synchronise automatiquement les cotations, ce qui vous permet d'obtenir une courbe de rentabilité clairement synchronisée sur une échelle de temps. MetaTrader 4 ne dispose pas de cette fonctionnalité. Je pense que c'est là son principal inconvénient. Mais il convient de noter que MetaQuotes fait de son mieux pour soutenir le quatrième terminal et que sa popularité reste élevée. En tant qu'utilisateur actif de MetaTrader 4, je peux dire que ces lacunes ne sont pas aussi importantes qu'elles le paraissent.

Le langage MQL4 a été récemment mis à jour en MQL5. Cela signifie qu'en écrivant des modèles similaires aux nôtres, nous aurons un minimum de différences dans le code. Il est dans ma tradition d'essayer d'implémenter des chosespour les deux terminaux, vous recevrez donc un modèle pour les deux terminaux. Ces améliorations apportées à l'ancien terminal nous permettent, entre autres, d'utiliser les fonctions suivantes dont nous avons vraiment besoin :

  • CopyClose - pour obtenir les prix de clôture des barres
  • CopyOpen - pour obtenir les prix d'ouverture des barres
  • CopyHigh - pour obtenir les plus hauts des barres
  • CopyLow - pour obtenir les plus bas des barres
  • CopyTime - pour obtenir les prix d’ouverture des barres
  • SymbolInfoTick - pour obtenir le dernier tick entrant pour le symbole demandé
  • SymbolInfoInteger - pour obtenir des données du symbole, décrites par un nombre entier ou une liste numérotée
  • SymbolInfo******* - d’autres fonctions dont nous avons besoin

Ces caractéristiques sont présentes à la fois dans MQL4 et dans MQL5. Ces fonctions vous permettent d'obtenir des données sur les barres pour n'importe quel symbole et n'importe quelle période. Ainsi, la seule différence désagréable entre les testeurs de la quatrième et de la cinquième version est le fait que ces fonctions dans le quatrième terminal ne fonctionneront que pour le graphique actuel sur lequel le test est effectué, et le reste des requêtes vous informera simplement qu'il n'y a pas de données en raison des particularités du testeur de MetaTrader 4. Par conséquent, lorsque vous testez notre modèle, vous n'obtiendrez que des transactions sur le symbole sélectionné et une seule des courbes de profit pour un seul robot.

Dans le cinquième terminal, vous recevrez déjà les transactions pour tous les symboles demandés et la ligne commune de rentabilité. En ce qui concerne l'application dans le trading, lorsque vous tradez directement avec un tel robot dans les deux terminaux, vous bénéficiez de la pleine performance du modèle. En d'autres termes, la différence réside uniquement dans le testeur. Mais même dans ce cas, vous pouvez vous en sortir en créant un EA. Mais il est préférable de commencer par la version pour MetaTrader 5. Après tous les tests nécessaires, vous pouvez rapidement réaliser la version pour MetaTrader 4.

Bien entendu, il existe un certain nombre de différences que je n'ai pas abordées. Je tiens simplement à souligner l'importance de certaines d'entre elles, car ces nuances doivent être connues lors de la construction d'une structure élaborée pour un tel modèle. MetaTrader 5 est définitivement meilleur que son prédécesseur. Mais je n'ai néanmoins aucune envie de me débarrasser du quatrième terminal, car dans de nombreuses situations, sa demande en ressources informatiques n'est pas si importante par rapport au cinquième terminal. Les deux outils sont encore bons.


Les nuances de la construction d'un modèle universel

Pour construire un tel modèle, vous devez comprendre comment fonctionne le terminal, ce qu'est un Expert Advisor et ce qu'est un graphique MetaTrader. Vous devez également comprendre que chaque graphique est un objet distinct. Chacun de ces graphiques peut être associé à plusieurs indicateurs et à un seul EA. Il peut y avoir plusieurs graphiques identiques . Plusieurs graphiques sont généralement créés afin d'exécuter plusieurs EA différents surla période d’une symbole ou pour exécuter plusieurs copies d’ un EA avec des paramètres différents. En comprenant ces subtilités, nous devrions arriver à la conclusion que pour abandonner les graphiques multiples en faveur de notre modèle, nous devrons implémenter tout cela à l'intérieur de notre modèle. Cela peut être représenté sous la forme d'un diagramme :

structure des objets

Il convient également de parler des ticks. L'inconvénient de cette approche est que nous ne pourrons pas nous abonner au gestionnaire pour l'apparition d'un nouveau tick pour chaque graphique. Nousdevrons appliquer les ticks du graphique sur lequel notre robot travaille, ou utiliser le timer. En fin de compte, cela se traduira par des moments désagréables pour les robots de ticks :

  • Nous devrons écrire des gestionnaires OnTick personnalisés
  • Ces gestionnaires devront être implémentés comme des dérivations de OnTimer.
  • Les ticks ne seront pas parfaits car OnTimer fonctionne avec un délai (la valeur du délai n'est pas importante, mais sa présence l'est).
  • Pour obtenir des ticks, vous avez besoin de la fonction SymbolInfoTick.

Je pense que pour ceux qui comptent chaque milliseconde, cela peut être un moment irrésistible, surtout pour ceux qui aiment l'arbitrage. Mais je n'insiste pas sur ce point dans mon modèle. Au fil des années de construction de différents systèmes, j'en suis venu au paradigme du bar trading. Cela signifie que les opérations de trading et autres calculs se produisent pour la plupart lorsqu'une nouvelle barre apparaît. Cette approche présente un certain nombre d'avantages évidents :

  • L'imprécision dans la détermination du début d'une nouvelle barre n'a pas d'incidence significative sur les transactions.
  • Plus la période de la barre est longue, moins cette influence est importante.
  • La discrétisation sous forme de barres permet d'augmenter la vitesse de test de plusieurs ordres de grandeur.
  • La qualité des tests est la même, qu'il s'agisse de tests sur des ticks réels ou sur des ticks artificiels.

L'approche enseigne un certain paradigme de construction des EA. Ce paradigme élimine denombreux problèmes liés aux EA de type tick, accélère le processus de test, fournit une espérance mathématique de profit plus élevée, ce qui constitue le principal obstacle, et permet également d'économiser beaucoup de temps et de puissance de calcul. Je pense que l'on peut trouver bien d'autres avantages, mais je pense que cela suffit dans le contexte de cet article.

Pour mettre en œuvre notre modèle, il n'est pas nécessaire d'implémentertoute la structure de l'espace de travail du terminal de trading dans notre modèle, mais il suffit d'implémenter un graphique distinct pour chaque robot. Ce n'est pas la structure la plus optimale, mais si nous convenons que chaque instrument individuel ne sera présent qu'une seule fois dans la liste des instruments, cette optimisation n'est pas nécessaire. Il se présentera comme suit :

notre réalisation

Nous avons mis en place la structure la plus simple pour la mise en œuvre des graphiques. Il est maintenant temps de réfléchir aux entrées du modèle, et plus important encore, à la manière de prendre en compte le nombre dynamique de graphiques et d'EA pour chaque situation dans le cadre des possibilités autorisées par le langage MQL5. La seule façon de résoudre ce problème est d'utiliser desvariables d'entrée de type ’string’ (chaîne de caractères). Une chaîne de caractères permet de stocker une très grande quantité de données. En fait, pour décrire tous les paramètres nécessaires au modèle, nous aurons besoin de tableaux dynamiques dans les données d'entrée. Bien entendu, personne ne mettra en œuvre de telles mesures, tout simplement parce que peu de personnes utiliseraient ces possibilités. La chaîne est notre tableau dynamique, dans lequel nous pouvons mettre tout ce que nous voulons. Donc utilisons-la. Pour mon modèle le plus simple, j'ai décidé d'introduire 3 variables comme suit :

  • Graphiques - nos graphiques (liste)
  • Chart Lots - lots pour le trading (liste)
  • Chart Timeframes - périodes des graphiques (liste)

En général, nous pouvons combiner toutes ces données en une seule chaîne, mais sa structure sera alors complexe et il sera difficile pour un utilisateur potentiel de comprendre comment décrire correctement les données. Il sera également très facile de faire des erreurs en la remplissant. Et nous pouvons obtenir beaucoup de choses très désagréables en l'utilisant, sans parler de l'incroyable complexité de la fonction de conversion, qui sortira ces données des chaînes. J'ai vu des solutions similaires chez les vendeurs et, en général, ils ont fait tout ce qu'il fallait. Toutes les données sont simplement énumérées et séparées par des virgules. Au début de l'EA, ces donnéessont extraites d'une chaîne à l'aide de fonctions spéciales et insérées dans les tableaux dynamiques correspondants, qui sont ensuite utilisés dans le code. Nous suivrons également cette voie. Nous pouvons ajouter d'autres chaînes similaires avec des règles d'énumération identiques. J'ai décidé d'utiliser le caractère ":" comme séparateur. Si nous utilisons une virgule, il n'est pas évident de traiter les tableaux doubles tels que Chart Lots. Il est possible d'ajouter plus de variables de ce type et, d'une manière générale, de construire un modèle encore plus complet et polyvalent. Mais ma tâche ici consiste uniquement à montrer comment mettre cela en œuvre et à vous donner la première version du modèle que vous pouvez modifier rapidement et facilement.

Il ne suffit pas d'implémenter des tableaux de ce genre. Il est également nécessaire d'implémenter des variables communes, par exemple :

  • Work Timeframe For Unsigned - la période du graphique si elle n’est pas spécifiée
  • Fix Lot For Unsigned - la taille du lot s’il n'est pas spécifié

La liste Charts doit être remplie. La même action est optionnelle pour Chart Lots et Chart Timeframes. Par exemple, nous pouvons prendre des lots uniques pour tous les graphiques et la même période pour tous les graphiques. Une fonctionnalité similaire sera mise en œuvre dans notre modèle. Il est souhaitable d'appliquer ces règles de mise en œuvre dans la mesure du possible afin de garantir la concision et la clarté lors de la définition des paramètres d'entrée d'un EA construit sur la base de ces modèles. 

Définissons maintenant quelques autres variables importantes pour une mise en œuvre minimale d'un tel modèle :

  • Last Bars Count - le nombre de dernières barres d'un graphique que nous stockons pour chaque graphique.
  • Deposit For Lot - dépôt pour l'utilisation d'un lot spécifié
  • First Magic - identifiant unique pour les transactions d'un EA

Je pense que la première variable est assez claire. La seconde variable est beaucoup plus difficile à appréhender. C'est ainsi que je règle le lot automatique dans mes EA. Si je la fixe à "0", j'informe l'algorithme qu'il ne doit négocier qu'un lot fixe spécifié dans la chaîne correspondante ou dans une variable partagée que nous avons examinée plus haut. Sinon, je fixe le dépôt requis pour que le lot spécifié dans les paramètres puisse être appliqué. Il est facile de comprendre qu'avec un dépôt plus ou moins important, ce lot change de valeur en fonction de l'équation :

  • Lot = Lot d'entrée * ( Dépôt actuel / Dépôt pour le lot )

Je pense que tout devrait être clair maintenant. Si nous voulons un lot fixe, nous mettons zéro. Dans d'autres cas, nous ajustons le dépôt dans les paramètres d'entrée en fonction des risques. Je pense que c'est bon marché et puissant. Si nécessaire, vous pouvez modifier l'approche de l'évaluation des risques pour le lot automatique, mais j'aime personnellement cette option, il est inutile de trop réfléchir.

Il convient de parler de la synchronisation, et en particulier de la question de la définition du Nombre Magique des Experts. Lorsqu'il s'agit de trader avec des EA ou même sous une forme mixte, tous les programmeurs qui se respectent accordent une attention particulière à cette variable. En effet, lorsque l'on utilise plusieurs EA, il est très important de s'assurer que chacun d'entre eux possède un identifiant unique. Sinon, lorsque vous travaillez avec des ordres, des transactions ou des positions, vous obtiendrez un désordre complet et vos stratégies ne fonctionneront plus correctement, et dans la plupart des cas, elles cesseront complètement de fonctionner. J'espère ne pas avoir à expliquer pourquoi. Chaque fois qu'un EA est placé sur le graphique, nous devons configurer ces ID et nous assurer qu'ils ne se répètent pas. Une seule erreur peut avoir des conséquences désastreuses. Si vous fermez accidentellement le graphique avec l'EA, vous devrez de plus le reconfigurer à nouveau. La probabilité d'une erreur est donc fortement augmentée. Et elle est très désagréable à bien d'autres égards. Par exemple, vous fermez le graphique et vous oubliez l'identifiant utilisé. Dans ce cas, vous devrez fouiller dans l'historique des transactions pour le trouver. Sans l'identifiant, l'EA nouvellement redémarré peut fonctionner de manière incorrecte et bien d'autres choses désagréables peuvent se produire.

L'utilisation d'un modèle comme le mien nous libère de ce contrôle et minimise les erreurs possibles puisque nous ne devons définir que l'ID de départ dans les paramètres de l'EA, les autres ID seront automatiquement générés à l'aide d'un incrément et attribués aux copies correspondantes des EA. Ce processus se déroulera automatiquement à chaque redémarrage. Quoi qu'il en soit, il est beaucoup plus facile de se souvenir d'un seul identifiant de départ que d'un identifiant aléatoire en cours de route.


Rédaction d'un modèle universel

Il est temps de mettre en œuvre le modèle. J'essaierai d'omettre les éléments excessifs, afin que tous ceux qui ont besoin de ce modèle puissent le télécharger et voir le reste dans le code source. Ici, je ne montrerai que les choses qui sont directement liées à nos idées. Les niveaux d'arrêt et les autres paramètres sont définis par les utilisateurs. Vous trouverez mon implémentation dans le code source. Tout d'abord, définissons nos variables d'entrée, dont nous aurons certainement besoin :

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts
input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots
input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes
input int LastBars=10;//Last Bars Count
input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned
input double RepurchaseLotE=0.01;//Fix Lot For Unsigned
input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix)
input int MagicE=156;//First Magic

Vous pouvez voir ici un exemple de remplissage de variables de type chaîne de caractères reflétant nos tableaux dynamiques, tout comme l'exemple des variables partagées. Ce code sera d’ailleurs le même pour MQL4 et pour MQL5. J'ai essayé de faire en sorte que tout soit aussi similaire que possible.

Décidons maintenant de la manière dont nous obtiendrons les données de notre chaîne. Cette opération sera effectuée par la fonction correspondante, mais nous allons d'abord créer des tableaux dans lesquels notre fonction ajoutera les données obtenues à partir des chaînes de caractères :

//+------------------------------------------------------------------+
//|Arrays                                                            |
//+------------------------------------------------------------------+
string S[];// Symbols array
double L[];//Lots array
ENUM_TIMEFRAMES T[];//Timeframes array

La fonction suivante remplit ces tableaux :

//+------------------------------------------------------------------+
//| Fill arrays                                                      |
//+------------------------------------------------------------------+
void ConstructArrays()
   {
      int SCount=1;
      for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools
         {
         if (SymbolsE[i] == ':')
            {
            SCount++;
            }
         }
      ArrayResize(S,SCount);//set the size of the character array
      ArrayResize(CN,SCount);//set the size of the array to use bars for each character
      int Hc=0;//found instrument index
      for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools
         {
         if (i == 0)//if we just started
            {
            int LastIndex=-1;
            for (int j = i; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i,LastIndex);
               Hc++;
               }
            else
               {
               S[Hc]=SymbolsE;
               Hc++;
               }
            }          
         if (SymbolsE[i] == ':')
            {
            int LastIndex=-1;
            for (int j = i+1; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1));
               Hc++;
               }
            else
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1));
               Hc++;
               }               
            }
         }
      for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars
         {
         CN[i]=LastBars;
         }
      ConstructLots();
      ConstructTimeframe();         
   }

En résumé, ici la quantité de données dans une chaîne est calculée grâce aux séparateurs. Basé sur le premier tableau, la taille de tous les autres tableaux est définie de la même manière qu'un tableau avec des symboles. Les symboles sont ensuite remplis en premier, suivis par des fonctions telles que Construct Lots et ConstructTimeframe. Leur implémentation est similaire à celle de cette fonction, à quelques différences près. Vous pouvez voir leur implémentation dans le code source. Je ne les ai pas ajoutés à l'article afin de ne pas afficher le code en double.

Nous devons maintenant créer les classes correspondante pour le graphique virtuel et pour le robot virtuel qui lui est lié. Commençons par définir que les graphiques virtuels et les EA seront stockés dans des tableaux :

//+------------------------------------------------------------------+
//| Charts & experts pointers                                        |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];

Commençons par la classe des graphiques :

//+------------------------------------------------------------------+
//| Chart class                                                      |
//+------------------------------------------------------------------+
class Chart
   {
   public:
   datetime TimeI[];
   double CloseI[];
   double OpenI[];
   double HighI[];
   double LowI[];
   string BasicSymbol;//the base instrument that was extracted from the substring
   double ChartPoint;//point size of the current chart
   double ChartAsk;//Ask
   double ChartBid;//Bid
   datetime tTimeI[];//auxiliary array to control the appearance of a new bar
   static int TCN;//tcn
   string CurrentSymbol;//symbol
   ENUM_TIMEFRAMES Timeframe;//timeframe
   int copied;//how much data is copied
   int lastcopied;//last amount of data copied
   datetime LastCloseTime;//last bar time
   MqlTick LastTick;//last tick fos this instrument
   
   Chart()
      {
      ArrayResize(tTimeI,2);
      }
   
   void ChartTick()//this chart tick
      {
      SymbolInfoTick(CurrentSymbol,LastTick);
      ArraySetAsSeries(tTimeI,false);
      copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI);
      ArraySetAsSeries(tTimeI,true);
      if ( copied == 2 && tTimeI[1] > LastCloseTime )
         {
         ArraySetAsSeries(CloseI,false);                        
         ArraySetAsSeries(OpenI,false);                           
         ArraySetAsSeries(HighI,false);                        
         ArraySetAsSeries(LowI,false);                              
         ArraySetAsSeries(TimeI,false);                                                            
         lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI);
         lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI);   
         lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI);   
         lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI);
         lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI);
         ArraySetAsSeries(CloseI,true);
         ArraySetAsSeries(OpenI,true);
         ArraySetAsSeries(HighI,true);                        
         ArraySetAsSeries(LowI,true);
         ArraySetAsSeries(TimeI,true);         
         LastCloseTime=tTimeI[1];
         }
      ChartBid=LastTick.bid;
      ChartAsk=LastTick.ask;
      ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT);
      }
   };
int Chart::TCN = 0;

La classe ne comporte qu'une seule fonction, qui contrôle la mise à jour des ticks et des barres, ainsi que les champs nécessaires à l'identification de certains paramètres d'un graphique particulier. Certains paramètres sont manquants. Si vous le souhaitez, vous pouvez ajouter les éléments manquants en ajoutant leur mise à jour, par exemple, comme pour la mise à jour de ChartPoint. Les barres sont construites dans le style MQL4. Je trouve toujours très pratique de travailler avec des tableaux prédéterminés dans MQL4. C'est très pratique lorsque vous savez que la barre zéro est la barre actuelle. Quoi qu'il en soit, ce n'est que ma vision des choses. Vous êtes libre de suivre celles qui vous convient.

Nous devons maintenant décrire la classe d'un EA virtuel distinct :

//+------------------------------------------------------------------+
//| Bot instance class                                               |
//+------------------------------------------------------------------+
class BotInstance//expert advisor object
   {
   public:
   CPositionInfo  m_position;// trade position object
   CTrade         m_trade;// trading object   
   ///-------------------this robot settings----------------------
   int MagicF;//Magic
   string CurrentSymbol;//Symbol
   double CurrentLot;//Start Lot
   int chartindex;//Chart Index
   ///------------------------------------------------------------   
      
   
   ///constructor
   BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index
      {
      chartindex=chartindex0;
      MagicF=MagicE+index;
      CurrentSymbol=Charts[chartindex].CurrentSymbol;
      CurrentLot=L[index];
      m_trade.SetExpertMagicNumber(MagicF);
      }
   ///
   
   void InstanceTick()//bot tick
      {
      if ( bNewBar() ) Trade();
      }
      
   private:
   datetime Time0;
   bool bNewBar()//new bar
      {
      if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 )
         {
         if (Time0 != 0)
            {
            Time0=Charts[chartindex].TimeI[1];
            return true;
            }
         else
            {
            Time0=Charts[chartindex].TimeI[1];
            return false;
            }
         }
      else return false;
      }
      
   //////************************************Main Logic********************************************************************
   void Trade()//main trade function
      {
      //Close[0]   -->   Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart
      //Open[0]   -->   Charts[chartindex].OpenI[0] -----------------------------------------------------------------------
      //High[0]   -->   Charts[chartindex].HighI[0] -----------------------------------------------------------------------
      //Low[0]   -->   Charts[chartindex].LowI[0] -------------------------------------------------------------------------
      //Time[0]   -->   Charts[chartindex].TimeI[0] -----------------------------------------------------------------------      

      if ( true )
         {
            CloseBuyF();
            //CloseSellF();       
         }
      if ( true )
         {
            BuyF();
            //SellF(); 
         }

      }
      
   double OptimalLot()//optimal lot calculation
      {
      if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE);
      else return CurrentLot;
      }
      
   //here you can add functionality or variables if the trading function turns out to be too complicated
   //////*******************************************************************************************************************
   
   ///trade functions
   int OrdersG()//the number of open positions / orders of this virtual robot
      {
      ulong ticket;
      bool ord;
      int OrdersG=0;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol )
            {
            OrdersG++;
            }
         }
      return OrdersG;
      }
   
   /////////********/////////********//////////***********/////////trade function code block
   void BuyF()//buy market
      {
      double DtA;
      double CorrectedLot;
   
      DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime
      if ( (DtA > 0 || DtA < 0) )
         {
         CorrectedLot=OptimalLot(Charts[chartindex]);
         if ( CorrectedLot > 0.0 )
            {
            //try buy logic
            }            
         }
      }
      
   void SellF()//sell market
      {
      //Same logic
      }

   void CloseSellF()//close sell position
      {
      ulong ticket;
      bool ord;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL 
         && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol )
            {
            //Close Sell logic
            }
         }    
      }
      
   void CloseBuyF()//close buy position
      {
      //same logic 
      }        
      
   bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot
      {
      int MagicT[];
      ArrayResize(MagicT,magiccount);
      for ( int i=0; i<magiccount; i++ )
         {
         MagicT[i]=MagicE+i;
         }
      for ( int i=0; i<ArraySize(MagicT); i++ )
         {
         if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true;
         }
      return false;
      }
   /////////********/////////********//////////***********/////////end trade function code block
   };

J'ai supprimé une partie de la logique répétitive afin de réduire la quantité de code. Il s'agit de la classe dans laquelle l’ensemble de l'algorithme de votre EA doit être implémenté. La principale fonctionnalité présente dans cette classe :

  • Trade() - fonction principale de trading qui est appelée dans le gestionnaire de barres pour le graphique correspondant
  • BuyF() - fonction d’achat au marché
  • SellF() - fonction de vente au marché
  • CloseBuyF() - fonction de fermeture des positions d'achat au marché
  • CloseSellF() - fonction de fermeture des positions de vente au marché

Il s'agit de l'ensemble minimal de fonctions permettant de démontrer le trading par barres. Pour cette démonstration, il suffit d'ouvrir une position et de la clôturer sur la barre suivante. Cela suffit dans le cadre de cet article. Cette classe comporte des fonctionnalités supplémentaires qui devraient permettre de mieux comprendre le fonctionnement du système :

  • OrdersG() - compte les positions ouvertes sur un symbole spécifique lié au graphique
  • OptimalLot() - préparation d'un lot avant de l'envoyer à la fonction de trading (sélection d'un lot fixe ou calcul d'un lot automatique)
  • bOurMagic() - vérification de la conformité des transactions de l'historique avec la liste des transactions autorisées (uniquement pour le tri d'un historique personnalisé)

Ces fonctions peuvent être nécessaires pour mettre en œuvre la logique de trading. Il serait également raisonnable de rappeler le nouveau gestionnaire de barres :

  • InstanceTick() - simulation de tick sur une instance donnée d’un EA
  • bNewBar() - prédicat permettant de vérifier l'apparition d'une nouvelle barre (utilisé dans InstanceTick)

Si le prédicat affiche une nouvelle barre, la fonction Trade est déclenchée. Il s'agit de la fonction dans laquelle la logique principale de trading doit être définie. La connexion avec le graphique correspondant s'effectue à l'aide de la variable chartindex attribuée lors de la création de l'instance. Ainsi, chaque instance d'un EA connaît le graphique sur lequel il doit prendre les cotations. 

Examinons maintenant le processus de création des graphiques virtuels et des EA eux-mêmes. Les graphiques virtuels sont créés en premier :

//+------------------------------------------------------------------+
//| Creation of graph objects                                        |
//+------------------------------------------------------------------+
void CreateCharts()
   {
   bool bAlready;
   int num=0;
   string TempSymbols[];
   string Symbols[];
   ConstructArrays();//array preparation
   int tempcnum=CN[0];
   Chart::TCN=tempcnum;//required number of stored bars for all instruments
   for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph
      {
      Charts[j] = new Chart();
      Charts[j].lastcopied=0;
      ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays
      ArrayResize(Charts[j].OpenI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].HighI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].LowI,tempcnum+2);//-----------------------------------
      ArrayResize(Charts[j].TimeI,tempcnum+2);//----------------------------------
      Charts[j].CurrentSymbol = S[j];//symbol
      Charts[j].Timeframe = T[j];//timeframe
      }
   ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots      
   }

Après avoir créé les graphiques et défini la taille du tableau avec les EA virtuels, nous devons créer les instances des EA eux-mêmes et les connecter avec les graphiques :

//+------------------------------------------------------------------+
//| create and hang all virtual robots on charts                     |
//+------------------------------------------------------------------+
void CreateInstances()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      for (int j = 0; j < ArraySize(Charts); j++)
         {
         if ( Charts[j].CurrentSymbol == S[i] )
            {
            Bots[i] = new BotInstance(i,j);
            break;
            } 
         }
      }
   }

La connexion s'effectue à l'aide de l'index "j" défini dans chaque instance de l'EA virtuel lors de sa création. La variable correspondante présentée ci-dessus y est mise en évidence. Bien sûr, tout cela peut être fait de nombreuses façons et de manière beaucoup plus élégante. Mais je pense que l'essentiel est que l'idée générale soit claire. 

Il ne reste plus qu'à montrer comment les ticks sont simulés sur chaque graphique et EA associés :

//+------------------------------------------------------------------+
//| All bcharts & all bots tick imitation                            |
//+------------------------------------------------------------------+
void AllChartsTick()
   {
   for (int i = 0; i < ArraySize(Charts); i++)
      {
      Charts[i].ChartTick();
      }
   }

void AllBotsTick()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick();
      }
   }

La seule chose que je souhaite noter est que ce modèle a été obtenu en retravaillant mon modèle plus complexe, qui était destiné à des fins beaucoup plus sérieuses,. Il peut donc y avoir des éléments excessifs ici et là. Je pense que vous pouvez facilement les supprimer et rendre le code plus net si vous le souhaitez.

Outre le modèle, il existe une interface simple qui, je pense, peut s'avérer utile, par exemple, pour la rédaction d'une commande en freelance ou à d'autres fins :


J'ai laissé de l'espace libre dans cette interface, il suffira pour trois entrées au cas où vous n'auriez pas assez d'espace. Vous pouvez facilement étendre ou modifier complètement sa structure si nécessaire. Si nous voulons ajouter les trois champs manquants dans cet exemple particulier, nous devons trouver les endroits suivants dans le code :

//+------------------------------------------------------------------+
//| Reserved elements                                                |
//+------------------------------------------------------------------+

   "template-UNSIGNED1",//UNSIGNED1
   "template-UNSIGNED2",//UNSIGNED2
   "template-UNSIGNED3",//UNSIGNED3

   //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1
   //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2
   //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3

   ////////////////////////////
   //TempText="UNSIGNED1 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED2 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED3 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);
   //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText);
   ///////////////////////////
 

Les trois premières entrées attribuent des noms aux nouveaux éléments de l'interface, les trois suivantes sont utilisées lors de la création de l'interface au début de l'EA, et les trois dernières sont utilisées dans la fonction de mise à jour des informations sur l'interface. Il est maintenant temps de tester les performances des deux modèles. Le visualiseur du testeur suffira pour une démonstration visuelle. Je ne montrerai que l'option pour MetaTrader 5, car son visualiseur est bien meilleur. Le résultat des travaux montrera clairement tout ce qui est nécessaire pour confirmer l'efficacité :

vérification à l'aide du visualiseur du testeur de MetaTrader 5


Comme vous pouvez le voir, nous avons téléchargé les 7 graphiques pour les principales paires de devises. Le journal de visualisation montre que le trading est en cours pour tous les symboles listés. Le trading est effectué de manière indépendante, selon les besoins. En d'autres termes, les EAs traitent chacun sur leur propre graphique et n'interagissent pas du tout


Conclusion

Dans cet article, nous avons passé en revue les principales nuances de la construction de modèles universels pour les terminaux MetaTrader 4 et MetaTrader 5, créé un modèle simple mais fonctionnel, analysé les points les plus importants de son fonctionnement et confirmé sa viabilité à l'aide du visualiseur du testeur MetaTrader 5. Je pense qu'il est désormais évident qu'un modèle comme celui-ci n'est pas très compliqué. En général, il est possible de réaliser différentes implémentations de ces modèles. Mais il est évident que ces modèles peuvent être complètement différents tout en restant applicables. L'essentiel est de comprendre les nuances fondamentales de la construction de ces structures. Si nécessaire, vous pouvez retravailler les modèles pour un usage personnel.


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

Fichiers joints |
MultiTemplate.mq4 (93.94 KB)
MultiTemplate.mq5 (91.41 KB)
Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes
Maintenant, peu de développeurs se rappellent de la façon d'écrire une DLL simple et des caractéristiques spéciales des différentes liaisons système. À l'aide de plusieurs exemples, je vais tenter de montrer l'ensemble du processus de création de la DLL simple en 10 minutes, ainsi que de discuter de certains détails techniques de notre implémentation de liaison. Je vais montrer étape par étape le processus de la création de DLL dans Visual Studio avec des exemples d'échange de différents types de variables (nombres, tableaux, chaînes, etc.). En outre, je vais vous expliquer comment protéger votre terminal client des plantages dans les DLL personnalisées.
Développer un Expert Advisor de trading à partir de zéro (partie 31) : Vers l'avenir (IV) Développer un Expert Advisor de trading à partir de zéro (partie 31) : Vers l'avenir (IV)
Nous continuons à supprimer les parties distinctes de notre EA. Ceci est le dernier article de cette série. La dernière chose à enlever est le système de sonorisation. Cela peut être un peu déroutant si vous n'avez pas suivi ces séries d'articles.
L'Histogramme des prix (Profile du Marché) et son implémentation  en MQL5 L'Histogramme des prix (Profile du Marché) et son implémentation en MQL5
Le Profile du Marché a été élaboré par le brillant penseur Peter Steidlmayer. Il a suggéré l’utilisation de la représentation alternative de l'information sur les mouvements de marché « horizontaux » et « verticaux » qui conduit à un ensemble de modèles complètement différent. Il a assumé qu'il existe une impulsion sous-jacente du marché ou un modèle fondamental appelé cycle d'équilibre et de déséquilibre. Dans cet article, j’examinerai l'Histogramme des Prix - un modèle simplifié de profil de marché, et décrirai son implémentation dans MQL5.
Développer un Expert Advisor à partir de zéro (partie 30) : CHART TRADE en tant qu'indicateur ? Développer un Expert Advisor à partir de zéro (partie 30) : CHART TRADE en tant qu'indicateur ?
Aujourd'hui, nous allons à nouveau utiliser Chart Trade. Mais cette fois-ci, il s'agira d'un indicateur sur le graphique pouvant être présent ou non sur le graphique.