LifeHack für Trader: ein back-Test ist gut, und vier – ist besser

Vladimir Karputov | 22 August, 2016

Vor jedem Trader bei dem ersten einzelnen Test steht eine und derselbe Frage — "Welchen von vier Modus ich verwenden soll?" Jeder des angebotenen Modus hat eigene Vorteile und Besonderheiten, deshalb machen wir es einfacher — wir werden direkt alle Modus durch eine Taste starten! Im Artikel ist es vorgeführt, wie man mit Hilfe Win API und der kleinen Magie gleichzeitig alle vier Graphik des Tests sehen kann.

Inhaltsverzeichnis


Einführung

Das Hauptziel des vorliegenden Artikels — ist zu zeigen, wie man aus einem Terminal (nennen wir mal es Meisterterminal) einen einzelnen Test (nicht die Optimierung, und zwar die Prüfung!) des Beraters direkt in vier Terminalen starten soll (wir nennen sie als untergeordnete Terminals und ordnen sie unter die Bezeichnungen #1, #2, #3 und #4 zu). Dabei werden Testers der Strategien in den untergeordneten Terminalen in verschiedenen Modus der Tick-Erzeugung gestartet:

Die wichtigen Beschränkungen:

  1. Das Meisterterminal soll ohne Schlüssel /portable gestartet werden.
  2. Man muss mindestens fünf installierten Terminals MetaTrader 5 haben.
  3. Das Handelskonto im Meisterterminal — nennen wir es Meister-Konto — muss selbst einmal in jedem der untergeordneten Terminals gestartet werden. Das muss man wissen, weil der Experte aus dem vorliegenden Artikel das Kennwort vom Handelskonto durch die ini-Dateien in die untergeordneten Terminals nicht übermittelt. Anstatt dem Kennwort wird nur die Nummer des Handelskontos übermittelt, auf der man den Teste der Strategien starten muss, und diese Nummer stimmt immer mit der Nummer der Meister-Konto überein.
    Solches Verhalten wird logisch gesehen, da man den Test des Beraters in verschiedenen Modus der Tick-Erzeugung auf einem und derselben Handelskonto leiten muss.
  4. Vor dem Start des Experten laden Sie maximal den zentralen Prozessor aus: schalten Sie Onlinespiele, den Medienplayer und andere viel Ressource besetzte Programme aus. Ansonsten kann einer der Prozessen-Kern gesperrt werden und auf diesem Kern wird der Test nicht gestartet.

1. Die Grundprinzipien

Ein kopf ist gut zwei besser (das polnische Sprichwort). 

Ich bevorzuge immer die Nutzung der standardmäßigen Möglichkeiten der Software. Im Bezug auf die Handelsterminale MetaTrader 5 lautet diese Regel so: "Niemals das Terminal mit dem Schlüssel/portable starten, niemals im Betriebssystem die Kontrolle der Berechnungsaufzeichnungen der Benutzer (UAC) ausschalten. Abgesehen davon, die Arbeit mit den Dateien wird der beschriebene EA im Ordner AppData durchführen.

Alle im Artikel aufführende Skrinshots demonstrieren die Arbeit in Windows 10, da es das neueste und vollständige Lizenzsystem ist. Gerade in der Anwendung wird zu ihr der ganze Code betrachtet, der in dieser Arbeit beschrieben ist.

Der betrachtete EA, neben den Möglichkeiten MQL5, verwendet dll:


in Abb. 1. Die Abhängigkeiten 

Es wurden solche Funktionsaufrufe verwendet, wie Windows API:


2. Eingangsparameter


in Abb. 3. Eingangsparameter 

Der Pfad folder of the MetaTrader#Х installation — sind Pfade zu den Installation-Ordern der untergeordneten Terminals. Bei der Aufgabe des Pfades in mq5-Code muss man den doppelten Slash verordnen. Es ist auch sehr wichtig den doppelten rückgängigen Slash am Ende des Pfades einzufügen:

//--- input parameters                                 
input string   ExtInstallationPathTerminal_1="C:\\Program Files\\MetaTrader 5 1\\";    // folder of the MetaTrader#1 installation
input string   ExtInstallationPathTerminal_2="D:\\MetaTrader 5 2\\";                   // folder of the MetaTrader#2 installation
input string   ExtInstallationPathTerminal_3="D:\\MetaTrader 5 3\\";                   // folder of the MetaTrader#3 installation
input string   ExtInstallationPathTerminal_4="D:\\MetaTrader 5 4\\";                   // folder of the MetaTrader#4 installation
input string   ExtTerminalName="terminal64.exe";                                       // correct name of the file of the terminal

Der Titel des Terminals "terminal64.exe" wurde für ein 64-Bit-Betriebssystem aufgeführt.

 

Die Verbindung zwischen dem Ordner der Installierung und dem Verzeichnis der Daten im Ordner AppData 

Beim Start des Terminals wird das Terminal nach gewöhnlicher Weise oder mit dem Schlüssel /portable verschiedene Pfade für die Variable TERMINAL_DATA_PATH ausgeben. Wir betrachten diese Situation auf dem Beispiel des Meisterterminals, das im Ordner "C:\Program Files\MetaTrader 5 1 installiert ist.

Wenn das Meisterterminal mit dem Schlüssel/portable gestartet ist, wird es aus diesem Terminal MQL solche Ergebnisse ausgeben:

TERMINAL_PATH = C:\Program Files\MetaTrader 5
TERMINAL_DATA_PATH = C:\Program Files\MetaTrader 5
TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Und die Antwort dieses Terminals ohne Schlüssel/portable:

TERMINAL_PATH = C:\Program Files\MetaTrader 5
TERMINAL_DATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Aber es wird uns nützlich sein, nur wenn wir die Parameter aus dem laufenden Terminal bekommen werden. Und was soll man mit den untergeordneten Terminals tun, in den wir den Test des EAs starten werden? Wie soll man die Installationsverknüpfungen der untergeordneten Terminals mit den Verknüpfungen ihrer Verzeichnisse der Daten verbinden?

Hier muss man erklären, warum ist es so wichtig, den Pfad zu den Verzeichnisse der Daten im Ordner AppData (das Zitat aus der Auskunft) zu wissen:

Von MS Windows Vista, als Voreinstellung der Programme, die im Verzeichnis Program Files installiert sind, ist es verboten, Daten im Installation-Verzeichnis zu speichern. Alle Daten sollen im getrennten Verzeichnis der Benutzer-Daten Windows gespeichert werden.

Mit anderen Worten, unser Experte kann die Dateien im Ordner ändern und erstellen, der ähnlich dem folgenden Ordner ist: C:\Users\Benutzer-Name\AppData\Roaming\MetaQuotes\Terminal\Identifikator des Terminals\MQL5\Files. Hier ist "der Identifikator des Terminales" ist ein Identifikator des Meisterterminals.


3. Wir stellen den Ordner der Installierung und den Ordner AppData der untergeordneten Terminals gegenüber

Der Experte erfüllt den Start der untergeordneten Terminals mittels des Hinweises der Konfigurationsdatei. Dabei wird für jedes Terminal seine eigene Konfigurationsdatei verwendet. In jeder Konfigurationsdatei gibt es den Hinweis darauf, dass es beim Start des Terminalen der Test des aufgegebenen EAs begonnen werden muss. Die entsprechenden Befehle werden in der Sektion [Tester] der Konfigurationsdatei aufgestellt:

...
[Tester]
Expert=test
...

Wie Sie sehen, wird der Pfad nicht angewiesen, und es bedeutet, dass der getestete EA ausschließlich im "Sandbox" MQL5 sein kann. Auf dem Beispiel des untergeordneten Terminals 1 können zwei Pfade sein:

  1. C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\MQL5\Experts
  2. oder C:\Program Files\MetaTrader 5 1\MQL5\Experts

Die Variante №2 schließen wir aus, weil es Sicherheitspolitik laut ist, von Windows Vista an, wird es uns verboten, im Ordner "Program Files" zu schreiben. Es bleibt die Variante №1 — und es bedeutet, dass wir für alle untergeordneten Terminals die Gegenüberstellung der Installation-Ordner und der Ordner in AppData durchführen müssen. 

3.1. Geheimnis №1

In jedem Verzeichnis der Daten gibt es die Datei "origin.txt". Auf dem Beispiel des untergeordneten Terminals 1:


 

in Abb. 4. Die Datei origin.txt 

und der Inhalt der Datei origin.txt:

C:\Program Files\MetaTrader 5 1

Der vorliegende Eintrag in der Datei weist darauf hin, dass der Ordner "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962" das Terminal erstellt hat, das in "C:\Program Files\MetaTrader 5 1" installiert wurde.

3.2. FindFirstFileW, FindNextFileW

FindFirstFileW — führt die Suche des Verzeichnisses für die Datei oder das Unterverzeichnis mit dem Namen durch, der mit einem bestimmten Namen (oder dem Teil des Namens übereinstimmt, wenn spezielle Symbole verwendet werden).

HANDLE  FindFirstFileW(
   string           lpFileName,         //
   WIN32_FIND_DATA  &lpFindFileData     //
   ); 

Parameter

lpFileName

[in]  Das Verzeichnis oder der Pfad und der Name der Datei, die die Symbole der Substitution enthalten kann, zum Beispiel, das Sternchen (*) oder ein Fragezeichen(?).

lpFindFileData

[in][out]  Der Register auf die Struktur WIN32_FIND_DATA, die die Informationen über die gefundene Datei oder das Verzeichnis bekommt. 

Rückgabewert

Wenn die Funktion erfolgreich endet, der zurückgegebene Wert wird Handel der Suche, der im nachfolgenden Aufruf FindNextFile or FindClose verwendet wird, und der Parameter  lpFindFileData enthält die Informationen über die erste Datei oder die gefundene Datei.

Wenn die Funktion abstützt oder kann nicht die Dateien aus der Suche-Zeile im Parameter lpFileName finden, dann kehrt der WertINVALID_HANDLE_VALUE zurück, und der Inhalt lpFindFileData wird unbestimmt. Um die zusätzlichen Informationen über den Fehler zu bekommen, rufen Sie die Funktion  GetLastError auf.

Wenn die Funktion nicht angeht, weil die entsprechenden Dateien nicht gefunden werden können,die Funktion GetLastError liefert ERROR_FILE_NOT_FOUND zurück.


FindNextFileWsetzt die Suche der Datei aus dem vorhergehenden Aufruf der Funktion FindFirstFile fort , FindFirstFileEx, oder FindFirstFileTransacted.

bool  FindNextFileW(
   HANDLE           FindFile,           //
   WIN32_FIND_DATA  &lpFindFileData     //
   );

Parameter

FindFile

[in] Handel der Suche, der vom vorhergehenden Aufruf der Funktion FindFirstFile oder FindFirstFileEx zurückgegeben ist.

lpFindFileData

[in][out]  Der Register auf die Struktur WIN32_FIND_DATA, die die Informationen über die gefundene Datei oder das Verzeichnis bekommt. 

Rückgabewert

Wenn die Funktion erfolgreich endet, so ist der zurückgegebene Wert der Null nicht gleich, und der Parameter  lpFindFileData wird die Informationen über die folgende Datei oder über das gefundene Verzeichnis enthalten.

Wenn die Funktion mit einem Fehler endet, so ist der zurückgegebene Wert der Null gleich, und der Inhalt lpFindFileData wird unbestimmt. Um die zusätzlichen Informationen über den Fehler zu bekommen, rufen Sie die Funktion  GetLastError auf.

Wenn die Funktion abstützt, weil die die Dateien nicht mehr finden kann, die Funktion GetLastError liefert ERROR_NO_MORE_FILES zurück.

Das Beispiel der Erklärung Win API Funktion  FindFirstFileW und FindNextFileW (der Code wurde aus der hinzugefügten Datei  ListingFilesDirectory.mqh genommen):

#define MAX_PATH                 0x00000104  //
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010  //
#define ERROR_NO_MORE_FILES      0x00000012  //there are no more files
#define ERROR_FILE_NOT_FOUND     0x00000002  //the system cannot find the file specified
//+------------------------------------------------------------------+
//| FILETIME structure                                               |
//+------------------------------------------------------------------+
struct FILETIME
  {
   uint              dwLowDateTime;
   uint              dwHighDateTime;
  };
//+------------------------------------------------------------------+
//| WIN32_FIND_DATA structure                                        |
//+------------------------------------------------------------------+
struct WIN32_FIND_DATA
  {
   uint              dwFileAttributes;
   FILETIME          ftCreationTime;
   FILETIME          ftLastAccessTime;
   FILETIME          ftLastWriteTime;
   uint              nFileSizeHigh;
   uint              nFileSizeLow;
   uint              dwReserved0;
   uint              dwReserved1;
   ushort            cFileName[MAX_PATH];
   ushort            cAlternateFileName[14];
  };

#import "kernel32.dll"
int      GetLastError();
long     FindFirstFileW(string lpFileName,WIN32_FIND_DATA  &lpFindFileData);
int      FindNextFileW(long FindFile,WIN32_FIND_DATA &lpFindFileData);
int      FindClose(long hFindFile);
int      FindNextFileW(int FindFile,WIN32_FIND_DATA &lpFindFileData);
int      FindClose(int hFindFile);
int      CopyFileW(string lpExistingFileName,string lpNewFileName,bool bFailIfExists);
#import

bool WinAPI_FindClose(long hFindFile)
  {
   bool res;
   if(_IsX64)
      res=FindClose(hFindFile)!=0;      
   else
      res=FindClose((int)hFindFile)!=0;      
//---
   return(res);
  }
  
bool WinAPI_FindNextFile(long hFindFile,WIN32_FIND_DATA &lpFindFileData)
  {
   bool res;
   if(_IsX64)
      res=FindNextFileW(hFindFile,lpFindFileData)!=0;      
   else
      res=FindNextFileW((int)hFindFile,lpFindFileData)!=0;      
//---
   return(res);
  }

3.3. Die beispielsweise Verwendung FindFirstFileW, FindNextFileW

Der Skript "ListingFilesDirectory.mq5" — ist zugleich ein Beispiel, ist aber auch fast eine vollständige Kopie des Arbeitscodes des EAs. Mit anderen Worten, dieser Code ist maximal ähnlich mit der Realität.

Die Aufgabe: den Namen aller Ordners für die Verknüpfung TERMINAL_COMMONDATA_PATH — "Common" zu bekommen. 

Auf dem Beispiel meines Computers ist die Verknüpfung TERMINAL_COMMONDATA_PATH liefert den Wert "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common" zurück. Das bedeutet, wenn wir von dieser Verknüpfung "Common" ausschließen", dann bekommen wir die echte Verknüpfung C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\":


in Abb. 5. Find First 

Normalerweise ist für die Suche aller Dateien wird die Maske der Suche "*.*" verwendet. Das bedeutet, wir müssen zwei Operationen mit folgenden Zeilen durchführen: das Wort "Common" ausschließen, und danach die Maske "*.*" hinzufügen:

   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
   int pos=StringFind(common_data_path,"Common",0);
   if(pos!=-1)
     {
      common_data_path=StringSubstr(common_data_path,0,pos-1);
     }
   else
      return;

   string path_addition="\\*.*";
   string mask_path=common_data_path+path_addition;
   printf("mask_path=%s",mask_path);

Prüfen wir mal, welcher Pfad sich im Endeffekt ergeben hat. Dazu werden wir den Halt-Punkt feststellen und starten Einrichtung


 

in Abb. 6. Einrichtung 

Dann erhalten wir:

mask_path=C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*

Bisher ist alles richtig: die Maske ist vorbereitet, um ALLE Dateien und Ordner im Verzeichnis "C zu suchen:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\".

Wir gehen weiter: wir initialisieren Suchhandel "hFind" (in meinem Fall ist es eher wegen Gewohnheit verursacht worden) und rufen Win API der Funktion FindFirstFileW auf:

   printf("mask_path=%s",mask_path);
   hFind=-100;
   hFind=FindFirstFileW(mask_path,ffd);
   if(hFind==INVALID_HANDLE)
     {
      PrintFormat("Failed FindFirstFile (hFind) with error: %x",kernel32::GetLastError());
      return;
     }

// List all the files in the directory with some info about them

Wenn der Aufruf FindFirstFileW mit Misserfolg endet, so wird Suchhandel "hFind" dem "INVALID_HANDLE" gleich sein und die Ausführung des Skripts endet.

Im Falle des erfolgreichen Aufrufs FindFirstFileW rganisieren wir die Loop do while, in der wir den Namen der Datei oder des Ordners bekommen, und am Ende der Loop wird Win API der Funktion FindNextFileW aufgerufen:

// List all the files in the directory with some info about them
   PrintFormat("hFind=%d",hFind);
   bool rezult=0;
   do
     {
      string name="";
      for(int i=0;i<MAX_PATH;i++)
        {
         name+=ShortToString(ffd.cFileName[i]);
        }
      
      Print("\"",name,"\", File Attribute Constants (dec): ",ffd.dwFileAttributes);
      //---
      ArrayInitialize(ffd.cFileName,0);
      ArrayInitialize(ffd.cAlternateFileName,0);
      ffd.dwFileAttributes=-100;
      ResetLastError();
      rezult=FindNextFileW(hFind,ffd);
     }
   while(rezult!=0);
   if(kernel32::GetLastError()!=ERROR_NO_MORE_FILES)
      PrintFormat("Failed FindNextFileW (hFind) with error: %x",kernel32::GetLastError());
   WinAPI_FindClose(hFind);

Die Loop do while wird weiter gehen, bis der Aufruf Win API der Funktion FindNextFileW nicht nullwertige Wert zurückgibt. Wenn der Aufruf Win API der Funktion FindNextFileW Null zurückgibt und der Fehler dem "ERROR_NO_MORE_FILES" nicht gleich ist — bedeutet dies, es ist ein kritischer Fehler aufgetreten.

Am Ende der Arbeit des Skripts schlissen wir den Handel

Das Ergebnis der Arbeit des Skripts "ListingFilesDirectory.mq5":

mask_path=C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*
hFind=-847293552
".", File Attribute Constants (dec): 16
"..", File Attribute Constants (dec): 16
"038C9E8FAFF9EA373522ECC6D5159962", File Attribute Constants (dec): 16
"0C46DDCEB43080B0EC647E0C66170465", File Attribute Constants (dec): 16
"2A6A33B25AA0984C6AB9D7F28665B88E", File Attribute Constants (dec): 16
"50CA3DFB510CC5A8F28B48D1BF2A5702", File Attribute Constants (dec): 16
"BC11041F9347CD71C5F8926F53AA908A", File Attribute Constants (dec): 16
"Common", File Attribute Constants (dec): 16
"Community", File Attribute Constants (dec): 16
"D0E8209F77C8CF37AD8BF550E51FF075", File Attribute Constants (dec): 16
"D3852169A6E781B7F35488A051432620", File Attribute Constants (dec): 16
"EE57F715BA53F2E183D6731C9376293D", File Attribute Constants (dec): 16
"Help", File Attribute Constants (dec): 16

3.4. Wir schauen in den Ordner der Terminals hinein

Das oben aufgeführte Beispiel hat uns die Arbeit auf dem oberen Niveau — im Ordner "C demonstriert:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\". Aber wir erinnern uns an den Abschnitt 3.1. Geheimnis №1, dementsprechend müssen wir in alle angelegten Ordner hineinschauen.

Dazu organisieren wir eine zweistufige Suche, wobei man für die Suche in den angelegten Ordnern eine solche Maske der primären Suche für Win API der Funktion FindFirstFileW verwenden muss:

"C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\" + der Name des gefundenen Ordners der oberen Ebene + "origin.txt".

So wird die primäre Suche FindFirstFileW im angelegten Ordner nur eine Datei — "origin.txt" suchen.

Ich führe die vollständige Liste der Funktion FindDataPath()auf:

//+------------------------------------------------------------------+
//| Find and read the origin.txt                                     |
//+------------------------------------------------------------------+
void FindDataPath(string &array[][2])
  {
//---
   WIN32_FIND_DATA ffd;
   long            hFirstFind_0,hFirstFind_1;

   ArrayInitialize(ffd.cFileName,0);
   ArrayInitialize(ffd.cAlternateFileName,0);
//+------------------------------------------------------------------+
//| Get common path for all of the terminals installed on a computer.|
//| The common path on my computer:                                  |
//| C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common          |
//+------------------------------------------------------------------+
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
   int pos=StringFind(common_data_path,"Common",0);
   if(pos!=-1)
     {
      //+------------------------------------------------------------------+
      //| Cuts "Common" ... and we get:                                    |
      //| C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal                 |
      //+------------------------------------------------------------------+
      common_data_path=StringSubstr(common_data_path,0,pos-1);
     }
   else
      return;

//--- stage Search №0. 
   string filter_0=common_data_path+"\\*.*"; // filter_0==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*

   hFirstFind_0=FindFirstFileW(filter_0,ffd);
//---
   string str_handle="";
   if(hFirstFind_0==INVALID_HANDLE)
      str_handle="INVALID_HANDLE";
   else
      str_handle=IntegerToString(hFirstFind_0);
   Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",str_handle);
//---
   if(hFirstFind_0==INVALID_HANDLE)
     {
      PrintFormat("Failed FindFirstFile (hFirstFind_0) with error: %x",kernel32::GetLastError());
      return;
     }

//--- list all the files in the directory with some info about them
   bool rezult=0;
   do
     {
      if((ffd.dwFileAttributes  &FILE_ATTRIBUTE_DIRECTORY)==FILE_ATTRIBUTE_DIRECTORY)
        {
         string name_0="";
         for(int i=0;i<MAX_PATH;i++)
           {
            name_0+=ShortToString(ffd.cFileName[i]);
           }
         if(name_0!="." && name_0!="..")
           {
            ArrayInitialize(ffd.cFileName,0);
            ArrayInitialize(ffd.cAlternateFileName,0);
            //--- stage Search №1. search origin.txt file in the folder
            string filter_1=common_data_path+"\\"+name_0+"\\origin.txt";
            ResetLastError();
            hFirstFind_1=FindFirstFileW(filter_1,ffd);
            //---
            if(hFirstFind_1==INVALID_HANDLE)
               str_handle="INVALID_HANDLE";
            else
               str_handle=IntegerToString(hFirstFind_1);
            Print("   filter_1: \"",filter_1,"\", handle hFirstFind_1: ",str_handle);
            //---
            if(hFirstFind_1==INVALID_HANDLE)
              {
               if(kernel32::GetLastError()!=ERROR_FILE_NOT_FOUND)
                 {
                  PrintFormat("Failed FindFirstFile (hFirstFind_1) with error: %x",kernel32::GetLastError());
                  break;
                 }
               WinAPI_FindClose(hFirstFind_1);
               ArrayInitialize(ffd.cFileName,0);
               ArrayInitialize(ffd.cAlternateFileName,0);
               ResetLastError();
               rezult=WinAPI_FindNextFile(hFirstFind_0,ffd);
               continue;
              }
            //--- origin.txt file in this folder is found
            bool rezultTwo=0;
            string name_1="";
            for(int i=0;i<MAX_PATH;i++)
              {
               name_1+=ShortToString(ffd.cFileName[i]);
              }
            string origin=CopiedAndReadFile(filter_1); //--- receiving a string of the file found origin.txt
            if(origin!=NULL)
              {
               //--- write a string into an array
               int size=ArrayRange(array,0);
               ArrayResize(array,size+1,0);
               array[size][0]=common_data_path+"\\"+name_0;
               //value array[][0]==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
               array[size][1]=origin;
               //value array[][1]==C:\Program Files\MetaTrader 5 1\
              }
            WinAPI_FindClose(hFirstFind_1);
           }
        }
      ArrayInitialize(ffd.cFileName,0);
      ArrayInitialize(ffd.cAlternateFileName,0);
      ResetLastError();
      rezult=WinAPI_FindNextFile(hFirstFind_0,ffd);
     }
   while(rezult!=0); //if(hFirstFind_1==INVALID_HANDLE), we appear here
   if(kernel32::GetLastError()!=ERROR_NO_MORE_FILES)
      PrintFormat("Failed FindNextFileW (hFirstFind_0) with error: %x",kernel32::GetLastError());
   else
      Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",hFirstFind_0,", NO_MORE_FILES");
   WinAPI_FindClose(hFirstFind_0);
  }

Die Funktion FindDataPath() drückt ungefähr eine solche Information aus:

filter_0: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*", handle hFirstFind_0: 1901014212592
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\origin.txt", handle hFirstFind_1: 1901014213744
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\0C46DDCEB43080B0EC647E0C66170465\origin.txt", handle hFirstFind_1: 1901014213840
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\2A6A33B25AA0984C6AB9D7F28665B88E\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\50CA3DFB510CC5A8F28B48D1BF2A5702\origin.txt", handle hFirstFind_1: 1901014218448
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\BC11041F9347CD71C5F8926F53AA908A\origin.txt", handle hFirstFind_1: 1901014213936
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Community\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\origin.txt", handle hFirstFind_1: 1901014216720
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D3852169A6E781B7F35488A051432620\origin.txt", handle hFirstFind_1: 1901014217104
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\EE57F715BA53F2E183D6731C9376293D\origin.txt", handle hFirstFind_1: 1901014218640
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Help\origin.txt", handle hFirstFind_1: INVALID_HANDLE
filter_0: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*", handle hFirstFind_0: 1901014212592, NO_MORE_FILES 

Die Erklärungen zu den ersten Zeilen des Ausdruckens: erstens wird der Filter "filter_0" der primären Suche erstellt (der Filter ist dem "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*" gleich) und wir bekommen Handel der primären Suche "hFirstFind_0", der ist 1901014212592. Wenn der Wert "hFirstFind_0" dem "INVALID_HANDLE" nicht gleich ist — heißt, dass der "filter_0" der primären Suche, der in Win API der Funktion FindFirstFileW(filter_0,ffd) übertragen wurde, ist korrekt. Nach dem erfolgreichen Aufruf  FindFirstFileW(filter_0,ffd) bekommen wir den Namen des ersten geratenen Ordners: das ist der Ordner "038C9E8FAFF9EA373522ECC6D5159962". 

Weiter muss man die Suche der Datei origin.txt" innerhalb im Ordner 038C9E8FAFF9EA373522ECC6D5159962 durchführen. Dazu bilden wir den Maske-Filter. Zum Beispiel, für den Ordner 038C9E8FAFF9EA373522ECC6D5159962, wird die Maske so aussehen: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\origin.txt". Wenn der Handel "hFirstFind_1" dabei dem "INVALID_HANDLE" nicht gleich sein wird — bedeutet dies, dass es im angegebenen Ordner (038C9E8FAFF9EA373522ECC6D5159962)die gesuchte Datei (origin.txt) gibt. 

Im Ausdrucken ist es gut zu sehen, dass die primäre Suche in den angelegten Ordnern "INVALID_HANDLE" manchmal zurückgibt. Dies bedeutet, dass es in angegeben Ordnern keine Datei "origin.txt" gibt. 

Wir werden es ausführlicher betrachten, was wir tun sollen, wenn die Datei  "origin.txt" in einem angelegten Ordner gefunden wird.

3.5. CopyFileW

CopyFileW — kopiert die existierende Datei in eine neue Datei.

bool  CopyFileW(
   string lpExistingFileName,     //
   string lpNewFileName,          //
   bool bFailIfExists             //
   );

Parameter

lpExistingFileName

[in] Der Name der existierenden Datei.

Hier wird die Beschränkung nach der Länge des Namens — MAX_PATH der Symbole zwangsweise genommen, aber es reicht immer für unser Beispiel.

Wenn die Datei mit dem Namen lpExistingFileName nicht existiert, dann hat Funktion Misserfolg, und GetLastError gibt ERROR_FILE_NOT_FOUND zurück.

lpNewFileName

[in]  Der Name der neuen Datei. 

Hier wird die Beschränkung nach der Länge des Namens — MAX_PATH der Symbolen zwangsweise genommen, aber es reicht immer für unser Beispiel.

bFailIfExists
[in] 
Wenn dieser Parameter TRUE und die neue Datei, die in lpNewFileName angegeben ist, wirklich existiert, dann hat Funktion Misserfolg. Wenn dieser Parameter FALSE d die neue Datei existieren, wird die Funktion die existierende Datei abschreiben und die Arbeit wird erfolgreich beenden.

Rückgabewert

Wenn die Funktion erfolgreich endet, so ist der zurückgegebene Wert der Null nicht gleich.

Wenn die Funktion mit einem Fehler endet, so ist der zurückgegebene Wert der Null gleich. Um die zusätzlichen Informationen über den Fehler zu bekommen, rufen Sie die Funktion  GetLastError auf.

Das Beispiel der Erklärung Win API Funktion CopyFileW (der Code wurde aus der hinzugefügten Datei  ListingFilesDirectory.mqh genommen):

#import "kernel32.dll"
int      GetLastError();
bool     CopyFileW(string lpExistingFileName,string lpNewFileName,bool bFailIfExists);
#import

3.6. Wir arbeiten mit der Datei "origin.txt"

Die Beschreibung der Arbeit der Funktion ListingFilesDirectory.mqh::CopiedAndReadFile(string full_file_name).

Im Eingangsparameter bekommt die Funktion den vollen Namen der Datei "origin.txt", der in einer des angelegten Ordners gefunden war. Es kann ungefähr ein solcher Pfad sein: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\origin.txt". Die Datei "origin.txt" öffnen und eben seinen Inhalt lesen werden wir mit Hilfe MQL5 tun, und es bedeutet, dass die Datei im "Sandbox" sein muss. Das Bedeutet, wir müssen die Datei "origin.txt" aus dem angelegten Ordner in den Sandbox (in diesem Fall in "den Sandboxen" in den allgemeinen Dateien aller Terminals) kopieren. Ein solches Kopieren führen wir mit Hilfe des Aufrufs Win API der Funktion CopyFileW aus.

Wir schreiben in die Variable "new_path" den Pfad zur Datei "origin.txt" im Sandbox:

//+------------------------------------------------------------------+
//| Copying to the Common Data Folder                                |
//| for all client terminals ***\Terminal\Common\Files               |
//+------------------------------------------------------------------+
string CopiedAndReadFile(string full_file_name)
  {
   string new_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\Files\\origin.txt";
// => new_path==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common\Files\origin.txt
//--- Win API

und rufen Win API der Funktion CopyFileW mit dem dritten Parameter, der false gleich ist, auf — erlauben wir die Übernahme der Datei "origin.txt" im Sandbox:

//--- Win API
   if(!CopyFileW(full_file_name,new_path,false))
     {
      Print("Error CopyFile ",full_file_name," to ",new_path);
      return(NULL);
     }
//--- open the file using MQL5

Mit den Mitteln MQL5 öffnen wir die Datei "origin.txt" für das Lesen, dabei werden wir nicht vergessen, die Fahne FILE_COMMON zu bezeichnen, denn die Datei befindet sich im Ordner der allgemeinen Dateien:

//--- open the file using MQL5
   string str;
   ResetLastError();
   int file_handle=FileOpen("origin.txt",FILE_READ|FILE_TXT|FILE_COMMON);
   if(file_handle!=INVALID_HANDLE)
     {
      //--- read a string using the MQL5 
      str=FileReadString(file_handle,-1)+"\\";
      //--- close the file using the MQL5
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("File %s open failed , MQL5 error=%d","origin.txt",GetLastError());
      return(NULL);
     }
   return(str);
  }

Wir lesen nur einmal — eine Zeile, schreiben zu ihr am Ende "\\" und geben das bekommende Ergebnis zurück.

3.7. Der letzte Strich

In den Eingangsparametern des EAs werden die Pfade zu den Ordnern der Installation für vier Terminals angegeben:

//--- input parameters                                 
input string   ExtInstallationPathTerminal_1="C:\\Program Files\\MetaTrader 5 1\\";    // folder of the MetaTrader#1 installation
input string   ExtInstallationPathTerminal_2="D:\\MetaTrader 5 2\\";                   // folder of the MetaTrader#2 installation
input string   ExtInstallationPathTerminal_3="D:\\MetaTrader 5 3\\";                   // folder of the MetaTrader#3 installation
input string   ExtInstallationPathTerminal_4="D:\\MetaTrader 5 4\\";                   // folder of the MetaTrader#4 installation

Diese Pfade werden gründlich geschrieben, und sie sollen auf die Installation-Ordner der Terminals korrekt hinweisen.

Auch unten, auf der globalen Ebene, wurden noch vier Zeile-Variable und ein Massiv erklärt:

string         slaveTerminalDataPath1=NULL;                                // the path to the Data Folder of the terminal #1
string         slaveTerminalDataPath2=NULL;                                // the path to the Data Folder of the terminal #2
string         slaveTerminalDataPath3=NULL;                                // the path to the Data Folder of the terminal #3
string         slaveTerminalDataPath4=NULL;                                // the path to the Data Folder of the terminal #4
//---
string         arr_path[][2];

In diese Variable muss man die Pfade zu den Ordnern der Terminals in AppData aufschreiben, und das zweidimensionale Massiv wird darin helfen. Jetzt kann man das allgemeine Schema bilden, wie die Ordner der Installierung der untergeordneten Terminals mit ihren Ordnern in AppData vergleichen:

GetStatsFromAccounts_EA.mq5::OnInit() >Aufruf> GetStatsFromAccounts_EA.mq5::FindDataFolders(arr_path) 
>Aufruf> ListingFilesDirectory.mqh::FindDataPath(string &array[][2]) >Aufruf> CopiedAndReadFile(string full_file_name) 

            string origin=CopiedAndReadFile(filter_1); //--- receiving a string of the file found origin.txt
            if(origin!=NULL)
              {
               //--- write a string into an array
               int size=ArrayRange(array,0);
               ArrayResize(array,size+1,0);
               array[size][0]=common_data_path+"\\"+name_0;
               //value array[][0]==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
               array[size][1]=origin;
               //value array[][1]==C:\Program Files\MetaTrader 5 1\
              }
            FindClose(hFirstFind_1);

In der Funktion ListingFilesDirectory.mqh::FindDataPath(string &array[][2]), wenn es in den angelegten Ordnern der Terminals die Datei "origin.txt" gefunden wird, wird die Funktion CopiedAndReadFile(string full_file_name) aufgerufen, und nach ihrem Aufruf wird die Aufzeichnung ins zweidimensionale Massiv durchgeführt. In der Messung des Massives "0" wird der Pfad zum Ordner des Terminals in AppData geschrieben, und in der Messung des Massives "1" wird der Pfad zum Ordner der Installation geschrieben (dieser Pfad, ich werde Sie erinnern, bekommen wir aus der gefundenen Datei "origin.txt").

>geben wir die Steuerung in > GetStatsFromAccounts_EA.mq5::FindDataFolders(arr_path)zurück: 

hier, durch eine Umgehung um zweidimensionales Massiv werden Variable slaveTerminalDataPath1, slaveTerminalDataPath2, slaveTerminalDataPath3 und slaveTerminalDataPath4 gefüllt:

   FindDataPath(array);
   for(int i=0;i<ArrayRange(array,0);i++)
     {
      //Print("array[",i,"][0]: ",array[i][0]);
      //Print("array[",i,"][1]: ",array[i][1]);
      if(StringCompare(ExtInstallationPathTerminal_1,array[i][1],true)==0)
         slaveTerminalDataPath1=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_2,array[i][1],true)==0)
         slaveTerminalDataPath2=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_3,array[i][1],true)==0)
         slaveTerminalDataPath3=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_4,array[i][1],true)==0)
         slaveTerminalDataPath4=array[i][0];
     }
   if(slaveTerminalDataPath1==NULL || slaveTerminalDataPath2==NULL ||
      slaveTerminalDataPath3==NULL || slaveTerminalDataPath4==NULL)
     {
      Print("slaveTerminalDataPath1 ",slaveTerminalDataPath1,", slaveTerminalDataPath2 ",slaveTerminalDataPath2);
      Print("slaveTerminalDataPath3 ",slaveTerminalDataPath3,", slaveTerminalDataPath4 ",slaveTerminalDataPath4);
      return(false);
     }

Wenn wir bis zu dieser Etappe angekommen sind, bedeutet es, der EA hat die Pfade der Installation der Terminals und die Pfade ihrer Ordner in AppData verglichen. Falls es selbst mindestens einen Pfad zum Ordner des Terminals in AppData nicht gefunden wurde (das heißt, der ist NULL), so werden alle Pfade in den letzten Zeilen ausgedruckt  und der EA wird die Arbeit mit einem Fehler beenden.


4. Die Auswahl des Expert Advisors (EA) für den Test

Vor dem Start der vier untergeordneten Terminals muss man die Datei des geprüften Experten zuerst wählen. Dieser Experte soll im Katalog der Daten des Meisterterminals vorläufig kompiliert und gelegen sein.

4.1. GetOpenFileName

GetOpenFileName — erstellt das Dialogfenster " Zu öffnen", das dem Benutzer zulässt, die Festplatte, den Ordner und den Namen der Datei oder des Satzes von Dateien zu hinzuweisen, die geöffnet sein werden. Die Erklärung und die Realisierung des Dialogfensters "Zu öffnen" wurde in der aufgenommenen Datei GetOpenFileNameW.mqh vollständig vorgestellt.

4.2. Wir wählen den EA mit Hilfe des Systemdialoges "Datei Öffnen"

Das Systemdialogfenster "Zu öffnen" wird aus OnInit() des EAs aufgerufen:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals

da wird GetOpenFileNameW.mqh::OpenFileName(void) aufgerufen

//+------------------------------------------------------------------+
//| Creates an Open dialog box                                       |
//+------------------------------------------------------------------+
string OpenFileName(void)
  {
   string path=NULL;
   string filter=NULL;
   if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
      filter="Der kompilierte Code";
   else
      filter="Compiled code";
   if(GetOpenFileName(path,filter+"\0*.ex5\0",TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\","Select source file"))
      return(path);
   else
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(NULL);
     }
  }

Die Variable "path" wird beim erfolgreichen Aufruf Win API der Funktion GetOpenFileName, den vollen Namen der gewählten Datei enthalten, in der Art wie: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Experts\Examples\MACD\MACD Sample.ex5".

Die Variable "filter" ist für den Text ① Abb. verantwortlich 2. Die Zeile "\0*.ex5\0" ist für den Filter der Datei-Typs (② Abb. 2) verantwortlich. Die Zeile "TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\"" gibt den Pfad zum Ordner auf, der im Systemdialog "Zu öffnen" geöffnet sein wird.

4.3. Die konfiguratierten ini-Datei

Für den Start des Terminals auf den Test des EAs aus der Kommandozeile (oder mit Hilfe Win API), muss man die konfigurationsini-Datei haben, in dem die Abteilung [Tester] und nötige Hinweise sein müssen:

[Tester]
Expert=test             //der Name der Datei des Experten, der auf den Test gestartet sein soll
Symbol=EURUSD           //der Name des Instruments, das als Hauptsymbol des Testes ausgenutzt wird
Period=H1               //Die Periode des Charts des Testes
Deposit=10000           // Die Summe des Anfangsdeposites für den Test
Model=4                 //Der Modus der Ticks-Erzeugung
Optimization=0          //Die Anschaltung/Ausschaltung der Optimierung und der Hinweis ihrer Art
FromDate=2016.01.22     //Das Anfangsdatum des Testes
ToDate=2016.06.06       //Das endliche Datum des Testes
Report=TesterReport     //Der Name der Datei, in der der Bericht über die Test-Ergebnisse gespeichert wird
ReplaceReport=1         //Aktivieren/Deaktivieren das Überschreiben der Datei des Berichtes 
UseLocal=1              //Aktivieren/Deaktivieren der Möglichkeit der Nutzung der lokalen Agenten für den Test
Port=3000               //Der Hafen des Test-Agenten
Visual=0                //Einshalten oder Ausschalten des Tests im visuellen Modus
ShutdownTerminal=0      //Aktivieren/Deaktivieren die Ausschaltung des Handelsplattformes nach der Vollendung des Tests 

Im Voraus hineinlaufend würde ich sagen, dass wir dieser Abschnitt [Tester] zute Datei selbständig hinzufügen werden.

Für die Grundlage war es entschieden, die ini-Datei des Meisterterminals zu nehmen. Diese Datei (common.ini) befindet sich im Verzeichnis der Daten des Terminals, im Ordner "config". Für mein Terminal sieht der Pfad zu ihm so aus: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\config\common.ini".

Das Schema der Arbeit mit der ini-Datei ist so:

  1. Den vollen Pfad zu "des common.ini" des Meisterterminals zu bekommen. Der volle Pfad ist es eine Zeile nach dieser Art 
    "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\config\common.ini". (MQL5)
  2. Den neuen Pfad zur ini-Datei im Sandbox "\Files" zu bekommen. Der neue Pfad ist es eine Zeile nach dieser Art:
    "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\myconfiguration.ini" Meisterterminal. (MQL5)
  3. Das Kopieren der Datei "common.ini" in "myconfiguration.ini". (WIn API die Funktion CopyFileW).
  4. Die Editierung der Datei "myconfiguration.ini". (MQL5).
  5. Den neuen Pfad zur ini-Datei im Sandbox des untergeordneten Terminals zu bekommen. Es ist die Zeile der Art (auf dem Beispiel meines untergeordneten Terminals №1)
    "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\MQL5\Files\myconfiguration.ini". (MQL5)
  6. Die editierte ini-Datei "myconfiguration.ini"  aus dem Sandbox des Meisterterminals in den Sandbox des untergeordneten Terminals zu kopieren. (WIn API die Funktion CopyFileW).
  7. Die Entfernung der Datei "myconfiguration.ini" aus dem Sandbox des Meisterterminals. (MQL5)

Für jedes untergeordnete Terminal muss man dieses Schema wiederholen. Obwohl es hier die Stelle für die Optimierung gibt, aber das Ziel wurde von mir nicht gestellt, diesen Prozess im gegenwärtigen Artikel zu beschreiben. 

Die Editierung der konfigurations ini-Dateien beginnen wir,nachdem der EA für den Test ausgewählt wurde, GetStatsFromAccounts_EA.mq5::OnInit():.mq5:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals
      if(!CopyCommonIni())
         return(INIT_FAILED);
      if(!CopyTerminalIni())
         return(INIT_FAILED);
      //--- copying an expert in the terminal folders

Das Schema der Arbeit mit der ini-Datei, auf dem Beispiel des untergeordneten Terminals №1, GetStatsFromAccounts_EA.mq5::CopyCommonIni():

//+------------------------------------------------------------------+
//| Copying common.ini - file in a shared folder of client           |
//| terminals. Edit the ini-file and copy obtained                   |
//| ini-files into folders                                           |
//| ...\AppData\Roaming\MetaQuotes\Terminal\"id terminal"\MQL5\Files |
//+------------------------------------------------------------------+
bool CopyCommonIni()
  {
//0 — "Evey tick", "1 — 1 minute OHLC", 2 — "Open price only"
//3 — "Math calculations", 4 — "Every tick based on real ticks" 
//--- path to Data Folder
   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
//--- path to Commomm Data Folder
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
//---
   string existing_file_name=terminal_data_path+"\\config\\common.ini"; // full path to the ini-file                                                        
   string temp_name_ini=terminal_data_path+"\\MQL5\\Files\\"+common_file_name;
   string test=NULL;
//--- terminal #1
   if(!CopyFileW(existing_file_name,temp_name_ini,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }
   EditCommonIniFile(common_file_name,3000,4);
   test=slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name;
   if(!CopyFileW(temp_name_ini,test,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }
   ResetLastError();
   if(!FileDelete(common_file_name,0))
      Print("#1 file ",common_file_name," not deleted, an error ",GetLastError());
//--- terminal #2

Im Aufruf dieser Funktion EditCommonIniFile(common_file_name,3000,4) werden übertragen:

common_file_name — Der Name der ini-Datei, die man editieren muss;

3000 — die Nummer des Hafens des Test-Agenten. Jedes Terminal soll auf seinem Test-Agenten gestartet werden. Die Nummerierung der Agenten wird seit 3000 ausgeführt. Die Häfen-Nummern der Test-Agenten kann man so sehen: im Terminal MetaTrader 5 in den Strategie-Tester gehen und im Titel "Journal" des Strategie-Testers mit der rechten Taste drücken. Dabei kann man im ausfallenden Menü die Nummerierung der Häfen der Test-Agenten sehen:


 

in Abb. 7. Test-Agenten 

4 - Der Typ des Tests: 

Die Editierung der konfigurations- commom.ini Datei wird in der Funktion GetStatsFromAccounts_EA.mq5 durchgeführt: EditCommonIniFile (string name, const int port, const int model) — die Operationen der Eröffnung der Datei, der Lesung aus der Datei und der Aufzeichnung in die Datei werden durch Mitteln MQL5 durchgeführt:

//+------------------------------------------------------------------+
//| Editing common.ini file                                          |
//+------------------------------------------------------------------+
bool EditCommonIniFile(string name,const int port,const int model)
  {
   bool tester=false;      // if false - means the section [Tester] not found
   int  count_tester=0;    // counter discoveries section [Tester]
//--- Datei eröffnen 
   ResetLastError();
   int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      //--- auxiliary variable
      string str;
      //--- read data
      while(!FileIsEnding(file_handle))
        {
         //--- read line 
         str=FileReadString(file_handle,-1);
         //--- find [Tester]
         if(StringFind(str,"[Tester]",0)!=-1)
           {
            tester=true;
            count_tester++;
           }
        }
      if(!tester)
        {
         FileWriteString(file_handle,"[Tester]\n",-1);
         FileWriteString(file_handle,"Expert=test\n",-1);
         FileWriteString(file_handle,"Symbol=EURUSD\n",-1);
         FileWriteString(file_handle,"Period=H1\n",-1);
         FileWriteString(file_handle,"Deposit=10000\n",-1);
         //0 — "Evey tick", "1 — 1 minute OHLC", 2 — "Open price only"
         //3 — "Math calculations", 4 — "Every tick based on real ticks" 
         FileWriteString(file_handle,"Model="+IntegerToString(model)+"\n",-1);
         FileWriteString(file_handle,"Optimization=0\n",-1);
         FileWriteString(file_handle,"FromDate=2016.01.22\n",-1);
         FileWriteString(file_handle,"ToDate=2016.06.06\n",-1);
         FileWriteString(file_handle,"Report=TesterReport\n",-1);
         FileWriteString(file_handle,"ReplaceReport=1\n",-1);
         FileWriteString(file_handle,"UseLocal=1\n",-1);
         FileWriteString(file_handle,"Port="+IntegerToString(port)+"\n",-1);
         FileWriteString(file_handle,"Visual=0\n",-1);
         FileWriteString(file_handle,"ShutdownTerminal=0\n",-1);
        }
      //--- close file
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
      return(false);
     }
   return(true);
  }

4.4. Geheimnis №2

Vor dem Arbeitsende, wird das Terminal MetaTrader 5 die Anordnung der Fenster und der Paneele, sowie ihre Umfänge in der Datei "terminal.ini" speichern. Diese Datei selbst befindet sich im Verzeichnis der Daten des untergeordneten Terminals, im Ordner "config". Zum Beispiel, für mein untergeordnetes Terminal №1 wird der volle Pfad zum "terminal.ini" so aussehen:

"C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\config\terminal.ini".

In der Datei "terminal.ini" wird uns nur der Block "[Window]" interessieren. Machen wir das Terminal MetaTrader 5 in ein Fenster. Das Terminal wird ungefähr solche Umfänge übernehmen:


in Abb. 8. Das zusammengerollte Terminal ins Fenster

Wenn dieses Terminal geschlossen ist, so wird in der Datei terminal.ini der Block [Window] solche Art haben:

Arrange=1
[Window]
Fullscreen=0
Type=1
Left=412
Top=65
Right=1212
Bottom=665
LSave=412

Das heißt, der Block [Window] bewahrt die Koordinaten des Terminales und seinen Zustand. 


4.5. Wir geben die Größe des Terminals (die Breite, die Höhe) ein. Die Eingabe der Zeilen in die Mitte der Datei 

Die Koordinaten in den Dateien terminal.ini der untergeordneten Terminals muss damit ändern, um alle vier untergeordneten Terminals beim Start in einer solchen aufgebauten Anordnung zu haben:

 

in Abb. . Die Anordnung der Terminals

Wie es oben gesagt wurde, muss man die Datei "terminal.imi" für jedes untergeordnete Terminal editieren. Hier muss man das beachten, dass man die Zeilen nicht ins Ende, sondern in der Mitte der Datei "terminal.ini" einstellen muss. Unten wurden die Besonderheiten dieser Prozedur vorgeführt.

Ich werde auf solchem Beispiel erklären: es gibt die Datei "test.txt", die im "Sandbox" des Terminals ist. Der Inhalt der Datei "test.txt":

s=0
df=12
asf=3
g=3
n=0
param_f=123

Man muss die Informationen in der zweiten und dritten Zeile ändern, sodass es sich so ergebe:

s=0
df=1256
asf=5
g=3
n=0
param_f=123

Anscheinend, soll man dies so tun:

Wir sehen es auf dem Beispiel des Script-Codes "InsertRowsMistakenly.mq5":
//+------------------------------------------------------------------+
//|                                         InsertRowsMistakenly.mq5 |
//|                              Copyright © 2016, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2016, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- open file
   ResetLastError();
   string name="test.txt";
   int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      FileReadString(file_handle,-1);
      FileWriteString(file_handle,"df=1256"+"\r\n",-1);
      FileWriteString(file_handle,"asf=5"+"\r\n",-1);
      //--- close file
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
      return;
     }
  }
//+------------------------------------------------------------------+

Wir bekommen ein unerwartetes Ereignis — in der vierten Zeile sind Symbole verschwunden "g=":

Es war so und es ist dann so geworden 
s=0
df=12
asf=3
g=3
n=0
param_f=123
s=0
df=1256
asf=5
3
n=0
param_f=123

Warum ist es so geschehen? Stellen Sie sich vor, dass die Datei aus einer Menge der Zellen besteht, die nacheinander gehen. In jeder Zelle wird ein Symbol unterbracht. Deshalb, wenn wir in der Datei, seit seiner Mitte schreiben, dann schreiben wir die Zellen eigentlich einfach wieder ein. Wenn es mehrere Symbole hinzufügen als es auf dieser Stelle (wie im höheren Beispiel ursprünglich war: es war "df=12", und wir haben auf zwei Symbole mehr - "df=1256" aufgezeichnet), so werden die überflüssigen Symbole den weiteren Code einfach beschädigen. So sieht das aus:

write string

in Abb. 10. Die Beschädigung der Informationen.

Um die Beschädigung der Informationen beim Einschub der Zeilen in die Mitte der Datei zu vermeiden, werden wir auf folgende Weise handeln.

Die Ordnung der Aufrufe der Funktionen:

GetStatsFromAccounts_EA.mq5::OnInit() >Aufruf> GetStatsFromAccounts_EA.mq5::CopyTerminalIni()

//+------------------------------------------------------------------+
//| Editing Files "terminal.ini"                                     |
//+------------------------------------------------------------------+
bool CopyTerminalIni()
  {
//--- path to the terminal data folder 
   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
//---
   string existing_file_name=NULL;
   string ext_ini=terminal_data_path+"\\MQL5\\Files\\terminal_ext.ini";
   string ini=terminal_data_path+"\\MQL5\\Files\\terminal.ini";
   int left=0;
   int top=0;
   int right=0;
   int bottom=0;
//---
   for(int i=1;i<5;i++)
     {
      switch(i)
        {
         case 1:
            existing_file_name=slaveTerminalDataPath1+"\\config\\terminal.ini";
            left=0; top=0; right=682; bottom=420;
            break;
         case 2:
            existing_file_name=slaveTerminalDataPath2+"\\config\\terminal.ini";
            left=682; top=0; right=1366; bottom=420;
            break;
         case 3:
            existing_file_name=slaveTerminalDataPath3+"\\config\\terminal.ini";
            left=0; top=738-413; right=682; bottom=738;
            break;
         case 4:
            existing_file_name=slaveTerminalDataPath4+"\\config\\terminal.ini";
            left=682; top=738-413; right=1366; bottom=738;
            break;
        }
      //---
      if(!CopyFileW(existing_file_name,ext_ini,false))
        {
         PrintFormat("Failed with error: %x",kernel32::GetLastError());
         return(false);
        }
      if(!EditTerminalIniFile("terminal_ext.ini",left,top,right,bottom))
         return(false);
      if(!CopyFileW(ini,existing_file_name,false))
        {
         PrintFormat("Failed with error: %x",kernel32::GetLastError());
         return(false);
        }
      ResetLastError();
      if(!FileDelete("terminal.ini",0))
         Print("#",i," file terminal.ini not deleted, an error ",GetLastError());
      ResetLastError();
      if(!FileDelete("terminal_ext.ini",0))
         Print("#",i," file terminal_ext.ini not deleted, an error ",GetLastError());
     }
//---
   return(true);
  }

 >Aufruf> GetStatsFromAccounts_EA.mq5::EditTerminalIniFile

//+------------------------------------------------------------------+
//| Editing terminal.ini file                                        |
//+------------------------------------------------------------------+
bool EditTerminalIniFile(string ext_name,const int Left=0,const int Top=0,const int Right=1366,const int Bottom=738)
  {
//--- creates and opens files
   string name="terminal.ini";
   ResetLastError();
   int terminal_ini_handle=FileOpen(name,FILE_WRITE|FILE_TXT);
   int terminal_ext_ini__handle=FileOpen(ext_name,FILE_READ|FILE_TXT);
   if(terminal_ini_handle==INVALID_HANDLE)
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
     }
   if(terminal_ext_ini__handle==INVALID_HANDLE)
     {
      PrintFormat("Unable to open file %s, error = %d",ext_name,GetLastError());
     }
   if(terminal_ini_handle==INVALID_HANDLE && terminal_ext_ini__handle==INVALID_HANDLE)
     {
      FileClose(terminal_ext_ini__handle);
      FileClose(terminal_ini_handle);
      return(false);
     }

//--- auxiliary variable
   string str=NULL;
//--- read data
   while(!FileIsEnding(terminal_ext_ini__handle))
     {
      //--- read line
      str=FileReadString(terminal_ext_ini__handle,-1);
      FileWriteString(terminal_ini_handle,str+"\r\n",-1);
      //--- find [Window]
      if(StringFind(str,"[Window]",0)!=-1)
        {
         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Fullscreen=0\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Type=1\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Left="+IntegerToString(Left)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Top="+IntegerToString(Top)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Right="+IntegerToString(Right)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Bottom="+IntegerToString(Bottom)+"\r\n",-1);
        }
     }
//--- close files
   FileClose(terminal_ext_ini__handle);
   FileClose(terminal_ini_handle);
   return(true);
  }

So werden die Dateien "terminal.ini" in den untergeordneten Terminals editiert, und dies zulässt, sie wie in Abb. 9 zu starten. . Dabie kann man die Test-Chart sehen und zugleich die Genauigkeit des Tests in verschiedenen Modus vergleichen. 


5. Der Start der untergeordneten Terminals auf den Test

Bei uns ist im Moment alles für den Start der untergeordneten Terminals im Test-Modus des EAs fertig:

Es bleibt zwei Aufgaben übrig: der gewählte EA in die Sandboxen der untergeordneten Terminals zu kopieren und diese Terminals zu starten.

5.1. Das Kopieren des EAs in die Ordner der untergeordneten Terminals

Das Kopieren, der früher gewählten EA (sein Name ist in der Variable "expert_name" gespeichert) wird in OnInit() durchgeführt:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals
      if(!CopyCommonIni())
         return(INIT_FAILED);
      if(!CopyTerminalIni())
         return(INIT_FAILED);
      //--- copying an expert in the terminal folders
      ResetLastError();
      if(!CopyFileW(expert_name,slaveTerminalDataPath1+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #1 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath2+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #2 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath3+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #3 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath4+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }
      //---
      Sleep(sleeping);

5.2. ShellExecuteW

ShellExecuteW — erledigt die Operation auf der angegebenen Datei.

//--- x64
long ShellExecuteW(
   long hwnd,               //
   string lpOperation,      //
   string lpFile,           //
   string lpParameters,     //
   string lpDirectory,      //
   int nShowCmd             //
   );
//--- x32
int ShellExecuteW(
   int hwnd,                //
   string lpOperation,      //
   string lpFile,           //
   string lpParameters,     //
   string lpDirectory,      //
   int nShowCmd             //
   );

Parameter

hwnd

[in] Handel des Elternfensters, der für die Abbildung des Benutzer-Interfaces und die Fehler-Meldungen verwendet wird. Dieser Wert soll NULL sein, wenn die Operation mit den Fenstern nicht verbunden ist.

lpOperation

[in] Die Zeile mit dem Namen des Befehls, der die Handlung bestimmt, die erfüllt werden soll. Der Satz der zugänglichen Befehle hängt von der konkreten Datei oder dem Ordner ab. In der Regel, es ist die Handlung, die aus dem kontextabhängigen Menü des Objektes erreichbar sind. Normalerweise werden die folgenden Befehle verwendet:

"edit"

Es startet den Editor und öffnet ein Dokument für die Editierung. Wenn lpFile keine Datei des Dokumentes ist, so wird die Funktion nicht erfüllt sein.

"explore"

Öffnet den Ordner, der in  lpFile angegeben ist.

"find"

Leitet die Suche, die im Katalog anfängt, der in lpDirectory angegeben ist.

"open"

Öffnet das Element, das vom Parameter lpFile angegeben ist. Dieses Element kann die Datei oder der Ordner sein.

"print"

Druckt die Datei aus, die  lpFile angegeben ist. Wenn lpFile keine Datei des Dokumentes ist, endet die Funktion mit Fehler.

"NULL"

Es wird der Name des Befehls standardmäßig verwendet, wenn es solche gibt. Wenn es keinen solchen Befehl gibt, so wird der Befehl "open" verwendet. Wenn es kein Befehl verwendet wird, so verwendet das System den ersten Befehl, der in der Liste angegeben ist.

lpFile 

[in] Die Zeile, die eine Datei oder ein Objekte angibt, auf der man ein Kommando ausführen kann. Es wird der vollständige Name (Es schlisst nicht nur den Namen der Datei ein, sondern auch den Pfad zu ihm) übergeben. Beachten Sie, dass das Objekt nicht alle Befehle unterstützen kann. Zum Beispiel, nicht alle Dokumente den Befehl "print" unterstützen. Wenn der relative Pfad für den Parameter lpDirectory verwendet wird, so verwenden Sie den relativen Pfad nicht für lpFile.

lpParameters

[in] Wenn lpFile  weist auf die ausgeführte Datei auf, dieser Parameter ist die Zeile, die die Parameter bestimmt, die der Anwendung übergeben werden. Das Format dieser Zeile klärt sich vom Namen des Befehls, der erfüllt sein muss. Wenn lpFile weist auf die Datei des Dokumentes aus, lpParameters muss NULL sein.

lpDirectory

[in] die Zeile, die den Arbeitskatalog bestimmt. Wenn dieser Wert NULL ist, dann wird der laufende Arbeitskatalog verwendet. Wenn der relative Pfad in lpFile angegeben ist, so verwenden Sie den relativen Pfad nicht für lpDirectory.

nShowCmd

[in] Die Fahnen, die bestimmen, wie die Anwendung bei seiner Eröffnung dargestellt werden soll. Wenn lpFile die Datei des Dokumentes bestimmt, wird die Fahne der entsprechenden Anwendung einfach übergeben. Die verwendeten Fahnen:

//+------------------------------------------------------------------+
//| Enumeration command to start the application                     |
//+------------------------------------------------------------------+
enum EnSWParam
  {
   //+------------------------------------------------------------------+
   //| Displays the window as a minimized window. This value is similar |
   //| to SW_SHOWMINIMIZED, except the window is not activated.         |
   //+------------------------------------------------------------------+
   SW_SHOWMINNOACTIVE=7,
   //+------------------------------------------------------------------+
   //| Activates and displays a window. If the window is minimized or   |
   //| maximized, the system restores it to its original size and       |
   //| position. An application should specify this flag when           |
   //| displaying the window for the first time.                        |
   //+------------------------------------------------------------------+
   SW_SHOWNORMAL=1,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a minimized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMINIMIZED=2,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a maximized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMAXIMIZED=3,
   //+------------------------------------------------------------------+
   //| Hides the window and activates another window.                   |
   //+------------------------------------------------------------------+
   SW_HIDE=0,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it in its current size         |
   //| and position.                                                    |
   //+------------------------------------------------------------------+
   SW_SHOW=5,
  };

Rückgabewert

Wenn die Funktion erfolgreich endet, sie gibt einen größeren Wert, als 32 zurück.

Das Beispiel der Erklärung Win API der Funktion ShellExecuteW:

#import  "shell32.dll"
int  GetLastError();
//+------------------------------------------------------------------+
//| ShellExecute function                                            |
//| https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx
//| Performs an operation on a specified file                        |
//+------------------------------------------------------------------+
//--- x64
long ShellExecuteW(long hwnd,string lpOperation,string lpFile,string lpParameters,string lpDirectory,int nShowCmd);
//--- x32
int ShellExecuteW(int hwnd,string lpOperation,string lpFile,string lpParameters,string lpDirectory,int nShowCmd);
#import
#import "kernel32.dll"

//+------------------------------------------------------------------+
//| Enumeration command to start the application                     |
//+------------------------------------------------------------------+
enum EnSWParam
  {
   //+------------------------------------------------------------------+
   //| Displays the window as a minimized window. This value is similar |
   //| to SW_SHOWMINIMIZED, except the window is not activated.         |
   //+------------------------------------------------------------------+
   SW_SHOWMINNOACTIVE=7,
   //+------------------------------------------------------------------+
   //| Activates and displays a window. If the window is minimized or   |
   //| maximized, the system restores it to its original size and       |
   //| position. An application should specify this flag when           |
   //| displaying the window for the first time.                        |
   //+------------------------------------------------------------------+
   SW_SHOWNORMAL=1,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a minimized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMINIMIZED=2,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a maximized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMAXIMIZED=3,
   //+------------------------------------------------------------------+
   //| Hides the window and activates another window.                   |
   //+------------------------------------------------------------------+
   SW_HIDE=0,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it in its current size         |
   //| and position.                                                    |
   //+------------------------------------------------------------------+
   SW_SHOW=5,
  };

5.3. Der Start der Terminals

Es werden die untergeordneten Terminals aus OnInit() gestartet:

      //---
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_1,slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_2,slaveTerminalDataPath2+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_3,slaveTerminalDataPath3+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_4,slaveTerminalDataPath4+"\\MQL5\\Files\\"+common_file_name);
     }
//---
   return(INIT_SUCCEEDED);
  }

Dabei erwartet der EA zwischen den Starts "sleeping" die Millisekunden. Dieser Parameter standardmäßig ist "sleeping" 9000 gleich (das heißt, 9 Sekunden). Wenn die Fehler der Autorisation der Agenten in den untergeordneten Terminalen auftreten, dann vergrößern Sie diesen Parameter. 

Die Parameter, die in der Funktion Win API übergeben werden (auf dem Beispiel meines untergeordneten Terminals №1) sehen so aus:

LaunchSlaveTerminal("C:\Program Files\MetaTrader 5 1\",
"C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\MQL5\Files\myconfiguration.ini");


6. Die möglichen Fehler

Es kann die Situation entstehen, wenn eines der untergeordneten Terminals gestartet wird, aber der Tester kann nicht zu den Agenten des Testes angeschlossen werden.

Im Tester, im Titel Journale wird eine solche Aufzeichnung erscheinen:

2016.07.15 15:10:48.327 Tester  EURUSD: history data begins from 2014.01.14 00:00
2016.07.15 15:10:49.212 Core 1  agent process started
2016.07.15 15:10:49.717 Core 1  connecting to 127.0.0.1:3002
2016.07.15 15:11:00.771 Core 1  tester agent authorization error
2016.07.15 15:11:01.417 Core 1  connection closed

In den Logs des Agenten sind folgende Aufzeichnungen zu sehen:

2016.07.15 16:08:45.416 Startup MetaTester 5 x64 build 1368 (13 Jul 2016)
2016.07.15 16:08:45.612 Server  MetaTester 5 started on 127.0.0.1:3000
2016.07.15 16:08:45.612 Startup initialization finished
2016.07.15 16:09:36.811 Server  MetaTester 5 stopped
2016.07.15 16:09:38.422 Tester  shutdown tester machine

Für solche Fälle ist es empfehlenswert, die Pause zwischen den Starts der Terminals (die Variable "sleeping") zu verlängern, sowie, alle Ressource-geforderte Anwendungen auszuladen, die zu ihrer Gunst den Kern des Prozessors ergreifen können.


Fazit

Die Aufgabe, der Start des Tests des gewählten EAs direkt in vier Test-Modus, ist erfüllt. Nach dem Start des EAs kann man tatsächlich gleichzeitig beobachten, wie der Test direkt in vier Terminals geht.

Auch war es im Artikel vorgeführt, wie solche Funktionen Win API aufgerufen werden können: