MetaTrader 5 herunterladen

Eine DLL-freie Lösung für die Kommunikation zwischen Terminals von MetaTrader 5 mithilfe von Named Pipes

9 März 2016, 13:40
investeo
0
343

Einleitung

Ich habe einige Zeit überlegt, ob es Möglichkeiten für die Kommunikation zwischen Terminals von MetaTrader 5 gibt. Mein Ziel war es, den Tick-Indikator von verschiedenen Bietern in einem der Terminals zu nutzen und ihre Ticks anzuzeigen.

Die natürliche Lösung war die Verwendung separater Dateien auf einer Festplatte. Ein Terminal schrieb Daten in die Datei und das andere las sie. Obwohl diese Methode für das Senden einzelner Meldungen geeignet ist, scheint sie für den Gebotsfluss nicht die effektivste Methode zu sein.

Dann stieß ich auf einen guten Beitrag von Alexander darüber, wie sich Gebote mithilfe von WCF-Services in .NET-Anwendungen exportieren lassen, und als ich fast fertig war, erschien ein weiterer Beitrag von Sergeev.

Beide Beiträge waren nahe an dem, was ich brauchte, doch ich suchte nach einer DLL-freien Lösung, die von verschiedenen Terminals genutzt werden konnte – einem, das als Server diente, und dem anderen als Client. Während ich im Internet nach einer Lösung suchte, fand ich die Empfehlung, Named Pipes für die Kommunikation zu nutzen, und las die MSDN-Spezifikation für die Interprozesskommunikation mithilfe von Named Pipes sorgfältig durch.

Ich fand heraus, dass Named Pipes die Kommunikation über denselben Computer oder über verschiedene Computer in einem Intranet unterstützen, und beschloss, dieses Vorgehen auszuprobieren.

In diesem Beitrag wird die Kommunikation mithilfe von Named Pipes vorgestellt und der Prozess zum Erstellen der Klasse CNamedPipes beschrieben. Der Beitrag beinhaltet auch einen Tick-Indikator zum Testen der Verbindung zwischen Terminals von MetaTrader 5 und des allgemeinen Durchsatzes des Systems.

1. Interprozesskommunikation mithilfe von Named Pipes

Wenn wir an ein klassisches Rohr (engl. "pipe") denken, stellen wir uns eine Art Zylinder vor, der für die Übertragung von Medien verwendet wird. Dabei handelt es sich auch um ein Mittel für die Interprozesskommunikation in einem Betriebssystem. Stellen Sie sich einfach ein Rohr vor, das zwei Prozesse verbindet, in unserem Fall Terminals von MetaTrader 5, die Daten austauschen. 

Pipes können einen Namen haben oder nicht. Dabei gibt es zwei wesentliche Unterschiede: Der erste ist, dass namenlose Pipes nicht in einem Netzwerk verwendet werden können, der zweite ist, dass zwei Prozesse miteinander verwandt sein müssen. Das heißt, ein Prozess muss übergeordnet sein, der andere untergeordnet. Pipes mit Namen (Named Pipes) haben diese Einschränkung nicht.

Um mithilfe von Pipes kommunizieren zu können, muss ein Serverprozess ein Pipe mit einem bekannten Namen einrichten. Der Name des Pipes ist ein String und muss die Form \\servername\pipe\pipename haben. Werden Pipes auf demselben Computer verwendet, kann der Servername weggelassen und durch einen Punkt ersetzt werden:  \\.\pipe\pipename.

Der Client, der versucht, eine Verbindung mit dem Pipe aufzubauen, muss dessen Namen kennen. Ich nutze die Benennungskonvention \\.\pipe\mt[account_number], um Terminals voneinander zu unterscheiden, doch die Benennungskonventionen sind beliebig austauschbar.

2. Umsetzung der Klasse CNamedPipes

Ich fange mit einer kurzen Beschreibung des Low-Level-Mechanismus zur Erstellung von und Verbindung mit einem Named Pipe an. Unter Windows-Betriebssystemen sind alle Funktionen zur Steuerung von Pipes über die Bibliothek kernel32.dll verfügbar. Die Funktion, die eine Instanz eines Named Pipe auf der Serverseite erstellt, ist CreateNamedPipe().

Nach der Erstellung des Pipes ruft der Server die Funktion ConnectNamedPipe() auf, um auf die Verbindung eines Clients zu warten. Ist die Verbindung erfolgreich, gibt ConnectNamedPipe() einen ganzzahligen Wert ungleich Null aus. Allerdings ist es möglich, dass der Client sich nach dem Aufruf von CreateNamedPipe() und vor dem Aufruf von ConnectNamedPipe() erfolgreich verbindet. In diesem Fall gibt ConnectNamedPipe() Null aus und GetLastError() den Fehler 535 (0X217): ERROR_PIPE_CONNECTED.

Das Schreiben und Lesen aus einem Pipe wird durch dieselben Funktionen bewerkstelligt, mit denen auf Dateien zugegriffen wird:

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

Nachdem ich von Named Pipes erfahren hatte, erstellte ich die Klasse CNamedPipes, um die zugrundeliegenden Low-Level-Anweisungen auszublenden.

Es reicht nun aus, die Datei CNamedPipes.mqh in den entsprechenden Ordner (/include) des Terminals zu legen, sie im Quellcode einzufügen und ein CNamedPipe-Objekt zu deklarieren.

Die Klasse, die ich erstellt habe, stellt einige grundlegende Methoden zur Steuerung von Named Pipes zur Verfügung:

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

Diese Klasse kann je nach zusätzlichen Anforderungen erweitert werden.

Die Methode Create() versucht, ein Pipe mit einem festgelegten Namen zu erstellen. Um die Verbindung zwischen Terminals zu erleichtern, ist der Eingabeparameter 'account' die Account-Nummer eines Clients, der das Pipe nutzen wird.

Wird kein Account-Name eingegeben, versucht die Methode, ein Pipe mit der aktuellen Account-Nummer des Terminals zu öffnen. Wurde das Pipe erfolgreich erstellt, gibt die Funktion Create() true aus.

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

  }

Die Methode Connect() wartet darauf, dass sich ein Client mit einem Pipe verbindet. Sie gibt true aus, wenn der Client sich erfolgreich mit einem Pipe verbunden hat.

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

Die Methode Disconnect() trennt die Verbindung des Servers vom Pipe.

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

Die Methode Open() muss durch den Client verwendet werden. Sie versucht, ein vorher erstelltes Pipe zu öffnen. Sie gibt true aus, wenn die Öffnung des Pipes erfolgreich war.  Sie gibt false aus, wenn aus irgendeinem Grund keine Verbindung zum erstellten Pipe innerhalb von 5 Sekunden möglich war oder wenn die Öffnung des Pipes fehlschlägt.

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

Die Methode Close() schließt das Handle des Pipes.

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

Die nächsten sechs Methoden werden zum Lesen und Schreiben in den Pipes verwendet. Die ersten beiden Paare bearbeiten Strings im Unicode- und ANSI-Format. Beide können genutzt werden, um Befehle oder Meldungen zwischen Terminals zu versenden.

Die String-Variable wird in MQL5 als Objekt im Unicode-Format gespeichert. Deshalb bot es sich an, Unicode-Methoden bereitzustellen, doch da MQL5 auch die Methode UnicodeToANSI bietet, habe ich auch eine Kommunikation durch ANSI-Strings implementiert. Die letzten beiden Methoden bearbeiten den Versand und Empfang des MqlTick-Objekts über ein Named Pipe. 

Die Methode WriteUnicode() schreibt eine Meldung aus Unicode-Zeichen. Da jedes Zeichen aus zwei Bytes besteht, wird die Meldung als Arrays des Typen ushort versendet.

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

Die Methode ReadUnicode() empfängt ushort-Arrays und sendet ein string-Objekt zurück.

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

Die Methode WriteANSI() schreibt ein ANSI-Array des Typen uchar in das 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;
  }

Die Methode ReadANSI() liest das uchar-Array aus dem Pipe und sendet ein string-Objekt zurück.

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

Die Methode WriteTick() schreibt ein einzelnes MqlTick-Objekt in das 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;
  }

Die Methode ReadTick() liest ein einzelnes MqlTick-Objekt aus dem Pipe. Wenn das Pipe leer ist, gibt sie 0 aus, andernfalls sollte sie eine gewisse Menge an Bytes des MqlTick-Objekts ausgeben.

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

Nun, da die grundlegenden Methoden zum Bearbeiten von Named Pipes bekannt sind, können wir mit zwei MQL-Programmen beginnen: einem einfachen Script zum Erhalten von Geboten und einem Indikator zum Senden von Geboten.

3. Serverscript zum Erhalten von Geboten

Der Beispielserver initiiert ein Named Pipe und wartet darauf, dass sich ein Client verbindet. Nach der Trennung des Clients zeigt er an, wie viele Ticks von diesem Client insgesamt erhalten wurden, und wartet darauf, dass sich ein neuer Client verbindet. Wenn die Verbindung des Clients getrennt wurde und der Server die globale Variable 'gvar0' findet, verlässt er den Vorgang. Wenn die Variable 'gvar0' nicht vorhanden ist, lässt sich der Server durch einen Rechtsklick auf ein Diagramm und Auswahl der Option Expert List manuell anhalten.

//+------------------------------------------------------------------+
//|                                              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. Einfacher Indikator zum Senden von Geboten

Der Indikator zum Senden von Geboten öffnet ein Pipe innerhalb der OnInit()-Methode und sendet einen einzelnen MqlTick, sobald die OnCalculate()-Methode ausgelöst wird:
//+------------------------------------------------------------------+
//|                                        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. Ausführen von Tick-Indikatoren von mehreren Anbietern in einem einzelnen Client Terminal

Da ich eingehende Gebote in separaten Tick-Indikatoren darstellen wollte, wurde die Situation komplizierter. Ich erreichte dies durch die Umsetzung eines Pipe-Servers, der eingehende Ticks durch Auslösen der EventChartCustom()-Methode an den Tick-Indikator sendet.

Bid- und Ask-Gebote werden als einzelner String, geteilt durch einen Semikolon gesendet, z. B. '1.20223;120225'. Der jeweilige Indikator verarbeitet ein benutzerdefiniertes Ereignis innerhalb von OnChartEvent() und zeigt ein Tick-Diagramm an. 

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

Für die Darstellung der Ticks wählte ich den Tick-Indikator aus dem MQLmagazine, doch anstatt der OnCalculate()-Methode setzte ich die Verarbeitung innerhalb von OnChartEvent() um und fügte bedingte Anweisungen hinzu. Ein Gebot wird nur für die weitere Verarbeitung akzeptiert, wenn der Parameter dparam der Pipe-Nummer entspricht und die ID des Ereignisses CHARTEVENT_CUSTOM+6666 beträgt:

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 
        }
  }

Im unten abgebildeten Screenshot sehen Sie drei Tick-Indikatoren.

Zwei von ihnen zeigen Ticks an, die durch Pipes erhalten wurden, und ein dritter Indikator, der keine Pipes nutzt, wurde ausgeführt, um zu überprüfen, ob Ticks verlorengegangen sind.  

Tick-Indikator mit Daten von verschiedenen Terminals

Abb. 1. Gebote, die durch ein Named Pipe erhalten wurden

Sehen Sie sich ein Screencast mit Kommentaren an, wie ich die Indikatoren ausführe:

Abb. 2. Screencast mit Beschreibung der Einrichtung der Indikatoren

6. Test des Systemdurchsatzes

Da Pipes einen gemeinsamen Speicher nutzen, ist die Kommunikation sehr schnell. Ich führte Tests aus, in denen ich 100.000 und 1.000.000 Ticks auf einmal zwischen zwei Terminals von MetaTrader 5 versendete. Das versendende Script verwendet die Funktion WriteTick() und misst die Dauer mithilfe von 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();

Der Server liest die eingehenden Gebote. Die Dauer wird vom ersten eingehenden Gebot bis zur Trennung der Verbindung des Clients gemessen:

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

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

Die Ergebnisse von 10 Musterdurchläufen sahen wie folgt aus:

Durchlauf Gebote Versandzeit [ms] Empfangszeit [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

Tabelle 1. Messungen des Durchsatzes

Die durchschnittliche Geschwindigkeit des Versands von 1.000.000 Geboten lag bei etwa 170.000 Ticks/Sekunde auf einem Laptop mit Windows Vista mit 2,0 GHz, T4200-CPU und 3 GB RAM.

Fazit

Ich habe eine Methode für die Kommunikation zwischen Terminals von MetaTrader 5 mithilfe von Named Pipes vorgestellt. Die Methode hat sich als ausreichend für den Versand von Echtzeitgeboten zwischen Terminals erwiesen.

Die Klasse CNamedPipes kann je nach zusätzlichen Anforderungen erweitert werden, zum Beispiel, um Absicherungen von zwei unabhängigen Accounts zu ermöglichen. Im Anhang finden Sie den Quellcode der CNamedPipe-Klasse mit Dokumentation im chm-Format und weitere Quellcodes, die ich zum Verfassen dieses Beitrags genutzt habe.

Übersetzt aus dem Englischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/en/articles/115

Einen Expert Advisor mit Hilfe des MQL5 Objekt-orientierten Programmieransatzes schreiben Einen Expert Advisor mit Hilfe des MQL5 Objekt-orientierten Programmieransatzes schreiben

Dieser Beitrag beschäftigt sich mit dem Objekt-orientierten Ansatz, um das zu machen, was wir bereits im Artikel "Schrittweiser Leitfaden zum Schreiben eines Expert Advisors in MQL5 für Anfänger" getan haben - einen einfachen Expert Advisor erstellen. Die meisten Menschen glauben, das sei schwer, doch ich darf Ihnen versichern: wenn Sie diesen Beitrag gelesen haben, dann können Sie Ihren eigenen Objekt-orientierten Expert Advisor schreiben.

Bibliothek für die Erstellung von Diagrammen über die Google Chart API Bibliothek für die Erstellung von Diagrammen über die Google Chart API

Die Erstellung verschiedener Typen von Diagrammen ist ein wesentlicher Bestandteil der Analyse der Marktsituation und der Tests eines Handelssystems. Um ein ansehnliches Diagramm erstellen zu können, muss die Datenausgabe häufig in einer Datei organisiert werden, die daraufhin in Anwendungen wie MS Excel verwendet wird. Das ist nicht sehr praktisch und nimmt uns die Möglichkeit, die Daten dynamisch zu aktualisieren. Die Google Charts API stellt die Instrumente für die Erstellung von Diagrammen online durch Senden einer speziellen Anfrage an den Server bereit. In diesem Beitrag werden wir versuchen, den Prozess der Erstellung einer solchen Anfrage zu automatisieren und ein Diagramm vom Google-Server abzurufen.

Die Ereignisverarbeitungsroutine "Neuer Balken" Die Ereignisverarbeitungsroutine "Neuer Balken"

Die Programmiersprache MQL5 kann helfen, Probleme auf einer ganz neuen Ebene zu lösen. Selbst Aufgaben, für die es bereits eine Lösung gibt, können dank der objektorientierten Programmierung auf ein höheres Niveau gebracht werden. In diesem Beitrag geht es um ein besonders einfaches Beispiel für die Überprüfung des Auftretens eines neuen Balkens in einem Diagramm, das in ein leistungsfähiges und vielseitiges Hilfsmittel verwandelt wurde. Was ist das für ein Hilfsmittel? Das verrät dieser Artikel.

Growing Neural Gas: Umsetzung in MQL5 Growing Neural Gas: Umsetzung in MQL5

In diesem Artikel wird ein Beispiel für die Entwicklung eines MQL5-Programms zur Umsetzung des als Growing Neural Gas (GNG) bezeichneten adaptiven Clustering-Algorithmus vorgestellt. Dieser Beitrag richtet sich an Anwender, die die Dokumentation zu dieser Programmiersprache gelesen haben und über gewisse Erfahrungen und Grundkenntnisse im Bereich Neuroinformatik verfügen.