English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Calculs Parallèles dans MetaTrader 5

Calculs Parallèles dans MetaTrader 5

MetaTrader 5Exemples | 12 janvier 2022, 14:14
205 0
ds2
ds2


Introduction au parallélisme des processeurs

Presque tous les PC modernes sont capables d'effectuer plusieurs tâches simultanément - en raison de la présence de plusieurs noyaux de processeur. Leur nombre augmente chaque année - 2, 3, 4, 6 noyaux ... Intel a récemment démontréun processeur expérimental à 80 noyaux (oui, ce n'est pas une faute de frappe - quatre-vingt noyaux - malheureusement, cet ordinateur n'apparaîtra pas dans les magasins, car ce processeur a été créé uniquement dans le but d'étudier les capacités potentielles de la technologie).

Pas tous les utilisateurs d'ordinateurs (et même pas tous les programmeurs novices) comprennent comment cela fonctionne. Par conséquent, quelqu'un se posera sûrement la question : pourquoi avons-nous besoin d'un processeur avec autant de noyaux, alors qu'avant même (avec un seul cœur), l'ordinateur pouvait exécuter plusieurs programmes simultanément et qu'ils fonctionnaient tous ? La confiance est, ce n'est pas le cas. Examinons le schéma suivant.

Figure 1.Exécution parallèle d’applications

Figure 1. Exécution parallèle d'applications

Case Asur le diagramme indique ce qui se passe lorsqu'un seul programme est exécuté sur un processeur mono-noyau Le processeur consacre tout son temps à son implémentation, et le programme effectue une certaine quantité de travail au cours du temps T.

Case B- 2 programmes lancés. Mais le processeur est agencé de telle manière que physiquement, à un moment donné, l'un de ses noyaux ne peut exécuter qu'une seule commande, il doit donc constamment basculer entre les deux programmes : il exécutera une partie du premier, puis le deuxième, etc. Cela se produit très rapidement, plusieurs fois par seconde, il semble donc que le processeur exécute les deux programmes simultanément. En réalité cependant, leur exécution prendra deux fois plus de temps que si chaque programme était exécuté séparément sur le processeur.

Case C montre que ce problème est effectivement résolu si le nombre de noyaux dans un processeur correspond au nombre de programmes en cours d'exécution. Chaque programme dispose d’ un noyau séparé, et la vitesse de son exécution augmente, tout comme dans le cas A.

Case Dest une réponse au délire commun de nombreux utilisateurs. Ils pensent que si un programme s'exécute sur un processeur multi-noyau, il est exécuté plusieurs fois plus rapidement. En général, cela ne peut pas être vrai car le processeur n'est pas en mesure de diviser indépendamment le programme en parties distinctes et de les exécuter toutes simultanément.

Par exemple, si le programme demande d'abord un mot de passe, puis que ses vérifications sont effectuées, il serait inadmissible d'effectuer la demande de mot de passe sur un noyau et la vérification sur un autre, en même temps. La vérification ne réussira tout simplement jamais, car le mot de passe, au moment de sa création, n'a pas encore été saisi.

Le processeur ne connaît pas toutes les conceptions que le programmeur a mises en œuvre, ni toute la logique du travail du programme, et par conséquent, il ne peut pas séparer indépendamment le programme parmi les noyaux. Ainsi, si nous exécutons un seul programme dans un système multi-noyau, il n'utilisera qu'un seul noyau et sera exécuté à la même vitesse que s'il était exécuté sur un processeur monocœur.

Case E explique ce qui doit être fait pour que le programme utilise tous ses noyaux et soit exécuté plus rapidement. Puisque le programmeur connaît la logique du programme, il devrait, au cours de son élaboration, marquer d'une manière ou d'une autre les parties du programme qui peuvent être exécutées en même temps. Le programme, lors de son exécution, communiquera cette information au processeur, et le processeur affectera alors le programme au nombre de noyaux requis.


Parallélisme dans MetaTrader

Dans le chapitre précédent, nous avons déterminé ce qu'il faut faire pour utiliser tous les noyaux CPU et accélérer l'exécution des programmes : nous devons en quelque sorte allouer le code parallélisable du programme dans des fils séparés. Dans de nombreux langages de programmation, il existe des classes ou des opérateurs spéciaux pour cela. Mais il n'y a pas d'instrument intégré de ce type dans le langage MQL5. Alors, que pouvons-nous faire?

Il y a deux manières de résoudre ce problème :

1. Utiliser la DLL 2. Utiliser les ressources non linguistiques de MetaTrader
En créant une DLL dans un langage muni d'un outil intégré de parallélisation, nous obtiendrons également la parallélisation dans le MQL5-EA. Selon les informations des développeurs de MetaTrader, l'architecture du terminal client est multi-fil. Ainsi, sous certaines conditions, les données de marché entrantes sont traitées dans des fils séparés. Ainsi, si nous pouvons trouver un moyen de séparer le code de notre programme en un certain nombre d' EA ou d'indicateurs, alors MetaTrader pourra utiliser un certain nombre de noyaux de processeur pour son exécution.


La première méthode que nous ne aborderons pas dans cet article. Il est clair que dans DLL, nous pouvons implémenter tout ce que nous souhaitons. Nous tenterons de trouver une solution, qui n'impliquera que les moyens standards de MetaTrader et qui ne nécessitera l'utilisation d'aucun autre langage que MQL5.

Et donc, davantage sur la deuxième méthode. Nous devrons effectuer une série d'expériences pour savoir exactement comment plusieurs noyaux sont pris en charge dans MetaTrader. Pour le faire, créons un indicateur de test et un EA de test, qui effectueront tout travail en cours, qui charge lourdement le processeur.

J'ai écrit l'indicateuri-flood suivant :

//+------------------------------------------------------------------+
//|                                                      i-flood.mq5 |
//+------------------------------------------------------------------+
#property indicator_chart_window

input string id;
//+------------------------------------------------------------------+
void OnInit()
  {
   Print(id,": OnInit");
  }
//+------------------------------------------------------------------+
int OnCalculate(const int rt,const int pc,const int b,const double &p[])
  {
   Print(id,": OnCalculate Begin");
   
   for (int i=0; i<1e9; i++)
     for (int j=0; j<1e1; j++);
     
   Print(id,": OnCalculate End");
   return(0);   
  }
//+------------------------------------------------------------------+

Et l'EAe-flood analogue à celui-ci :

//+------------------------------------------------------------------+
//|                                                      e-flood.mq5 |
//+------------------------------------------------------------------+
input string id;
//+------------------------------------------------------------------+
void OnInit()
  {
   Print(id,": OnInit");
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   Print(id,": OnTick Begin");
   
   for (int i=0; i<1e9; i++)
     for (int j=0; j<1e1; j++);
     
   Print(id,": OnTick End");
  }
//+------------------------------------------------------------------+

De plus, en ouvrant diverses combinaisons de fenêtres graphiques (un graphique, deux graphiques avec le même symbole, deux graphiques avec des symboles différents), et en y plaçant une ou deux copies de cet indicateur ou EA, on peut suivre comment le terminal utilise les noyaux du CPU.

Ces indicateurs et EA envoient également des messages au journal, et il est intéressant de suivre la séquence de leur apparition. Je ne fournirai pas ces journaux car vous pouvez les générer vous-même, mais dans cet article, nous nous intéressons à savoir combien de noyaux et dans quelles combinaisons de graphiques sont utilisés par le terminal.

Nous pouvons mesurer le nombre de noyaux de travail via le "Task Manager" de Windows :

Figure 2. Noyaux CPU

Figure 2. Noyaux de processeur


Les résultats de toutes les mesures sont rassemblés dans le tableau ci-dessous :


combination
 Le contenu du terminal
l'utilisation du processeur
1
2 indicateurs sur un graphique 1 noyau
2
2 indicateurs sur des cartes différentes, la même paire 1 noyau
3
2 indicateurs sur différents graphiques, différentes paires 2 noyaux
4
2 EA sur le même graphique - cette situation est impossible -
5
2 EA sur des cartes différentes, la même paire 2 noyaux
6
2 EA sur différents graphiques, différentes paires 2 noyaux
7
2 indicateurs sur différentes paires, créés à partir de l'EA 2 noyaux


La 7ème combinaison est un moyen courant de créer un indicateur, utilisé dans de nombreuses stratégies de trading.

La seule particularité est que j'ai créé deux indicateurs sur deux paires de devises différentes, car les combinaisons 1 et 2 indiquent clairement que cela n'a aucun sens de placer les indicateurs sur la même paire. Pour cette combinaison, j'ai utilisé le démarreur EAe-flood-starter , qui a produit deux copies d'i-flood :

//+------------------------------------------------------------------+
//|                                              e-flood-starter.mq5 |
//+------------------------------------------------------------------+
void OnInit() 
  {
   string s="EURUSD";
   for(int i=1; i<=2; i++) 
     {
      Print("Indicator is created, handle=",
            iCustom(s,_Period,"i-flood",IntegerToString(i)));
      s="GBPUSD";
     }
  }
//+------------------------------------------------------------------+

Ainsi, tous les calculs de noyaux ont été effectués, et maintenant nous savons pour quelles combinaisons MetaTrader utilise plusieurs noyaux. Ensuite, nous tenterons d'appliquer ces connaissances pour mettre en œuvre les idées de calculs parallèles.


Nous concevons un système parallèle

En ce qui concerne le terminal de trading pour le système parallèle, nous entendons un groupe d'indicateurs ou d' EA (ou un mélange des deux) qui effectuent ensemble une tâche commune, par exemple, effectuer des trades ou dessiner sur le graphique. Ce qui indique que ce groupe fonctionne comme un grand indicateur ou comme un grand EA. Mais en même temps, répartit la charge de calcul sur tous les noyaux de processeur disponibles.

Un tel système se compose de deux types de composants logiciels :

  • CM - module de calcul. Leur nombre peut aller de 2 et jusqu'au nombre de noyaux de processeur. C'est dans le CM que tout le code devant être parallélisé est placé. Comme nous l'avons découvert dans le chapitre précédent, le CM peut être implémenté en tant qu'indicateur, ainsi qu'en tant qu' EA - pour toute forme d'implémentation, il existe une combinaison qui utilise tous les noyaux de processeur;
  • MM - le module principal. Exécute les principales fonctions du système. Donc, si le MM est un indicateur, alors il effectue un dessin sur le graphique, et si le MM est un Ea, alors il exécute les fonctions de trading. Le MM gère également tous les CM.

Par exemple, pour un MM EA et un processeur à 2 noyaux, le schéma de fonctionnement du système ressemblera à ceci :

Figure 3. Schéma du système avec 2 noyaux CPU.

Figure 3. Schéma du système avec 2 noyaux CPU.

Il faut comprendre que le système élaboré par nous n'est pas un programme traditionnel, où vous pouvez simplement appeler la procédure nécessaire à un moment donné. Le MM et le CM sont des EA ou des indicateurs, c'est-à-dire qu'il s'agit de programmes indépendants et autonomes. Il n'y a pas de lien direct entre eux, ils fonctionnent indépendamment, et ne peuvent pas communiquer directement entre eux.

L'exécution de l'un de ces programmes ne commence qu'avec l'apparition dans le terminal d'un quelconqueevent (par exemple, l'arrivée de cotations ou d'un tick de minuterie). Et entre les événements, toutes les données que ces programmes souhaitent se transmettre doivent être stockées quelque part en dehors du, dans un endroit accessible publiquement (appelons-le "Data Exchange Buffer"). Ainsi, le schéma ci-dessus est implémenté dans le terminal de la manière suivante :

Figure 4. Détails d’implémentation

Figure 4. Détails d'implémentation

Pour l’implémentation de ce système, nous devons répondre aux questions suivantes :

  • laquelle des combinaisons de noyaux multiples, trouvées dans le chapitre précédent, allons-nous utiliser dans notre système ?
  • puisque le système se compose de plusieurs EA ou indicateurs, comment pouvons-nous mieux organiser l'échange de données (bilatéral) entre eux (c'est-à-dire, à quoi ressembleront physiquement les données du presse-papiers) ?
  • comment peux-on organiser la coordination et la synchronisation de leurs actions ?

Pour chacune de ces questions, il y a plus d'une réponse, et elles sont toutes fournies ci-dessous. Dans la pratique, les options spécifiques doivent être sélectionnées en fonction de la situation particulière. Nous le ferons dans le prochain chapitre. En attendant, examinons toutes les réponses possibles.

Combination

La Combinaison 7 est la plus idéale pour une utilisation pratique régulière (toutes les autres combinaisons sont répertoriées dans le chapitre précédent), car il n'est pas nécessaire d'ouvrir des fenêtres supplémentaires dans le terminal et d'y placer des EA ou des indicateurs. L'ensemble du système est situé dans une seule fenêtre et tous les indicateurs (CM-1 et CM-2) sont créés automatiquement par l'EA (MM). Le manque de fenêtres supplémentaires et d'actions manuelles a dissipé la confusion pour le trader et, par conséquent, les erreurs liées à de telles erreurs de confusion.

Dans certaines stratégies de trading, d'autres combinaisons peuvent être plus utiles. Par exemple, sur la base de n'importe lequel d'entre eux, nous pouvons créer des systèmes logiciels complets, fonctionnant sur le principe "client-serveur". Où les mêmes CM seront communs à plusieurs MM. Ces CM communs peuvent jouer, non seulement un rôle secondaire d'"ordinateurs", mais être un "serveur" qui stocke quelques informations unifiées pour toutes les stratégies, ou même les coordinateurs de leur travail collectif. Un serveur CM pourrait, par exemple, contrôler de manière centralisée la répartition des moyens dans certains portefeuilles de stratégies et de paires de devises, tout en maintenant le niveau de risque global souhaité.

L'échange de données

Nous pouvons transmettre les informations entre le MM et le CM en utilisant l'une des 3 manières suivantes :

  1. Variables globales du terminal;
  2. fichiers;
  3. Indicateur tampons

La 1ère méthode est optimale lorsqu'il y a un petit nombre de variables numériques en cours de transfert. S'il est nécessaire de transférer des données de texte, elles devront en quelque sorte être codées en nombres, car les variables globales n'ont que le type double.

L'alternative est la 2ème méthode, car tout peut être écrit dans le(s) fichier(s). Et c'est une méthode idéale (et peut-être plus rapide que la 1ère) pour les circonstances dans lesquelles vous devez transférer de grandes quantités de données.

La troisième méthode convient si le MM et le CM sont desindicateurs. Seules les données de type double peuvent être transférées, mais il est plus pratique de transférer de grands tableaux numériques. Mais il y a un inconvénient : lors de la formation d'une nouvelle barre, la numérotation des éléments dans les tampons est décalée. Étant donné que le MM et le CM se trouvent sur des paires de devises différentes, les nouvelles barres n'apparaîtront pas simultanément. Nous devons prendre en compte ces changements.

Synchronisation

Lorsque le terminal reçoit une cotation pour le MM, et qu'il commence à la traiter, il ne peut pas transférer immédiatement le contrôle au CM. Il ne peut que (comme l’indique le schéma ci-dessus) former une tâche (en la plaçant dans les variables globales, un fichier, ou un tampon indicateur), et attendre que le CM soit exécuté. Étant donné que tous les CM sont situés sur des paires de devises différentes, l'attente peut prendre un certain temps. En effet, une paire peut recevoir la cotation , tandis que l'autre ne l'a pas encore reçue, et cela ne viendra que dans quelques secondes ou même quelques minutes (par exemple, cela peut se produire pendant la nuit sur des paires non liquides).

Ainsi, pour que le CM obtienne le contrôle, nous devons pas utiliser les évènements OnTick etOnCalculatequi dépendent des cotations. Au lieu d'eux, nous devons utiliser l'événement OnTimer (innovation de MQL5), qui est exécuté avec une fréquence indiquée (par exemple, 1 seconde). Dans ce cas, les retards dans le système seront fortement réduits.

De plus, au lieu de OnTimer, nous pouvons utiliser la technique du cycle : c'est-à-dire placer un cycle infini pour le CM dans OnInit ou OnCalculate. Chacune de ses itérations est un analogue d'un chronomètre.

Avertissement. J'ai effectué quelques expériences et constaté que lors de l'utilisation de la combinaison 7, l'événement OnTimer ne fonctionne pas dans les indicateurs (pour une raison quelconque), bien que les minuteries aient été créées avec succès.

Vous devez également faire attention aux boucles infinies dans OnInit et OnCalculate : si même un indicateur CM est situé sur la même paire de devises que le MM-EA, alors le prix cesse de bouger sur le graphique, et l'EA cesse de fonctionner (il cesse de générer les événements OnTick). Les développeurs du terminal ont expliqué les raisons de ce comportement.

De la part des développeurs : les scripts et les EA fonctionnent dans leurs propres fils séparés, tandis que tous les indicateurs, sur un seul symbole, fonctionnent dans le même fil. Dans le même flux que les indicateurs, toutes les autres actions sur ce symbole sont également exécutées consécutivement : le traitement des ticks, la synchronisation de l'historique, et le calcul des indicateurs. Ainsi, si l'indicateur effectue une action infinie, tous les autres événements de son symbole ne seront jamais exécutés.

Programme Exécution Note
Script Dans son propre fil, il y a autant de fils d'exécution que de scripts Un script de cyclisme ne peut pas perturber le travail d'autres programmes
Expert Advisor Dans son propre thread, il y a autant de fils d'exécution qu'il y a d'EA Un script de cyclisme ne peut pas perturber le travail d'autres programmes
Indicateur Un fil d'exécution pour tous les indicateurs sur un symbole. Autant de symboles avec indicateurs qu'il y a de fils d'exécution pour eux Un cycle infini dans un indicateur interrompra le travail de tous les autres indicateurs sur ce symbole


Création d'un test Expert Advisor

Choisissons une stratégie de trading, qu'il serait logique de paralléliser, et un algorithme qui conviendrait pour cela.

Par exemple, cela peut être une stratégie simple : compiler la séquence à partir des N dernières barres et trouver la séquence la plus similaire à celle-ci dans l'historique. Sachant où le prix a changé dans l'histoire, nous ouvrons le deal correspondant.

Si la longueur de la séquence est relativement petite, cette stratégie fonctionnera très rapidement dans MetaTrader 5 - en quelques secondes. Mais si on prend une grande longueur - par exemple, l'ensemble des barres de l'intervalle de temps M1 pour les dernières 24 heures (qui seront 1440 barres), - et si on remonte dans l'historique il y a un an (environ 375 000 bars), cela demandera un temps considérable. Et pourtant, cette recherche peut être facilement parallélisée : il suffit de diviser l'historique en parties égales sur le nombre de noyaux de processeur disponibles, et d'affecter à chaque cœur de rechercher un emplacement précis.

Les paramètres du système parallèle seront les suivants :

  • MM - est l'EA qui implémente la stratégie de trading modelée ;
  • le calcul parallèle est effectué dans les indicateurs CM, générés automatiquement à partir de l'Ea (c'est-à-dire en utilisant la combinaison 7);
  • le code de calcul dans les indicateurs CM est placé à l'intérieur d'un cycle infini dans l'OnInit ;
  • échange de données entre les indicateurs MM-EA et CM - effectué via les variables globales du terminal.

Pour la commodité de l’élaboration et de l'utilisation ultérieure, nous allons créer l 'EA de manière à ce qu'en fonction des paramètres, il puisse fonctionner en parallèle (avec des calculs dans les indicateurs), et comme d'habitude (c'est-à-dire sans l'utilisation de indicateurs) EA. Le code de l'Expert Advisore-MultiThreadobtenu :

//+------------------------------------------------------------------+
//|                                                e-MultiThread.mq5 |
//+------------------------------------------------------------------+
input int Threads=1; // How many cores should be used
input int MagicNumber=0;

// Strategy parameters
input int PatternLen  = 1440;   // The length of the sequence to analyze (pattern)
input int PrognozeLen = 60;     // Forecast length (bars)
input int HistoryLen  = 375000; // History length to search

input double Lots=0.1;
//+------------------------------------------------------------------+
class IndData
  {
public:
   int               ts,te;
   datetime          start_time;
   double            prognoze,rating;
  };

IndData Calc[];
double CurPattern[];
double Prognoze;
int  HistPatternBarStart;
int  ExistsPrognozeLen;
uint TicksStart,TicksEnd;
//+------------------------------------------------------------------+
#include <ThreadCalc.mqh>
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
int OnInit()
  {

   double rates[];

//--- Make sure there is enough history
   int HistNeed=HistoryLen+Threads+PatternLen+PatternLen+PrognozeLen-1;
   if(TerminalInfoInteger(TERMINAL_MAXBARS)<HistNeed)
     {    
      Print("Change the terminal setting \"Max. bars in chart\" to the value, not lesser than ",
            HistNeed," and restart the terminal");
      return(1);      
     }
   while(Bars(_Symbol,_Period)<HistNeed)
     {      
      Print("Insufficient history length (",Bars(_Symbol,_Period),") in the terminal, upload...");
      CopyClose(_Symbol,_Period,0,HistNeed,rates);
     }
   Print("History length in the terminal: ",Bars(_Symbol,_Period));

//--- For a multi-core mode create computational indicators
   if(Threads>1)
     {
      GlobalVarPrefix="MultiThread_"+IntegerToString(MagicNumber)+"_";
      GlobalVariablesDeleteAll(GlobalVarPrefix);

      ArrayResize(Calc,Threads);

      // Length of history for each core
      int HistPartLen=MathCeil(HistoryLen/Threads);
      // Including the boundary sequences
      int HistPartLenPlus=HistPartLen+PatternLen+PrognozeLen-1;

      string s;
      int snum=0;
      // Create all computational indicators
      for(int t=0; t<Threads; t++)
        {      
         // For each indicator - its own currency pair,
         // it should not be the same as for the EA
         do
            s=SymbolName(snum++,false);
         while(s==_Symbol);

         int handle=iCustom(s,_Period,"i-Thread",
                            GlobalVarPrefix,t,_Symbol,PatternLen,
                            PatternLen+t*HistPartLen,HistPartLenPlus);

         if(handle==INVALID_HANDLE) return(1);
         Print("Indicator created, pair ",s,", handle ",handle);
        }
     }

   return(0);
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   TicksStart=GetTickCount();

   // Fill in the sequence with the last bars
   while(CopyClose(_Symbol,_Period,0,PatternLen,CurPattern)<PatternLen) Sleep(1000);

   // If there is an open position, measure its "age"
   // and modify the forecast range for the remaining 
   // planned life time of the deal
   CalcPrognozeLen();

   // Find the most similar sequence in the history
   // and the forecast of the movement of its price on its basis
   FindHistoryPrognoze();

   // Perform the necessary trade actions
   Trade();

   TicksEnd=GetTickCount();
   // Debugging information in
   PrintReport();
  }
//+------------------------------------------------------------------+
void FindHistoryPrognoze()
  {
   Prognoze=0;
   double MaxRating;

   if(Threads>1)
     {
      //--------------------------------------
      // USE COMPUTATIONAL INDICATORS
      //--------------------------------------
      // Look through all of the computational indicators 
      for(int t=0; t<Threads; t++)
        {
         // Send the parameters of the computational task
         SetParam(t,"PrognozeLen",ExistsPrognozeLen);
         // "Begin computations" signal 
         SetParam(t,"Query");
        }

      for(int t=0; t<Threads; t++)
        {
         // Wait for results
         while(!ParamExists(t,"Answer"))
            Sleep(100);
         DelParam(t,"Answer");

         // Obtain results
         double progn        = GetParam(t, "Prognoze");
         double rating       = GetParam(t, "Rating");
         datetime time[];
         int start=GetParam(t,"PatternStart");
         CopyTime(_Symbol,_Period,start,1,time);
         Calc [t].prognoze   = progn;
         Calc [t].rating     = rating;
         Calc [t].start_time = time[0];
         Calc [t].ts         = GetParam(t, "TS");
         Calc [t].te         = GetParam(t, "TE");

         // Select the best result
         if((t==0) || (rating>MaxRating))
           {
            MaxRating = rating;
            Prognoze  = progn;
           }
        }
     }
   else
     {
      //----------------------------
      // INDICATORS ARE NOT USED
      //----------------------------
      // Calculate everything in the EA, into one stream
      FindPrognoze(_Symbol,CurPattern,0,HistoryLen,ExistsPrognozeLen,
                   Prognoze,MaxRating,HistPatternBarStart);
     }
  }
//+------------------------------------------------------------------+
void CalcPrognozeLen()
  {
   ExistsPrognozeLen=PrognozeLen;

   // If there is an opened position, determine 
   // how many bars have passed since its opening
   if(PositionSelect(_Symbol))
     {
      datetime postime=PositionGetInteger(POSITION_TIME);
      datetime curtime,time[];
      CopyTime(_Symbol,_Period,0,1,time);
      curtime=time[0];
      CopyTime(_Symbol,_Period,curtime,postime,time);
      int poslen=ArraySize(time);
      if(poslen<PrognozeLen)
         ExistsPrognozeLen=PrognozeLen-poslen;
      else
         ExistsPrognozeLen=0;
     }
  }
//+------------------------------------------------------------------+
void Trade()
  {

   // Close the open position, if it is against the forecast
   if(PositionSelect(_Symbol))
     {
      long type=PositionGetInteger(POSITION_TYPE);
      bool close=false;
      if((type == POSITION_TYPE_BUY)  && (Prognoze <= 0)) close = true;
      if((type == POSITION_TYPE_SELL) && (Prognoze >= 0)) close = true;
      if(close)
        {
         CTrade trade;
         trade.PositionClose(_Symbol);
        }
     }

   // If there are no position, open one according to the forecast
   if((Prognoze!=0) && (!PositionSelect(_Symbol)))
     {
      CTrade trade;
      if(Prognoze > 0) trade.Buy (Lots);
      if(Prognoze < 0) trade.Sell(Lots);
     }
  }
//+------------------------------------------------------------------+
void PrintReport()
  {
   Print("------------");
   Print("EA: started ",TicksStart,
         ", finished ",TicksEnd,
         ", duration (ms) ",TicksEnd-TicksStart);
   Print("EA: Forecast on ",ExistsPrognozeLen," bars");

   if(Threads>1)
     {
      for(int t=0; t<Threads; t++)
        {
         Print("Indicator ",t+1,
               ": Forecast ", Calc[t].prognoze,
               ", Rating ", Calc[t].rating,
               ", sequence from ",TimeToString(Calc[t].start_time)," in the past");
         Print("Indicator ",t+1,
               ": started ",  Calc[t].ts,
               ", finished ",   Calc[t].te,
               ", duration (ms) ",Calc[t].te-Calc[t].ts);
        }
     }
   else
     {
      Print("Indicators were not used");
      datetime time[];
      CopyTime(_Symbol,_Period,HistPatternBarStart,1,time);
      Print("EA: sequence from ",TimeToString(time[0])," in the past");
     }

   Print("EA: Forecast ",Prognoze);
   Print("------------");
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Send the "finish" command to the indicators
   if(Threads>1)
      for(int t=0; t<Threads; t++)
         SetParam(t,"End");
  }
//+------------------------------------------------------------------+

Le code de l'indicateur de calcul,i-Thread utilisé par l'Expert Advisor :

//+------------------------------------------------------------------+
//|                                                     i-Thread.mq5 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

//--- input parameters
input string VarPrefix;  // Prefix for global variables (analog to MagicNumber)
input int    ThreadNum;  // Core number (so that indicators on different cores could
                        // differentiate their tasks from the tasks of the "neighboring" cores)
input string DataSymbol; // On what pair is the MM-EA working
input int    PatternLen; // Length of the sequence for analysis
input int    BarStart;   // From which bar in the history the search for a similar sequence began
input int    BarCount;   // How many bars of the history to perform a search on

//--- indicator buffers
double Buffer[];
//---
double CurPattern[];

//+------------------------------------------------------------------+
#include <ThreadCalc.mqh>
//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0,Buffer,INDICATOR_DATA);

   GlobalVarPrefix=VarPrefix;

   // Infinite loop - so that the indicator always "listening", 
   // for new commands from the EA
   while(true)
     {
      // Finish the work of the indicator, if there is a command to finish
      if(ParamExists(ThreadNum,"End"))
         break;

      // Wait for the signal to begin calculations
      if(!ParamExists(ThreadNum,"Query"))
        {
         Sleep(100);
         continue;
        }
      DelParam(ThreadNum,"Query");

      uint TicksStart=GetTickCount();

      // Obtain the parameters of the task
      int PrognozeLen=GetParam(ThreadNum,"PrognozeLen");

      // Fill the sequence from the last bars
      while(CopyClose(DataSymbol,_Period,0,PatternLen,CurPattern)
            <PatternLen) Sleep(1000);

      // Perform calculations
      int HistPatternBarStart;
      double Prognoze,Rating;
      FindPrognoze(DataSymbol,CurPattern,BarStart,BarCount,PrognozeLen,
                   Prognoze,Rating,HistPatternBarStart);

      // Send the results of calculations
      SetParam(ThreadNum,"Prognoze",Prognoze);
      SetParam(ThreadNum,"Rating",Rating);
      SetParam(ThreadNum,"PatternStart",HistPatternBarStart);
      SetParam(ThreadNum,"TS",TicksStart);
      SetParam(ThreadNum,"TE",GetTickCount());
      // Signal "everything is ready"
      SetParam(ThreadNum,"Answer");
     }
  }
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   // The handler of this event is required
   return(0);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   SetParam(ThreadNum,"End");
  }
//+------------------------------------------------------------------+

L'Expert Advisor et l'indicateur utilisent une bibliothèqueThreadCalc.mqh commune.

Voici son code :

//+------------------------------------------------------------------+
//|                                                   ThreadCalc.mqh |
//+------------------------------------------------------------------+
string GlobalVarPrefix;
//+------------------------------------------------------------------+
// It finds the price sequence, most similar to the assigned one.
// in the specified range of the history
// Returns the estimation of similarity and the direction 
// of the further changes of prices in history.
//+------------------------------------------------------------------+
void FindPrognoze(
                  string DataSymbol,    // symbol
                  double  &CurPattern[],// current pattern
                  int BarStart,         // start bar
                  int BarCount,         // bars to search
                  int PrognozeLen,      // forecast length

                  // RESULT
                  double  &Prognoze,        // forecast (-,0,+)
                  double  &Rating,          // rating
                  int  &HistPatternBarStart // starting bar of the found sequence
                  ) 
  {

   int PatternLen=ArraySize(CurPattern);

   Prognoze=0;
   if(PrognozeLen<=0) return;

   double rates[];
   while(CopyClose(DataSymbol,_Period,BarStart,BarCount,rates)
         <BarCount) Sleep(1000);

   double rmin=-1;
   // Shifting by one bar, go through all of the price sequences in the history
   for(int bar=BarCount-PatternLen-PrognozeLen; bar>=0; bar--) 
     {
      // Update to eliminate the differences in the levels of price in the sequences
      double dr=CurPattern[0]-rates[bar];

      // Calculate the level of differences between the sequences - as a sum 
      // of squares of price deviations from the sample values
      double r=0;
      for(int i=0; i<PatternLen; i++)
         r+=MathPow(MathAbs(rates[bar+i]+dr-CurPattern[i]),2);

      // Find the sequence with the least difference level
      if((r<rmin) || (rmin<0)) 
        {
         rmin=r;
         HistPatternBarStart   = bar;
         int HistPatternBarEnd = bar + PatternLen-1;
         Prognoze=rates[HistPatternBarEnd+PrognozeLen]-rates[HistPatternBarEnd];
        }
     }
   // Convert the bar number into an indicator system of coordinates
   HistPatternBarStart=BarStart+BarCount-HistPatternBarStart-PatternLen;

   // Convert the difference into the rating of similarity
   Rating=-rmin;
  }
//====================================================================
// A set of functions for easing the work with global variables.
// As a parameter contain the number of computational threads 
// and the names of the variables, automatically converted into unique
// global names.
//====================================================================
//+------------------------------------------------------------------+
string GlobalParamName(int ThreadNum,string ParamName) 
  {
   return GlobalVarPrefix+IntegerToString(ThreadNum)+"_"+ParamName;
  }
//+------------------------------------------------------------------+
bool ParamExists(int ThreadNum,string ParamName) 
  {
   return GlobalVariableCheck(GlobalParamName(ThreadNum,ParamName));
  }
//+------------------------------------------------------------------+
void SetParam(int ThreadNum,string ParamName,double ParamValue=0) 
  {
   string VarName=GlobalParamName(ThreadNum,ParamName);
   GlobalVariableTemp(VarName);
   GlobalVariableSet(VarName,ParamValue);
  }
//+------------------------------------------------------------------+
double GetParam(int ThreadNum,string ParamName) 
  {
   return GlobalVariableGet(GlobalParamName(ThreadNum,ParamName));
  }
//+------------------------------------------------------------------+
double DelParam(int ThreadNum,string ParamName) 
  {
   return GlobalVariableDel(GlobalParamName(ThreadNum,ParamName));
  }
//+------------------------------------------------------------------+

Notre système de trading, qui est en mesure d'utiliser plus d'un noyau de processeur dans son travail, est prêt !

Lorsque vous l'utilisez, vous devez vous rappeler que dans cet exemple, nous avons utilisé des indicateurs CM avec des boucles infinies.

Si vous envisagez d'exécuter d'autres programmes, avec ce système, dans le terminal, vous devez donc vous assurer que vous les utilisez sur les paires de devises qui ne sont pas utilisées par les indicateurs CM. Un bon moyen d'éviter un tel conflit est de modifier le système de sorte que dans les paramètres d'entrée du MM-EA, vous puissiez indiquer directement les paires de devises pour les indicateurs CM.


Mesurer la vitesse de travail de l' AE

Mode régulier

Ouvrez le graphique EURUSD M1 et lancez notre Expert Advisor, créé dans le chapitre précédent. Dans les paramètres, indiquez la durée des modèles sur 24 heures (1440 minutes barres) et la profondeur de la recherche dans l'historique - sur 1 an (375 000 barres).

Figure 4. Paramètres d’entrée d’Expert Advisor

Figure 4. Paramètres d'entrée de l'Expert Advisor

Paramètre "Fils" défini égal à 1. Cela indique que tous les calculs de l'EA seront établis dans un seul fil (sur un seul noyau). Pendant ce temps, il n'utilisera pas d'indicateurs de calcul, mais calculera tout lui-même. Fondamentalement, par le principe de travail d'une EA régulière.

Journal de son exécution :

Figure 6. Journal de l’Expert Advisor

Figure 6. Journal de l'Expert Advisor (1 fil)


Mode parallèle

Supprimons maintenant cette EA évaluation environnementale et la position qu'elle a ouverte. Ajoutez à nouveau l'EA, mais cette fois, avec le paramètre "Threads" égal à 2.

Désormais, l'EA devra créer et utiliser dans son travail 2 indicateurs de calcul, occupant deux noyaux de processeur. Journal de son exécution :

Figure 7. Journal d’Expert Advisor (2 fils)

Figure 7. Journal de l'Expert Advisor (2 fils)


Comparaison de vitesse

En analysant ces deux journaux, nous concluons que le moment approximatif d'exécution de l' EA est :

  • en mode normal - 52 secondes;
  • en mode 2 cœurs - 27 secondes.

Ainsi, en effectuant la parallélisation sur un processeur à 2 noyaux, nous étions en mesure d’ augmenter la vitesse d'un EA by 1.9 fois On peut supposer que lors de l'utilisation d'un processeur avec unegrosse quantité de noyaux, la vitesse d'exécution augmentera encore plus, proportionnellement au nombre de noyaux.

Contrôle de la justesse du travail

En plus du temps d'exécution, les journaux fournissent des informations supplémentaires, qui nous permettent de vérifier que toutes les mesures ont été effectuées correctement. Les lignes EA : Début des travaux ... fin des travaux ... " et " Indicateur ... : Commencer le travail... se terminer le travail..." indiquent que les indicateurs ont commencé leurs calculs une seconde avant que l' EA ne leur donne cet ordre.

Vérifions également qu'il n'y a pas de violation de la stratégie de trading lors du lancement de l'EA en mode parallèle. Selon les journaux, il est clair que le lancement de l'EA en mode parallèle a été effectué presque immédiatement après son lancement en mode régulier. Cela indique que les états du marché, dans les deux cas, étaient similaires. Les journaux indiquent que les dates, trouvées dans l'historique des modèles, dans les deux cas étaient également très similaires. Donc tout est bon : l'algorithme de la stratégie fonctionne aussi bien dans les deux cas.

Voici les modèles, décrits dans les situations de journaux. Tel était l’état actuel du marché (durée - barres de 1440 minutes) au moment de l'exécution de l'EA en mode normal :

Figure 8. État actuel du marché

Figure 8. État actuel du marché

L'EA a trouvé dans l'historique le modèle similaire suivant :

Figure 9. État de marché similaire

Figure 9. État de marché similaire

Lors de l'exécution de l'EA en mode parallèle, le même modèle a été trouvé par "l'indicateur 1". "L'indicateur 2", comme suit du journal, recherchait des modèles dans l'autre semestre de l'historique, il a donc trouvé un modèle similaire différent :

Figure 10. État de marché similaire

Figure 10. État de marché similaire

Et voici à quoi ressemblent lesglobal variables dans MetaTrader 5 pendant le travail de l'EA en mode parallèle :

Figure 11. Variables globales

Figure 11. Variables Globales

L'échange de données entre l'EA et les indicateurs à travers desglobal variables a été mis en œuvre avec succès.


Conclusion

Dans cette étude, nous avons constaté qu'il est possible de paralléliser des algorithmes ingénieux par les moyens standards de MetaTrader 5. Et la solution découverte à ce problème convient à une utilisation pratique dans les stratégies de trading du monde réel.

Ce programme, dans un système multi-noyau, fonctionne vraiment de manière proportionnellement plus rapide. Le nombre de noyaux dans un processeur augmente chaque année, et il est bon que les traders, qui utilisent MetaTrader, aient la possibilité d'utiliser efficacement ces ressources matérielles. Nous pouvons créer en toute sécurité des stratégies de trading plus ingénieuses, qui seront toujours en mesure d'analyser le marché en temps réel.


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

Fichiers joints |
i-flood.mq5 (0.7 KB)
e-flood.mq5 (0.58 KB)
i-thread.mq5 (3.1 KB)
threadcalc.mqh (3.93 KB)
Canaux de traçage - Schéma intérieure et extérieure Canaux de traçage - Schéma intérieure et extérieure
Je suppose que ce ne sera pas une exagération, si je dis que les canaux sont l'outil le plus populaire pour l'analyse du marché et la prise de décisions en trade après les moyennes mobiles. Sans plonger profondément dans la masse des stratégies de trade qui utilisent des canaux et leurs composants, nous allons discuter de la base mathématique et de l’implémentation pratique d'un indicateur, qui trace un canal déterminé par trois extremums sur l'écran du terminal client.
Concevoir et implémenter de nouveaux widgets GUI axés sur la classe CChartObject Concevoir et implémenter de nouveaux widgets GUI axés sur la classe CChartObject
Après avoir écrit un article précédent sur l'Expert Advisor semi-automatique avec interface graphique, il s'est avéré qu'il serait souhaitable d'améliorer l'interface avec de nouvelles fonctionnalités pour des indicateurs et des Expert Advisors plus complexes. Après m'être familiarisé avec les classes de bibliothèque standard MQL5, j'ai implémenté de nouveaux widgets. Cet article décrit un processus de conception et d’implémentation de nouveaux widgets d'interface graphique MQL5 pouvant être utilisés dans des indicateurs et des Expert Advisors. Les widgets présentés dans l'article sont CChartObjectSpinner, CChartObjectProgressBar et CChartObjectEditTable.
Création d’Expert Advisors Multiples sur la base de Modèles de Trading Création d’Expert Advisors Multiples sur la base de Modèles de Trading
L'utilisation de l'approche orientée-objet dans MQL5 simplifie considérablement la création d'Expert Advisors multidevises/multisystèmes/multi-périodes. Imaginez seulement, votre seul EA trade sur plusieurs dizaines de stratégies de trading, sur tous les instruments disponibles et sur tous les intervalles de temps possibles ! De plus, l' EA est facilement testé dans le testeur, et pour toutes les stratégies, comprises dans sa composition, il dispose d'un ou plusieurs systèmes fonctionnels de gestion de l'argent.
Création d'un Expert Advisor semi-automatique interactif par glisser-déposer axé sur un risque et un ratio R/R prédéfinis Création d'un Expert Advisor semi-automatique interactif par glisser-déposer axé sur un risque et un ratio R/R prédéfinis
Certains traders exécutent tous leurs trades automatiquement, et certains mélangent des trades automatiques et manuels en fonction de la sortie de plusieurs indicateurs. En tant que membre de ce dernier groupe, j'avais besoin d'un outil interactif pour évaluer dynamiquement les niveaux de prix des risques et des rendements directement à partir du graphique. Cet article présentera un moyen d’implémenter un Expert Advisor semi-automatique interactif avec un risque sur actions et un ratio R/R prédéfinis. Les paramètres de risque, de R/R et de taille de lot de l'Expert Advisor peuvent être modifiés pendant l'exécution sur le panneau EA.