English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Canaux Nommés

Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Canaux Nommés

MetaTrader 5Exemples | 17 novembre 2021, 15:42
132 0
investeo
investeo

Introduction

Je me suis interrogé pendant un certain temps sur les moyens de communication possibles entre les terminaux MetaTrader 5. Mon objectif était d'utiliser l'indicateur de tick et d'afficher les ticks de différents fournisseurs de cotations dans l'un des terminaux.

La solution naturelle consistait à utiliser des fichiers séparés sur un disque dur. Un terminal écrirait des données dans le fichier et l'autre les lirait. Cette méthode bien que pertinente pour l'envoi de messages uniques ne semble pas être la plus efficace pour les offres en streaming.

Ensuite, je suis tombé sur un bon article d'Alexander sur la façon d'exporter des devis vers des applications .NET à l'aide des services WCF et quand j'étais sur le point de terminer, un autre article de Sergeev est apparu.

Les deux articles étaient proches de ce dont j'avais besoin mais j'ai cherché une solution sans DLL qui pourrait être utilisée par différents terminaux, l'un servant de serveur et l'autre de client. En cherchant sur le Web, j'ai trouvé une note suggérant que l'on pouvait utiliser des Canaux Nommés pour la communication et j'ai lu attentivement la spécification cahier des charges MSDN pour la communication inter-processus utilisant les canaux.

J'ai découvert que les Canaux Nommés prennent en charge la communication sur le même ordinateur ou sur différents ordinateurs via intranet. J'ai décidé d'opter pour cette approche.

Cet article présente la communication Canaux Nommés et décrit un processus de conception de la classe CNamedPipes. Il comprend également le test du flux d'indicateurs de tick entre les terminaux MetaTrader 5 et le débit global du système.

1. Communication inter-processus à l'aide de Canaux Nommés

Lorsque nous pensons à un canal typique, nous imaginons une sorte de cylindre utilisé pour véhiculer des médias. C'est également un terme utilisé pour l'un des moyens de communication inter-processus sur un système d'exploitation. Vous pourriez simplement imaginer un canal qui relie deux processus, dans notre cas des terminaux MetaTrader 5 qui échangent des données. 

Les canaux peuvent être anonymes ou nommés. Il existe deux différences principales entre eux : la première est que les canaux anonymes ne peuvent pas être utilisés sur un réseau et la seconde que deux processus doivent être liés. C'est-à-dire qu'un processus doit être un parent et l'autre un processus enfant. Les Canaux Nommés n'ont pas cette limitation.

Afin de communiquer à l'aide de canaux, un processus serveur doit configurer un canal avec un nom connu. Le nom du canal est une chaîne et doit être sous la forme \\servername\pipe\pipename. Si des canaux sont utilisées sur le même ordinateur, servername peut être omis et un point peut être placé à la place : \\.\pipe\pipename.

Le client qui essaie de se connecter à un canal doit connaître son nom. J'utilise une convention de nom de \\.\pipe\mt[account_number] afin de distinguer les terminaux, mais la convention de nommage peut être modifiée arbitrairement.

2. Implémentation de la classe CNamedPipes

Je commencerai par une brève description du mécanisme de bas niveau de création et de connexion à un canal nommé. Sur les systèmes d'exploitation Windows, toutes les fonctions qui gèrent les canaux sont disponibles via la bibliothèque kernel32.dll. La fonction instanciant un canal nommé côté serveur estCreateNamedPipe()..

Une fois le canal créé, le serveur appelle ConnectNamedPipe() fonction pour attendre qu'un client se connecte. Si la connexion réussit, ConnectNamedPipe() renvoie un entier différent de zéro. Il est cependant possible que le client se soit connecté avec succès après avoir appelé CreateNamedPipe() et avant ConnectNamedPipe() a été appelé. Dans ce cas ConnectNamedPipe() renvoie zéro et GetLastError() renvoie l'erreur 535 (0X217) : ERROR_PIPE_CONNECTED.

L'écriture et la lecture à partir d'un canal sont réalisées avec les mêmes fonctions que pour l'accès aux fichiers :

BOOL WINAPI ReadFile(
  __in         HANDLE hFile,
  __out        LPVOID lpBuffer,
  __in         DWORD nNumberOfBytesToRead,
  __out_opt    LPDWORD lpNumberOfBytesRead,
  __inout_opt  LPOVERLAPPED lpOverlapped
);
BOOL WINAPI WriteFile(
  __in         HANDLE hFile,
  __in         LPCVOID lpBuffer,
  __in         DWORD nNumberOfBytesToWrite,
  __out_opt    LPDWORD lpNumberOfBytesWritten,
  __inout_opt  LPOVERLAPPED lpOverlapped
);

Ayant appris les canaux nommés, j'ai conçu la classe CNamedPipes afin de masquer les instructions de bas niveau sous-jacentes.

Il suffit maintenant de mettre le fichier CNamedPipes.mqh dans le dossier approprié et (/include) du terminal et de l'inclure dans le code source et de déclarer un objet CNamedPipe.

La classe que j'ai conçue expose quelques méthodes de base pour gérer les Canaux Nommés :

Create(), Connect(), Disconnect(), Open(), Close(), WriteUnicode(), ReadUnicode(), WriteANSI(), ReadANSI(), WriteTick(), ReadTick()

La classe peut être étendue en fonction d'exigences supplémentaires.

La méthode Create() essaie de créer un canal avec un nom donné. Pour simplifier la connexion entre les terminaux, le paramètre d'entrée 'compte' est le numéro de compte d'un client qui utilisera un canal.

Si le nom de compte n'est pas entré, la méthode essaie d'ouvrir un canal avec le numéro de compte d'un terminal actuel. La fonction Create() renvoie true si le canal a été créé avec succès.

//+------------------------------------------------------------------+
/// Create() : try to create a new instance of Named Pipe                 
/// \param account - source terminal account number                  
/// \return true - if created, false otherwise                       
//+------------------------------------------------------------------+
bool CNamedPipe::Create(int account=0)
  {
   if(account==0)
      pipeNumber=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN));
   else
      pipeNumber=IntegerToString(account);

   string fullPipeName=pipeNamePrefix+pipeNumber;

   hPipe=CreateNamedPipeW(fullPipeName,
                          (int)GENERIC_READ|GENERIC_WRITE|(ENUM_PIPE_ACCESS)PIPE_ACCESS_DUPLEX,
                          (ENUM_PIPE_MODE)PIPE_TYPE_RW_BYTE,PIPE_UNLIMITED_INSTANCES,
                          BufferSize*sizeof(ushort),BufferSize*sizeof(ushort),0,NULL);

   if(hPipe==INVALID_HANDLE_VALUE) return false;
   else
      return true;

  }

La méthode Connect() attend qu'un client se connecte à un canal. Il renvoie true si le client s'est connecté avec succès à un canal.

//+------------------------------------------------------------------+
/// Connect() : wait for a client to connect to a pipe   
/// \return true - if connected, false otherwise.
//+------------------------------------------------------------------+
bool CNamedPipe::Connect(void)
  {
   if(ConnectNamedPipe(hPipe,NULL)==false)
      return(kernel32::GetLastError()==ERROR_PIPE_CONNECTED);
   else return true;
  }

La méthode Disconnect() déconnecte le serveur d'un canal.

//+------------------------------------------------------------------+
/// Disconnect(): disconnect from a pipe
/// \return true - if disconnected, false otherwise    
//+------------------------------------------------------------------+
bool CNamedPipe::Disconnect(void)
  {
   return DisconnectNamedPipe(hPipe);
  }

La méthode Open() doit être utilisée par un client, elle essaie d'ouvrir un canal précédemment créé. Il renvoie vrai si l'ouverture du canal a abouti.  Il renvoie false si, pour une raison quelconque, il n'a pas pu se connecter au canal créé dans un délai de 5 secondes ou si l'ouverture du canal a échoué.

//+------------------------------------------------------------------+
/// Open() : try to open previously created pipe
/// \param account - source terminal account number
/// \return true - if successfull, false otherwise.
//+------------------------------------------------------------------+
bool CNamedPipe::Open(int account=0)
  {
   if(account==0)
      pipeName=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN));
   else
      pipeName=IntegerToString(account);

   string fullPipeName=pipeNamePrefix+pipeName;

   if(hPipe==INVALID_HANDLE_VALUE)
     {
      if(WaitNamedPipeW(fullPipeName,5000)==0)
        {
         Print("Pipe "+fullPipeName+" not available...");
         return false;
        }

      hPipe=CreateFileW(fullPipeName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
      if(hPipe==INVALID_HANDLE_VALUE)
        {
         Print("Pipe open failed");
         return false;
        }

     }
   return true;
  }

La méthode Close() ferme la poignée du canal.

//+------------------------------------------------------------------+
/// Close() : close pipe handle
/// \return 0 if successfull, non-zero otherwise  
//+------------------------------------------------------------------+
int CNamedPipe::Close(void)
  {
   return CloseHandle(hPipe);
  }

Les six méthodes suivantes sont utilisées pour lire et écrire dans les canaux. Les deux premières paires de poignées gèrent les chaînes aux formats Unicode et ANSI, les deux peuvent être utilisées pour envoyer des commandes ou des messages entre les terminaux.

La variable de chaîne dans MQL5 est stockée en tant qu'objet qui contient Unicode, donc la manière naturelle était de fournir des méthodes Unicode, mais puisque MQL5 fournit des méthodes UnicodeToANSI, j'ai également implémenté la communication de chaîne ANSI. Les deux dernières méthodes gèrent l'envoi et la réception d'objets MqlTick via un canal nommé. 

La méthode WriteUnicode() rédige le message composé de caractères Unicode. Étant donné que chaque caractère se compose de deux octets, il est envoyé sous forme de tableau de ushort à un canal.

//+------------------------------------------------------------------+
/// WriteUnicode() : write Unicode string to a pipe
/// \param message - string to send
/// \return number of bytes written to a pipe     
//+------------------------------------------------------------------+
int CNamedPipe::WriteUnicode(string message)
  {
   int ushortsToWrite, bytesWritten;
   ushort UNICODEarray[];
   ushortsToWrite = StringToShortArray(message, UNICODEarray);
   WriteFile(hPipe,ushortsToWrite,sizeof(int),bytesWritten,0);
   WriteFile(hPipe,UNICODEarray,ushortsToWrite*sizeof(ushort),bytesWritten,0);
   return bytesWritten;
  }

La méthode ReadUnicode() reçoit un tableau d'ushorts et renvoie un objet chaîne.

//+------------------------------------------------------------------+
/// ReadUnicode(): read unicode string from a pipe
/// \return unicode string (MQL5 string)
//+------------------------------------------------------------------+
string CNamedPipe::ReadUnicode(void)
  {
   string ret;
   ushort UNICODEarray[STR_SIZE*sizeof(uint)];
   int bytesRead, ushortsToRead;
 
   ReadFile(hPipe,ushortsToRead,sizeof(int),bytesRead,0);
   ReadFile(hPipe,UNICODEarray,ushortsToRead*sizeof(ushort),bytesRead,0);
   if(bytesRead!=0)
      ret = ShortArrayToString(UNICODEarray);
   
   return ret;
  }

La méthode WriteANSI() écrit le tableau uchar ANSI dans un canal.

//+------------------------------------------------------------------+
/// WriteANSI() : write ANSI string to a pipe
/// \param message - string to send
/// \return number of bytes written to a pipe                                                                  
//+------------------------------------------------------------------+
int CNamedPipe::WriteANSI(string message)
  {
   int bytesToWrite, bytesWritten;
   uchar ANSIarray[];
   bytesToWrite = StringToCharArray(message, ANSIarray);
   WriteFile(hPipe,bytesToWrite,sizeof(int),bytesWritten,0);
   WriteFile(hPipe,ANSIarray,bytesToWrite,bytesWritten,0);
   return bytesWritten;
  }

La méthode ReadANSI() lit le tableau uchar à partir d'un canal et renvoie un objet chaîne.

//+------------------------------------------------------------------+
/// ReadANSI(): read ANSI string from a pipe
/// \return unicode string (MQL5 string)
//+------------------------------------------------------------------+
string CNamedPipe::ReadANSI(void)
  {
   string ret;
   uchar ANSIarray[STR_SIZE];
   int bytesRead, bytesToRead;
 
   ReadFile(hPipe,bytesToRead,sizeof(int),bytesRead,0);
   ReadFile(hPipe,ANSIarray,bytesToRead,bytesRead,0);
   if(bytesRead!=0)
      ret = CharArrayToString(ANSIarray);
   
   return ret;
  }

La méthode WriteTick() écrit un seul objet MqlTick dans un canal.

//+------------------------------------------------------------------+
/// WriteTick() : write MqlTick to a pipe
/// \param MqlTick to send
/// \return true if tick was written correctly, false otherwise
//+------------------------------------------------------------------+
int CNamedPipe::WriteTick(MqlTick &outgoing)
  {
   int bytesWritten;

   WriteFile(hPipe,outgoing,MQLTICK_SIZE,bytesWritten,0);

   return bytesWritten;
  }

La méthode ReadTick() lit un seul objet MqlTick à partir d'un canal. Si un canal est vide, il renvoie 0, sinon il doit renvoyer un certain nombre d'octets de l'objet MqlTick.

//+------------------------------------------------------------------+
/// ReadTick() : read MqlTick from a pipe
/// \return true if tick was read correctly, false otherwise
//+------------------------------------------------------------------+
int CNamedPipe::ReadTick(MqlTick &incoming)
  {
   int bytesRead;

   ReadFile(hPipe,incoming,MQLTICK_SIZE,bytesRead,NULL);

   return bytesRead;
  }
//+------------------------------------------------------------------+

Puisque les méthodes de base de gestion des canaux nommés sont connues, nous pouvons commencer par deux programmes MQL : un script simple pour recevoir des offres et un indicateur pour envoyer des offres.

3. Script de serveur pour la réception de offres

L'exemple de serveur lance le canal nommé et attend qu'un client se connecte. Une fois le client déconnecté, il affiche le nombre de ticks reçus par ce client au total et attend qu'un nouveau client se connecte. Si le client est déconnecté et que le serveur trouve une variable globale 'gvar0', il se ferme. Si la variable 'gvar0' n'existe pas, vous pouvez arrêter manuellement le serveur en cliquant sur le bouton droit sur un graphique et en choisissant l'option Liste d'experts.

//+------------------------------------------------------------------+
//|                                              NamedPipeServer.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#include <CNamedPipes.mqh>

CNamedPipe pipe;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   bool tickReceived;
   int i=0;

   if(pipe.Create()==true)
      while (GlobalVariableCheck("gvar0")==false)
        {
         Print("Waiting for client to connect.");
         if (pipe.Connect()==true)
            Print("Pipe connected");
         while(true)
           {
            do
              {
               tickReceived=pipe.ReadTick();

               if(tickReceived==false)
                 {
                  if(GetError()==ERROR_BROKEN_PIPE)
                    {
                     Print("Client disconnected from pipe "+pipe.Name());
                     pipe.Disconnect();
                     break;
                    }
                 } else i++;
                  Print(IntegerToString(i) + "ticks received.");
              } while(tickReceived==true);
            if (i>0) 
            {
               Print(IntegerToString(i) + "ticks received.");
               i=0;
            };
            if(GlobalVariableCheck("gvar0")==true || (GetError()==ERROR_BROKEN_PIPE)) break;
           }

        }

 pipe.Close(); 
  }

4. Indicateur simple pour l'envoi de offres

L'indicateur d'envoi de devis ouvre un canal dans la méthode OnInit() et envoie un seul MqlTick à chaque fois que la méthode OnCalculate() est déclenchée :
//+------------------------------------------------------------------+
//|                                        SendTickPipeIndicator.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
#property indicator_chart_window

#include <CNamedPipes.mqh>

CNamedPipe pipe;
int ctx;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
 
   while (!pipe.Open(AccountInfoInteger(ACCOUNT_LOGIN)))
   {
      Print("Pipe not created, retrying in 5 seconds...");
      if (GlobalVariableCheck("gvar1")==true) break;
   }
   
   ctx = 0;
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
   ctx++;
   MqlTick outgoing;
   SymbolInfoTick(Symbol(), outgoing);
   pipe.WriteTick(outgoing);
   Print(IntegerToString(ctx)+" tick send to server by SendTickPipeClick.");
   return(rates_total);
  }
//+------------------------------------------------------------------+

5. Exécution d'indicateurs de tick de plusieurs fournisseurs dans un seul terminal client

La situation s'est compliquée davantage car je voulais afficher les offres entrantes dans des indicateurs de tick séparés. J'y suis parvenu en implémentant un serveur de canaux qui diffuse les ticks entrants à l'indicateur de tick en déclenchant la méthode EventChartCustom ().

Les soumissions et les demandes de devis sont envoyées sous la forme d'une seule chaîne divisée par un point-virgule, par exemple '1.20223;120225'. L'indicateur approprié gère un événement personnalisé dans OnChartEvent() et affiche un graphique en ticks. 

//+------------------------------------------------------------------+
//|                                   NamedPipeServerBroadcaster.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
#property script_show_inputs
#include <CNamedPipes.mqh>

input int account = 0;

CNamedPipe pipe;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   bool tickReceived;
   int i=0;

   if(pipe.Create(account)==true)
      while(GlobalVariableCheck("gvar0")==false)
        {
         if(pipe.Connect()==true)
            Print("Pipe connected");
            i=0;
         while(true)
           {
            do
              {
               tickReceived=pipe.ReadTick();
               if(tickReceived==false)
                 {
                  if(kernel32::GetLastError()==ERROR_BROKEN_PIPE)
                    {
                     Print("Client disconnected from pipe "+pipe.GetPipeName());
                     pipe.Disconnect();
                     break;
                    }
                  } else  {
                   i++; Print(IntegerToString(i)+" ticks received BY server.");
                  string bidask=DoubleToString(pipe.incoming.bid)+";"+DoubleToString(pipe.incoming.ask);
                  long currChart=ChartFirst(); int chart=0;
                  while(chart<100) 
                    {
                     EventChartCustom(currChart,6666,0,(double)account,bidask);
                     currChart=ChartNext(currChart); 
                     if(currChart==0) break;         // Reached the end of the charts list
                     chart++;
                    }
                     if(GlobalVariableCheck("gvar0")==true || (kernel32::GetLastError()==ERROR_BROKEN_PIPE)) break;
              
                 }
              }
            while(tickReceived==true);
            if(i>0)
              {
               Print(IntegerToString(i)+"ticks received.");
               i=0;
              };
            if(GlobalVariableCheck("gvar0")==true || (kernel32::GetLastError()==ERROR_BROKEN_PIPE)) break;
            Sleep(100);
           }

        }


  pipe.Close(); 
  }

Afin d'afficher les ticks, j'ai choisi l'indicateur de tick placé dans MQLmagazine, mais au lieu de la méthode OnCalculate() j'ai implémenté le traitement dans OnChartEvent() et ajout d'instructions conditionnelles. Une offre est acceptée pour le traitement uniquement si le paramètre dparam est égal au numéro de canal et que l'ID d'événement est égal à CHARTEVENT_CUSTOM +6666 :

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
  if (dparam==(double)incomingPipe)
   if(id>CHARTEVENT_CUSTOM)
     {
      if(id==CHARTEVENT_CUSTOM+6666)
        {
        // Process incoming tick
        }
     } else
        {
         // Handle the user event 
        }
  }

Sur la capture d'écran ci-dessous, il y a trois indicateurs de tick.

Deux d'entre eux affichent les ticks reçus via des canaux et un troisième indicateur qui n'utilise pas de canaux a été exécuté pour vérifier si aucun tick n'a été perdu.  

Indicateur de coche avec des données de différents terminaux

Fig. 1 Offres reçues via un canal nommé

Veuillez trouver ci-joint une vidéographie avec des commentaires sur la façon dont j'exécute les indicateurs :

Fig. 2 vidéographie décrivant la configuration des indicateurs

6. Tester le Débit du Système

Étant donné que les canaux utilisent la mémoire partagée, la communication est très rapide. J'ai effectué des tests d'envoi de 100 000 et 1 000 000 de ticks d'affilée entre deux terminaux MetaTrader 5. Le script d'envoi utilise la fonction WriteTick() et mesure la durée à l'aide de GetTickCount() :

   Print("Sending...");
   uint start = GetTickCount();
   for (int i=0;i<100000;i++)
      pipe.WriteTick(outgoing);
   uint stop = GetTickCount();
   Print("Sending took" + IntegerToString(stop-start) + " [ms]");
   pipe.Close();

Le serveur lit les offres entrantes. La durée est mesurée à partir de la première offre entrante jusqu'à la déconnexion du client :

//+------------------------------------------------------------------+
//|                                          SpeedTestPipeServer.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#property script_show_inputs
#include <CNamedPipes.mqh>

input int account=0;
bool tickReceived;
uint start,stop;

CNamedPipe pipe;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int i=0;
   if(pipe.Create(account)==true)
      if(pipe.Connect()==true)
         Print("Pipe connected");

   do
     {
      tickReceived=pipe.ReadTick();
      if(i==0) start=GetTickCount();
      if(tickReceived==false)
        {
         if(kernel32::GetLastError()==ERROR_BROKEN_PIPE)
           {
            Print("Client disconnected from pipe "+pipe.GetPipeName());
            pipe.Disconnect();
            break;
           }
        }
      else i++;
     }
   while(tickReceived==true);
   stop=GetTickCount();

   if(i>0)
     {
      Print(IntegerToString(i)+" ticks received.");
      i=0;
     };
   
   pipe.Close();
   Print("Server: receiving took "+IntegerToString(stop-start)+" [ms]");

  }
//+------------------------------------------------------------------+

Les résultats pour 10 séries d'échantillons étaient les suivants :

Courir
offres
Temps d'envoi [ms]
Temps de réception [ms]
1
 100000
 624
624
2  100000  702  702
3  100000  687  687
4  100000  592  608
5  100000  624  624
6  1000000  5616  5616
7  1000000  5788  5788
8  1000000  5928  5913
9
 1000000  5772  5756
10
 1000000  5710  5710

Tableau 1 Mesures de vitesse du débit

La vitesse moyenne d'envoi de 1 000 000 d’offres était d'environ 170 000 ticks/seconde sur un ordinateur portable exécutant Windows Vista avec un processeur T4200 à 2,0 GHz et 3 Go de RAM.

Conclusion

J'ai présenté une méthode de communication entre les terminaux MetaTrader 5 à l'aide de Canaux Nommés La méthode s'est avérée suffisante pour envoyer des offres en temps réel entre les terminaux.

La classe CNamedPipes peut être encore étendue en fonction d'exigences supplémentaires, par exemple pour rendre possible la couverture sur deux comptes indépendants. Veuillez trouver ci-joint le code source de la classe CNamedPipe avec la documentation au format chm et tout autre code source que j'ai implémenté pour écrire l'article.

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

Rédaction d'un Expert Advisor à l'aide de l'approche de programmation  orientée-objet MQL5 Rédaction d'un Expert Advisor à l'aide de l'approche de programmation orientée-objet MQL5
Cet article est axé sur l'approche orientée-objet pour faire ce que nous avons fait dans l'article « Guide étape par étape pour écrire un Expert Advisor en MQL5 pour les débutants » : créer un Expert Advisor simple. La plupart des gens pensent que c'est difficile, mais je tiens à vous rassurer qu'au moment où vous aurez fini de lire cet article, vous serez en mesure d'écrire votre propre Expert Advisor qui est orienté-objet.
Une bibliothèque pour créer un graphique via l'API Google Chart Une bibliothèque pour créer un graphique via l'API Google Chart
La création de différents types de diagrammes est une partie essentielle des analyses de l’état du marché et du test d'un système de trading. Fréquemment, afin de créer un beau diagramme, il est nécessaire d'organiser les données de sortie dans un fichier, après quoi elles sont utilisées dans des applications telles que MS Excel. Ce n'est pas très pratique et nous prive de la possibilité de mettre à jour dynamiquement les données. L'API Google Charts a fourni les moyens de créer des graphiques en mode en ligne, en envoyant une requête spéciale au serveur. Dans cet article, nous tentons d'automatiser le processus de création d'une telle demande et d'obtention d'un graphique du serveur Google.
La Méthode Optimale pour le calcul du volume total de la position par Nombre Magique Indiqué La Méthode Optimale pour le calcul du volume total de la position par Nombre Magique Indiqué
Le problème du calcul du volume total de position du symbole indiqué et du nombre magique est examiné dans cet article. La méthode suggérée ne demande que la partie minimale nécessaire de l'historique des deals, trouve le moment le plus proche où la position totale était égale à zéro et effectue les calculs avec les récents deals. Le travail avec des variables globales du terminal client est également envisagé.
L'utilisation de ORDER_MAGIC pour trader avec différents Expert Advisors sur un seul instrument L'utilisation de ORDER_MAGIC pour trader avec différents Expert Advisors sur un seul instrument
Cet article examine les questions du codage de l’information, en utilisant l’identification magique, ainsi que la division, l’assemblage et la synchronisation du trading automatique des différents Expert Advisors. Cet article sera intéressant pour les débutants, ainsi que pour les traders les plus expérimentés, car il traite la question des positions virtuelles, ce qui peut être utile dans l’implémentation des systèmes complexes de synchronisation d’Expert Advisors et de diverses stratégies.