English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Una soluzione senza DLL per comunicare tra i terminali MetaTrader 5 utilizzando le Named Pipe

Una soluzione senza DLL per comunicare tra i terminali MetaTrader 5 utilizzando le Named Pipe

MetaTrader 5Esempi | 16 dicembre 2021, 10:49
89 1
investeo
investeo

Introduzione

Mi chiedevo da tempo sulle possibili modalità di comunicazione tra i terminali MetaTrader 5. Il mio obiettivo era utilizzare l'indicatore tick e visualizzare i tick di diversi provider di quotazioni in uno dei terminali.

La soluzione naturale era utilizzare file separati su un disco rigido. Un terminale scriverebbe i dati sul file e l'altro li leggerebbe. Questo metodo, sebbene rilevante per l'invio di singoli messaggi, non sembra essere il più efficace per le quotazioni in streaming.

Poi mi sono imbattuto in un buon articolo di Alexander su come esportare quotazioni nelle applicazioni .NET utilizzando i servizi WCF e quando stavo per finire è apparso un altro articolo di Sergeev.

Entrambi gli articoli erano vicini a ciò di cui avevo bisogno, ma ho cercato una soluzione senza DLL che potesse essere utilizzata da diversi terminali, uno che fungesse da server e l'altro da client. Durante la ricerca sul Web ho trovato una nota che suggerisce che si potrebbero utilizzare le Named Pipe per la comunicazione e ho letto attentamente Specifiche MSDN per la Comunicazione tra Processi utilizzando le pipe.

Ho scoperto che le Named Pipe supportano la comunicazione sullo stesso computer o su computer diversi su intranet. Ho deciso di seguire questo approccio.

Questo articolo introduce la comunicazione Named Pipe e descrive un processo di progettazione della classe CNamedPipes. Include anche il test dello streaming dell'indicatore di tick tra i terminali MetaTrader 5 e il throughput complessivo del sistema.

1. Comunicazione tra processi mediante le Named Pipe

Quando pensiamo a una tipica pipe immaginiamo una sorta di cilindro che viene utilizzato per trasmettere i media. Questo è anche un termine usato per uno dei mezzi di comunicazione tra processi su un sistema operativo. Potresti semplicemente immaginare una pipe che collega due processi, nel nostro caso i terminali MetaTrader 5 che scambiano dati. 

Le pipe possono essere anonime o avere un nome. Ci sono due differenze principali tra loro: la prima è che le pipe anonime non possono essere utilizzate su una rete e la seconda che i due processi devono essere correlati. Questo processo deve essere un genitore e l'altro un processo figlio. Le named pipe non hanno questa limitazione.

Per comunicare utilizzando le pipe, un processo del server deve impostare una pipe con un nome noto. Il nome della pipe è una stringa e deve essere nel formato \\servername\pipe\pipename. Se le pipe vengono utilizzate sullo stesso computer, è possibile omettere il nome del server e inserire un punto al suo posto: \\.\pipe\pipename.

Il client che tenta di connettersi a una pipe deve conoscerne il nome. Sto usando una convenzione di nome di \\.\pipe\mt[account_number] per distinguere i terminali, ma la convenzione di denominazione può essere modificata arbitrariamente.

2. Implementazione della classe CNamedPipes

Inizierò con una breve descrizione del meccanismo di basso livello di creazione e connessione a una named pipe. Sui sistemi operativi Windows tutte le funzioni che gestiscono le pipe sono disponibili tramite la libreria kernel32.dll. La funzione che crea un'istanza di una named pipe sul lato del server è CreateNamedPipe().

Dopo la creazione della pipe, il server chiama la funzione ConnectNamedPipe() per attendere la connessione di un client. Se la connessione ha esito positivo, ConnectNamedPipe() restituisce un numero intero diverso da zero. È possibile, tuttavia, che il client si sia connesso correttamente dopo aver chiamato CreateNamedPipe() e prima che ConnectNamedPipe() sia stato chiamato. In questo caso ConnectNamedPipe() restituisce zero e GetLastError() restituisce l'errore 535 (0X217): ERROR_PIPE_CONNECTED.

La scrittura e la lettura da una pipe si ottengono con le stesse funzioni dell'accesso ai file:

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
);

Avendo imparato sulle named pipe, ho progettato la classe CNamedPipes per nascondere le istruzioni di basso livello sottostanti.

Ora è sufficiente mettere il file CNamedPipes.mqh nella cartella appropriata (/include) del terminale e includerlo nel codice sorgente e dichiarare un oggetto CNamedPipe.

La classe che ho progettato espone alcuni metodi di base per gestire le pipe denominate:

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

La classe può essere ulteriormente ampliata in base a ulteriori esigenze.

Il metodo Create() tenta di creare una pipe con un dato nome. Per semplificare la connessione tra terminali, il parametro di input 'account' è il numero di account di un client che utilizzerà una pipe.

Se il nome dell'account non viene inserito, il metodo tenta di aprire una pipe con il numero dell'account di un terminale corrente. La funzione Create() restituisce true se la pipe è stata creata correttamente.

//+------------------------------------------------------------------+
/// 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;

  }

Il metodo Connect() attende che un client si connetta a una pipe. Restituisce true se il client si è connesso correttamente a una pipe.

//+------------------------------------------------------------------+
/// 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;
  }

Il metodo Disconnect() disconnette il server da una pipe.

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

Il metodo Open() dovrebbe essere usato da un client e prova ad aprire una pipe precedentemente creata. Ritorna true se l'apertura della pipe ha avuto successo.  Ritorna false se per qualche motivo non è stato possibile connettersi alla pipe creata entro 5 secondi di timeout o se l'apertura della pipe non è riuscita.

//+------------------------------------------------------------------+
/// 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;
  }

Il metodo Close() chiude l'handle della pipe.

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

I successivi sei metodi vengono utilizzati per leggere e scrivere attraverso le pipe. Le prime due coppie gestiscono stringhe nei formati Unicode e ANSI, entrambe possono essere utilizzate per inviare comandi o messaggi tra terminali.

La variabile stringa in MQL5 è memorizzata come un oggetto che contiene Unicode, quindi il modo naturale era fornire metodi Unicode, ma poiché MQL5 fornisce metodi UnicodeToANSI ho anche implementato la comunicazione con stringhe ANSI. Gli ultimi due metodi gestiscono l'invio e la ricezione di oggetti MqlTick tramite una named pipe. 

Il metodo WriteUnicode() scrive il messaggio composto da caratteri Unicode. Poiché ogni carattere è composto da due byte, viene inviato come un array di ushort a una pipe.

//+------------------------------------------------------------------+
/// 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;
  }

Il metodo ReadUnicode() riceve l'array di ushort e restituisce un oggetto stringa.

//+------------------------------------------------------------------+
/// 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;
  }

Il metodo WriteANSI() scrive l'array uchar ANSI in una pipe.

//+------------------------------------------------------------------+
/// 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;
  }

Il metodo ReadANSI() legge l'array uchar da una pipe e restituisce un oggetto stringa.

//+------------------------------------------------------------------+
/// 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;
  }

Il metodo WriteTick() scrive un singolo oggetto MqlTick in una pipe.

//+------------------------------------------------------------------+
/// 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;
  }

ReadTick() legge un singolo oggetto MqlTick da una pipe. Se una pipe è vuota ritorna 0, in caso contrario dovrebbe ritornare un numero di byte dell'oggetto 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;
  }
//+------------------------------------------------------------------+

Poiché sono noti i metodi di base per gestire le named pipe, possiamo iniziare con due programmi MQL: un semplice script per ricevere le quotazioni e un indicatore per l'invio delle quotazioni.

3. Script del Server per Ricevere Quotazioni

Il server di esempio avvia la named pipe e attende la connessione di un client. Dopo la disconnessione del client, mostra quanti tick sono stati ricevuti da quel client in totale e attende la connessione di un nuovo client. Se il client è disconnesso e il server trova una variabile globale 'gvar0' esce. Se la variabile 'gvar0' non esiste, è possibile arrestare manualmente il server facendo click con il pulsante destro del mouse su un grafico e scegliendo l'opzione Elenco Expert.

//+------------------------------------------------------------------+
//|                                              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. Indicatore Semplice per l'Invio di Quotazioni

L'indicatore per l'invio delle quotazioni apre una pipe all'interno del metodo OnInit() e invia un singolo MqlTick ogni volta che viene attivato il metodo OnCalculate():
//+------------------------------------------------------------------+
//|                                        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. Esecuzione degli Indicatori di Tick da Più Provider in un Singolo Terminale Client

La situazione si è complicata perché volevo visualizzare le quotazioni in arrivo in indicatori di tick separati. Ho ottenuto questo risultato implementando il pipe server che trasmette i tick in entrata all'indicatore di tick attivando il metodo EventChartCustom().

Le quotazioni bid e ask vengono inviate come una singola stringa divisa da un punto e virgola, ad es '1.20223;120225'. L'indicatore appropriato gestisce un evento personalizzato all'interno di OnChartEvent() e visualizza un grafico tick. 

//+------------------------------------------------------------------+
//|                                   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(); 
  }

Per visualizzare i tick ho scelto l'indicatore di tick inserito in MQLmagazine, ma invece del metodo OnCalculate() ho implementato elaborazione all'interno di OnChartEvent() e aggiunto istruzioni condizionali. Una quotazione viene accettata per l'elaborazione solo se il parametro dparam è uguale al numero di pipe e l'ID evento è uguale a 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 
        }
  }

Nello screenshot qui sotto ci sono tre indicatori di tick

Due di essi visualizzano i tick ricevuti tramite le pipe e un terzo indicatore che non utilizza le pipe è stato eseguito per verificare se non sono stati persi tick.  

Indicatore di tick con dati provenienti da terminali diversi

Fig. 1 Quotazioni ricevute tramite una named pipe

In allegato uno screencast con commenti su come gestisco gli indicatori:

Fig. 2 Screencast che descrive la configurazione degli indicatori

6. Test del Rendimento del Sistema

Poiché le pipe utilizzano la memoria condivisa, la comunicazione è molto veloce. Ho condotto test di invio di 100.000 e 1.000.000 di tick di fila tra due terminali MetaTrader 5. Lo script di invio utilizza la funzione WriteTick() e misura l'intervallo di tempo utilizzando 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();

Il server legge le quotazioni in arrivo. L'intervallo di tempo viene misurato dalla prima quotazione in arrivo fino alla disconnessione del cliente:

//+------------------------------------------------------------------+
//|                                          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]");

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

I risultati per 10 esecuzioni di prova sono stati i seguenti:

Run
Quotazioni
Tempo di invio [ms]
Tempo di ricezione [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

Tabella 1 Throughput della velocità di produzione

La velocità media di invio di 1.000.000 di quotazioni era di circa 170 000 tick al secondo su un laptop con Windows Vista con CPU T4200 a 2.0GHz e 3GB di RAM.

Conclusione

Ho presentato un metodo di comunicazione tra i terminali MetaTrader 5 utilizzando Named Pipe. Il metodo si è rivelato sufficiente per inviare preventivi in tempo reale tra terminali.

La classe CNamedPipes può essere ulteriormente estesa in base ad ulteriori esigenze, ad esempio per rendere possibile la copertura su due account indipendenti. Si prega di trovare in allegato il codice sorgente della classe CNamedPipe con la documentazione in formato chm e un altro codice sorgente che ho implementato per scrivere l'articolo.

Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/115

Ultimi commenti | Vai alla discussione (1)
Emilio Reale
Emilio Reale | 23 nov 2023 a 18:14
lavoro fantastico
Scrivere un Expert Advisor Utilizzando l'Approccio di Programmazione Orientato agli Oggetti MQL5 Scrivere un Expert Advisor Utilizzando l'Approccio di Programmazione Orientato agli Oggetti MQL5
Questo articolo si concentra sull'approccio orientato agli oggetti per fare ciò che abbiamo fatto nell'articolo "Guida Passo per Passo per Scrivere un Expert Advisor in MQL5 per Principianti" - creazione di un semplice Expert Advisor. La maggior parte delle persone pensa che sia difficile, ma voglio assicurarti che quando avrai finito di leggere questo articolo, sarai in grado di scrivere il tuo Expert Advisor che è basato sugli oggetti.
Una Libreria per la Costruzione di un Grafico tramite l'API Google Chart Una Libreria per la Costruzione di un Grafico tramite l'API Google Chart
La costruzione di vari tipi di diagrammi è una parte essenziale nelle analisi della situazione del mercato e del test di un sistema di trading. Spesso, per costruire un diagramma di bell'aspetto, è necessario organizzare l'output dei dati in un file, dopo di che viene utilizzato in applicazioni come MS Excel. Questo non è molto conveniente e ci priva della possibilità di aggiornare dinamicamente i dati. L'API di Google Charts ha fornito i mezzi per creare grafici in modalità online, inviando una richiesta speciale al server. In questo articolo cercheremo di automatizzare il processo di creazione di tale richiesta e ottenere un grafico dal server di Google.
Il Metodo Ottimale per il Calcolo del Volume Totale della Posizione in Base al Numero Magico Specificato Il Metodo Ottimale per il Calcolo del Volume Totale della Posizione in Base al Numero Magico Specificato
Il problema del calcolo del volume totale della posizione del simbolo specificato e del numero magico è considerato in questo articolo. Il metodo proposto richiede solo la parte minima necessaria della cronologia degli affari, trova il momento più vicino in cui la posizione totale era uguale a zero ed esegue i calcoli con le operazioni recenti. Viene anche considerato il lavoro con le variabili globali del terminale client.
L'Uso di ORDER_MAGIC per il Trading con Diversi Expert Advisor su un Singolo Strumento L'Uso di ORDER_MAGIC per il Trading con Diversi Expert Advisor su un Singolo Strumento
Questo articolo considera le questioni della codifica delle informazioni, utilizzando l'identificazione magica, nonché la divisione, l'assemblaggio e la sincronizzazione del trading automatico di diversi Expert Advisor. Questo articolo sarà interessante per i principianti, così come per i trader più esperti, perché affronta la questione delle posizioni virtuali, che possono essere utili nell'implementazione di complessi sistemi di sincronizzazione di Expert Advisor e varie strategie.