English Русский 中文 Español 日本語 Português
LifeHack für Trader: Der vergleichende Bericht über einige Tests

LifeHack für Trader: Der vergleichende Bericht über einige Tests

MetaTrader 5Beispiele | 22 Dezember 2016, 09:34
604 0
Vladimir Karputov
Vladimir Karputov

Inhalt

 

Einführung

Der abwechselnde Start des EA-Tests auf einigen Symbolen — ist kein anschaulicher Prozess, da man die Testen-Ergebnisse nach jedem Symbol in den abgesonderten Dateien speichern muss und erst danach vergleichen. Ich schlage vor, diese Methode zu ändern und den gleichzeitigen Test des EAs gleich auf einigen Symbolen durchzuführen. In diesem Fall kann man die Test-Ergebnisse in einer Stelle zu sammeln und sie visuell zu vergleichen.

Einige Lösungen wurden schon teilweise in den folgenden Artikeln betrachtet:

Der Arbeitsplan wird es so:

  1. Wir bestimmen den EA, der getestet wird (Win API)
  2. Wir machen die Parsing des EA-Codes und schreiben in ihm den Bibliothek-Aufruf des graphischen Berichtes (Win API, MQL5 und die regulären Ausdrücke)
  3. Wir machen die Parsing in common.ini des Hauptterminales und bereiten die individuellen common.ini für jedes Terminal vor (Win API, MQL5 die regulären Ausdrücke)
  4. Wir kopieren die individuellen common.ini nach Ordern der Terminale (Win API)
  5. Wir kopieren die individuellen common.ini nach Ordern der Terminale (Win API)
  6. Wir machen die Parsing der Berichte der untergeordneten Terminale
  7. Wir reduzieren die Berichte der untergeordneten Terminale in einem allgemeinen Bericht

 

Die notwendigen Aktionen

Vor dem Start des EAs muss man "die Synchronisation" der Haupt- und untergeordneten Terminale durchführen.

  1. Sowohl im Hauptterminal, als auch in den untergeordneten Terminalen soll eines und das gleiche Handelskonto gestartet werden.
  2. In den Einstellungen aller untergeordneten Terminale muss man die Option "Aktivieren dll erlauben" anmachen. Wenn Sie die Terminale mit dem Schlüssel \Portable starten — kommen Sie ins Verzeichnis der Terminals-Installierung (mit Hilfe des Leiters oder eines anderen Dateimanager), starten Sie das Terminal "terminal64.exe" und stellen Sie in den Einstellungen "Aktivieren dll erlauben" ein.
  3. Die Bibliothek "DistributionOfProfits.mqh" muss in allen Verzeichnissen der Daten (dem Verzeichnis der Daten \MQL5\Include\DistributionOfProfits.mqh) der untergeordneten Terminals sein.

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

Da mein Computer vier Kerne hat, kann ich nur vier Agenten des Tests starten. Dies bedeutet, gleichzeitig (oder mit dem kleinen Verzug in etwas Sekunden) kann ich nur vier Terminale starten — eines für jeden Agent. Gerade deshalb wurden in den Eingangsparametern vier Einstellung-Gruppen dargestellt:

inputs

Parameter:

  • folder of the MetaTrader#ххх installation — der Ordner, in dem das Terminal installiert worden ist
  • the tested symbol for the terminal #xxx — das Symbol, auf dem der Strategie-Tester gestartet wird
  • the tested period for the terminal #xxx — die Periode, auf der der Strategie-Tester gestartet wird
  • correct name of the file of the terminal — der Dateiname des Terminals
  • sleeping in milliseconds — die Pause zwischen Starten der untergeordneten Terminals
  • date of beginning testing (only year, month and day) — das Datum des Test-Anfangs
  • dates of end testing (only year, month and day) — das Datum des Test-Endes
  • initial deposit — Deposit
  • leverage — Hebel

Vor dem Start der Hauptalgorithmen muss man zwischen dem Ordner der Installierung der untergeordneten Terminals und ihrem Verzeichnis der Daten im Ordner AppData verbinden. Hier ist das Beispiel eines einfachen Skripts Check_TerminalPaths.mq5:

//+------------------------------------------------------------------+
//|                                          Check_TerminalPaths.mq5 |
//|                        Copyright 2009, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print("TERMINAL_PATH = ",TerminalInfoString(TERMINAL_PATH));
   Print("TERMINAL_DATA_PATH = ",TerminalInfoString(TERMINAL_DATA_PATH));
   Print("TERMINAL_COMMONDATA_PATH = ",TerminalInfoString(TERMINAL_COMMONDATA_PATH));
  }
//+------------------------------------------------------------------+

Dieser Skript zeigt drei Parameter:

  • TERMINAL_PATH — der Ordner, aus dem das Terminal gestartet wurde
  • TERMINAL_DATA_PATH — der Ordner, in dem die Daten des Terminals gespeichert wurden
  • TERMINAL_COMMONDATA_PATH — Der allgemeine Ordner von allen Kundenterminalen, die im PC installiert worden sind

Das Beispiel für 3 Terminals (einer von ihnen wurde mit dem Schlüssel /Portable gestartet):

// Das Terminal wird im Hauptmodus gestartet
TERMINAL_PATH 			= C:\Program Files\MetaTrader 5
TERMINAL_DATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075
TERMINAL_COMMONDATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

// Das Terminal wird im Hauptmodus gestartet
TERMINAL_PATH 			= D:\MetaTrader 5 3
TERMINAL_DATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\0C46DDCEB43080B0EC647E0C66170465
TERMINAL_COMMONDATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

// Das Terminal wird im Modus Portable gestartet
TERMINAL_PATH 			= D:\MetaTrader 5 5
TERMINAL_DATA_PATH 			= D:\MetaTrader 5 5
TERMINAL_COMMONDATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Mehr über den Vergleich der Terminal-Ordner und ihrer Ordner im AppData kann man in den Abschnitten eines meines vorherigen Artikels lesen:

Wir wählen den EA mit Hilfe des Systemdialoges "Datei Öffnen" (die Funktion GetOpenFileNameW):

open file 

Mehr über den Aufruf des systematischen Dialog-Fensters "Datei Öffnen" habe ich schon bereits im Artikel "LifeHack für Trader: ein back-Test ist gut, und vier – ist besser": 4.2. erzählt. Wir wählen den EA mit Hilfe des Systemdialoges "Datei Öffnen"

In dieser Redaktion (die Datei GetOpenFileNameW.mqh, die Version 1.003) wurden Änderungen in der Funktion OpenFileName hinzugefügt:

//+------------------------------------------------------------------+
//| Creates an Open dialog box                                       |
//+------------------------------------------------------------------+
string OpenFileName(const string filter_description="Editable code",
                    const string filter="\0*.mq5\0",
                    const string title="Select source file")
  {
   string path=NULL;
   if(GetOpenFileName(path,filter_description+filter,TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\",title))
      return(path);
   else
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(NULL);
     }
  }

Nun ist es einfacher geworden, den Filter der Dateiensuche einzugeben. Außerdem beachten Sie: dass der Filter jetzt die Dateien in der editbaren Format *.mq5 sucht (im vorherigen Artikel wurde die Suche der kompilierten *ex5-Dateien durchgeführt). 


2. Und wieder über common.ini

Nun betrachten wir die Arbeitsbeschreibung der Funktion CopyCommonIni() der Datei Compare multiple tests.mq5.

Der Start der untergeordneten Terminals wird mit der Eingabe der selbst konfigurierten Datei durchgeführt. Die untergeordneten Terminals haben wir vier, und das bedeutet, die *.ini Dateien werden auch vier sein: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini, myconfiguration4.ini. Die Datei myconfigurationХ.ini wird basierend der Datei common.ini des Terminals erstellt, aus dem unser EA gestartet wird. Der Weg zur Datei common.ini:

TERMINAL_DATA_PATH\config\common.ini

Der Arbeitsalgorithmus der Erstellung und Editierung der Dateien myconfiguration.ini sieht so aus:

  • common.ini kopiern wir in den Ordner TERMINAL_COMMONDATA_PATH\Files\original.ini (WinAPI CopyFileW)
  • In der Datei original.ini suchen wir den Abschnitt [Common] (MQL5 + reguläre Ausdrücke).

    Auf dem Beispiel meines Hauptterminales (in dieses Terminal wurde der Eingang in mql5.communiyty nicht durchgeführt) sieht dieser Abschnitt so aus:

    [Common]
    Login=5116256
    ProxyEnable=0
    ProxyType=0
    ProxyAddress=
    ProxyAuth=
    CertInstall=0
    NewsEnable=0
    NewsLanguages=
    
  • Wir erstellen vier Dateien: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini und myconfiguration4.ini (MQL5)
  • Wir editieren diese vier Dateien (Wir kopieren darin den allgemeinen Teil [Common] und die individuellen Teile [Tester]) (MQL5)

2.1. common.ini -> original.ini

Es ist wahrscheinlich der leichteste Code: ich bekomme in den Variablen die Wege zu den Ordnern "Data Folder" und "Commomm Data Folder", ich initialisiere die Variable vom Wert "original.ini"

   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);    // path to Data Folder
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);// path to Commomm Data Folder
   string original_ini="original.ini";
   string arr_common[];
//---
   string full_name_common_ini=terminal_data_path+"\\config\\common.ini";     // full path to the common.ini file                                                        
   string full_name_original_ini=common_data_path+"\\Files\\"+original_ini;   // full path to the original.ini file  
//--- common.ini -> original.ini
   if(!CopyFileW(full_name_common_ini,full_name_original_ini,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }

Mit Hilfe des Win API der Funktion CopyFileW kopiere ich die konfigurierte Datei "common.ini" in die Datei "original.ini".

2.2. Die Suche des Teiles [Common] mit Hilfe der regulären Ausdrücke

Für die Suche und das Kopieren der Abteilung [Common] verwenden wir die regulären Ausdrücke. Vor uns steht eine besondere Aufgabe, denn die Datei common.ini besteht aus kurzen Zeilen, und am Ende der Zeilen werden immer die Symbole des Zeilenendes (die unsichtbaren Symbole) gestellt. Man kann mit zwei Wegen gehen:

Das Ablesen pro ZeileDas Ablesen der ganzen Datei in eine Variable
  • Nach einer Zeile abzulesen und das Zusammenfallen mit "[Common]" zu suchen, (es ist nicht nötig, direkt und genau zu suchen: man kann in die Suche etwas Ähnliches wie eine Schablone "[Com" einige Symbole "]") eingeben
    • Beim Befunden, ins Array der gefundenen Zeilen zu schreiben
    • Das folgende Zusammenfallen mit dem "[" suchen
      • Beim Befunden aufhören, ins Array zu schreiben, da im Array in diesen Moment schon die ganze Abteilung "[Common]" sein wird
  • Alle Zeilen in eine Zeile-Variable abzulesen
  • Es muss die Suche nach der Schablone "[Common]" einiger Symbole "]" durchgeführt werden, (hier ist es auch nicht nötig, direkt und genau zu suchen: man kann etwas Ähnliches der Schablone "[Com" einige Symbole "]") suchen
  • Beim Befunden muss das Zusammenfallen in die Zeile-Variable schreiben

Die Test-Datei "test_original.ini":

[Charts]
ProfileLast=Default
MaxBars=100000
PrintColor=0
SaveDeleted=0
TradeLevels=1
TradeLevelsDrag=0
ObsoleteLasttime=1475473485
[Common]
Login=1783501
ProxyEnable=0
ProxyType=0
ProxyAddress=
ProxyAuth=
CertInstall=0
NewsEnable=0
[Tester]
Expert=test         
Symbol=EURUSD          
Period=H1             
Deposit=10000     
Model=4              
Optimization=0         
FromDate=2016.01.22    
ToDate=2016.06.06      
Report=TesterReport    
ReplaceReport=1       
UseLocal=1              
Port=3000            
Visual=0              
ShutdownTerminal=0

Auf der Datei "test_original.ini" kann man üben, die regulären Ausdrücke mit Hilfe des Skriptes "Receiving lines.mq5" anzuwenden. In den Einstellungen des Skriptes kann man zwei Arbeitsmoduse auswählen:

  • Das Ablesen pro Zeile und die Suche in jeder Zeile 
  • oder das Ablesen der ganzen Datei in eine Variable.

einige Beispiele des Vergleiches in der Arbeit dieser zwei Methoden:

Das Ablesen pro ZeileDas Ablesen der ganzen Datei in eine Variable
Anfrage: "Prox(.*)0"
- Suche nach dem Wort "Prox"
- Jedes Symbol, außer Newline oder einem anderen Separator der Unicode-Zeile, die sich treffende Null oder mehr Mal (geizig) "(. *)"
- die Suche muss zu Ende gehen, wenn die Zahl "0" gefunden wird
12: 0: ProxyEnable=0,
13: 0: ProxyType=0,
: 0: ProxyEnable=0ProxyType=0ProxyAddress=ProxyAuth=CertInstall=0NewsEnable=0[Tester]Expert=test         Symbol=EURUSD          Period=H1             Deposit=10000     Model=4              Optimization=0         FromDate=2016.01.22    ToDate=2016.06.06      Report=TesterReport    ReplaceReport=1       UseLocal=1              Port=3000            Visual=0              ShutdownTerminal=0, 
Wie Sie sehen können, es wurden hier zwei Ergebnisse gegebenUnd hier ist es in der Ausgabe nur ein Ergebnis, aber darin gibt es vieles restliches (es ist die geizige Anfrage)
  
Anfrage: "Prox(.*?)0"
- Die Suche nach dem Wort "Prox"
- Jedes Symbol, außer Newline oder einem anderen Separator der Unicode-Zeile, die sich treffende Null oder mehr Mal (nicht geizig) "(.*?)"
- die Suche muss zu Ende gehen, wenn die Zahl "0" gefunden wird "0"
12: 0: ProxyEnable=0,
13: 0: ProxyType=0,
: 0: ProxyEnable=0, 1: ProxyType=0, 2: ProxyAddress=ProxyAuth=CertInstall=0,
Hier sind wieder zwei ErgebnisseUnd in diesem Fall wurden drei Ergebnisse erhalten, dabei das dritte — gar nicht dieses, welches ich bekommen wollte.

Welche Methode soll man für die Ausgliederung eines ganzen Blocks "[Common]" auswählen — das Ablesen pro Zeile oder das Ablesen in einem Variabel? Ich habe das Ablesen pro Zeile und einen solchen Algorithmus ausgewählt:

  1. Die Suche der Zeile "[Common]" (MQL5);
  2. Nach dem Befunden — schreiben der gefundenen Zeile ins Array;
  3. Weiter setzen wir fort, die Zeile ins Array zu schreiben, bis der reguläre Ausdruck das Symbol "[" nicht findet.

Das Beispiel dieser Methode wurde im Skript "Receiving lines v.2.mq5" realisiert:

//+------------------------------------------------------------------+
//|                                          Receiving lines v.2.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.000"
#property description "Singling the block \"[Common]\""
#property script_show_inputs
#include <RegularExpressions\Regex.mqh>
//---
input string   file_name="test_original.ini";         // file name
input string   str_format="(\\[)(.*?)(\\])";
//---
int            m_handel;
bool           m_found_Common=false;                  // after finding of the word "[Common]" - the flag will be true
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   string arr_text[]; // array for rezult
//---
   Print("format: ",str_format);
   m_handel=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_TXT);
   if(m_handel==INVALID_HANDLE)
     {
      Print("Operation FileOpen failed, error ",GetLastError());
      return;
     }
   Regex *rgx=new Regex(str_format);
   while(!FileIsEnding(m_handel))
     {
      string str=FileReadString(m_handel);
      if(str=="[Common]")
        {
         m_found_Common=true;
         int size=ArraySize(arr_text);
         ArrayResize(arr_text,size+1,10);
         arr_text[size]=str;
         continue;                        // goto while...
        }
      if(m_found_Common)
        {
         MatchCollection *matches=rgx.Matches(str);
         int count=matches.Count();
         if(count>0)
           {
            if(count>1)
              {
               Print("Alarm! matches.Count()==",count);
               return;
              }
            delete matches;
            break;                        // goto FileClose...
           }
         else
           {
            delete matches;               // if no match is found
           }
         int size=ArraySize(arr_text);
         ArrayResize(arr_text,size+1,10);
         arr_text[size]=str;
        }
     }
   FileClose(m_handel);
   delete rgx;
   Regex::ClearCache();

//--- testing
   int size=ArraySize(arr_text);
   for(int i=0;i<size;i++)
     {
      Print(arr_text[i]);
     }
  }
//+------------------------------------------------------------------+

Das Ergebnis der Arbeit des Skripts:

2016.10.05 06:58:09.276 Receiving lines v.2 (EURUSD,M1) format: (\[)(.*?)(\])
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) [Common]
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) Login=1783501
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyEnable=0
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyType=0
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyAddress=
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyAuth=
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) CertInstall=0
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) NewsEnable=0

Wie Sie sehen können, der Skript hat aus der Datei "test_original.ini" den Block der Parameter "[Common]" genau ausgewählt. Der Algorithmus aus dem Skript " Receiving lines v.2.mq5 " habe ich praktisch ohne Veränderungen in der Funktion SearchBlock() verwendet. Die Funktion SearchBlock() schreibt beim erfolgreichen Befunden des Blocks der Parameter "[Common]" diesen Block ins dienstliche Array arr_common[].

2.3. Wir erstellen vier Dateien: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini und myconfiguration4.ini

Alle vier Dateien entstehen vom konsequenten Aufruf des gegebenen Codes (Achte Sie die Flagge, die bei der Eröffnung der Dateien verwendet werden):

//+------------------------------------------------------------------+
//| Open new File                                                    |
//+------------------------------------------------------------------+
bool IniFileOpen(const string name_file,int  &handle)
  {
   handle=FileOpen(name_file,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      Print("Operation FileOpen file ",name_file," failed, error ",GetLastError());
      return(false);
     }
//---
   return(true);
  }

2.4. Die Editierung der ini-Dateien (Wir kopieren darin den allgemeinen Teil [Common] und die individuellen Teile [Tester])

Früher im dienstlichen Array arr_common[] wurde der Block der Parameter [Common] geschrieben. Nun wird dieses Array in allen vier Dateien geschrieben:

//--- recording block "[Common]"
   int arr_common_size=ArraySize(arr_common);
   for(int i=0;i<arr_common_size;i++)
     {
      FileWrite(handle1,arr_common[i]);
      FileWrite(handle2,arr_common[i]);
      FileWrite(handle3,arr_common[i]);
      FileWrite(handle4,arr_common[i]);
     }
//--- recording block "[Tester]"
   string expert_short_name="D0E820_test";
   WriteBlockTester(handle1,expert_short_name,ExtTerminal1Symbol,ExtTerminal1Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3000);
   WriteBlockTester(handle2,expert_short_name,ExtTerminal2Symbol,ExtTerminal2Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3001);
   WriteBlockTester(handle3,expert_short_name,ExtTerminal3Symbol,ExtTerminal3Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3002);
   WriteBlockTester(handle4,expert_short_name,ExtTerminal4Symbol,ExtTerminal4Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3003);
//--- close the files 
   FileClose(handle1);
   FileClose(handle2);
   FileClose(handle3);
   FileClose(handle4);

Weiter geht die Bildung des Blockes der Parameter [Tester]: für jedes Terminal werden die einzigartigen Parameter (das Symbol und Timeframe) sowie die allgemeinen Parameter (das Anfangsdatum und das Ende des Tests, das Anfangsdeposit, der Hebel) vorbereitet. 

Die erstellten Dateien myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini und myconfiguration4.ini bleiben im allgemeinen Ordner der Daten (TERMINAL_COMMONDATA_PATH\Files\). Die Handel dieser Dateien schließen wir zu.


3. Die Parsing und die Editierung der mq5-Datei des gewählten EAs

Die Aufgaben, die gelöst werden müssen:

3.1. Geheimnis №3 

Warum ist das Geheimnis Nummer drei? Das liegt daran, weil  Geheimnis №1 und  Geheimnis №2 früher im LifeHack für Trader: ein back-Test ist gut, und vier – ist besser gezeigt wurden.

Wir betrachten eine nächste Situation: das Terminal wird aus der Kommandozeile gestartet, und dabei wird die Konfigurationsdatei-ini eingegeben. In der ini-Datei bezeichnen wir den Namen des EAs, der im Tester beim Start des Terminals gestartet wird. In diesem Fall werden wir meinen, dass wir den Namen des EAs eingeben, der noch nicht kompiliert wurde.

Geheimnis №3.

So muss der Name des EAs muss ohne Erweiterung geschrieben werden. In Bezug auf diesen Artikel wird dies so aussehen:

NewsEnable=0
[Tester]
Expert=D0E820_test
Symbol=GBPAUD

das Terminal beim Start wird zuerst die KOMPILIERTE Datei suchen (in Bezug auf den Artikel wird das Terminal Expert=D0E820_test.ex5 suchen). Und nur falls das Terminal die kompilierte Datei nicht findet, wird das Terminal die Kompilation des in der in-Datei eingegebenen EAs beginnen.

Ausgehend davon, bevor die Editierung des gewählten EAs beginnt, muss man durch die Ordner der untergeordneten Terminals arbeiten und, die kompilierten Versionen des gewählten EAs entfernen (konkret muss man in unserem Fall die Dateien D0E820_test.ex5 entfernen). Wir werden mit Hilfe Win API die Funktionen DeleteFileW entfernen:

      if(!CopyCommonIni())
         return(INIT_FAILED);
      //--- delete all files: expert_short_name+".ex5"
      ResetLastError();
      string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);   // path to Commomm Data Folder
      //---
      string edited_expert=common_data_path+"\\Files\\"+expert_short_name+".mq5";
      //--- delete expert_short_name+".ex5" files
      string compiled_expert=expert_short_name+".ex5";
      DeleteFileW(slaveTerminalDataPath1+"\\MQL5\\Experts\\"+compiled_expert);
      DeleteFileW(slaveTerminalDataPath2+"\\MQL5\\Experts\\"+compiled_expert);
      DeleteFileW(slaveTerminalDataPath3+"\\MQL5\\Experts\\"+compiled_expert);
      DeleteFileW(slaveTerminalDataPath4+"\\MQL5\\Experts\\"+compiled_expert);

      //--- delete expert_short_name+".set" files

Und jetzt muss man unbedingt die Dateien *.set entfernen - es handelt sich darum, dass die Tester mit den Parametern sowieso gestartet werden, wenn Sie im gewählten EA einige Eingangsparameter ändern, die auf dem vorhergehenden Start waren. Deshalb löschen wir die *.set-Dateien:

      //--- delete expert_short_name+".set" files
      string set_files=expert_short_name+".set";
      DeleteFileW(slaveTerminalDataPath1+"\\Tester\\"+set_files);
      DeleteFileW(slaveTerminalDataPath2+"\\Tester\\"+set_files);
      DeleteFileW(slaveTerminalDataPath3+"\\Tester\\"+set_files);
      DeleteFileW(slaveTerminalDataPath4+"\\Tester\\"+set_files);

      //--- delete expert_short_name+".htm" files (reports)

Auch werden wir die Datei des Tester-Berichtes aus den Ordern der untergeordneten Terminals entfernen

      DeleteFileW(slaveTerminalDataPath4+"\\MQL5\\Experts\\"+compiled_expert);
      //--- delete expert_short_name+".htm" files (reports)
      string file_report=expert_short_name+".htm";
      DeleteFileW(slaveTerminalDataPath1+"\\"+file_report);
      DeleteFileW(slaveTerminalDataPath2+"\\"+file_report);
      DeleteFileW(slaveTerminalDataPath3+"\\"+file_report);
      DeleteFileW(slaveTerminalDataPath4+"\\"+file_report);

      //--- сopying an expert in the TERMINAL_COMMONDATA_PATH\Files folder
      if(!CopyFileW(expert_full_name,edited_expert,false))

Wofür entfernen wir die Dateien der Berichte? Es ist für die Verfolgung des Momentes notwendig, wenn die Dateien des Berichtes in allen untergeordneten Terminalen erscheinen werden — dann kann man die Parsing dieser Dateien für die Bildung der Vergleiches-Seite der erhaltenden Test-Ergebnisse nach einigen Symbolen machen.

Und nur nach der Entfernung der kompilierten Dateien kann man die gewählte Datei des EAs in den Ordner TERMINAL_COMMONDATA_PATH für die nachfolgende Arbeit mit der Datei durch die Mitteln MQL5 kopieren:

      DeleteFileW(slaveTerminalDataPath4+"\\"+file_report);
      //--- сopying an expert in the TERMINAL_COMMONDATA_PATH\Files folder
      if(!CopyFileW(expert_full_name,edited_expert,false))
        {
         PrintFormat("Failed CopyFileW expert_full_name with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      //--- parsing advisor file

3.2. Wir bauen "#include" ein

Die Beschreibung der Funktion Compare multiple tests.mq5::ParsingEA().

Für den allgemeinen Fall muss man bestimmen, ob es schon in der Datei des EAs die Zeile "#include <DistributionOfProfits.mqh>" gibt. Wenn es keine solche Zeile gibt, so muss man sie in den EA einbauen. Dabei muss man verstehen, dass viel Varianten existieren können:

VariantenPasst/Passt nicht
"#include <DistributionOfProfits.mqh>"Passt (die ideale Variante)
"#include<DistributionOfProfits.mqh>"Passt (in dieser Variante nach dem Wort "#include" steht kein Leerzeichen, sondern das Zeichen der Tabulation)
"#include <DistributionOfProfits.mqh>"Passt (in dieser Variante vor dem Wort "#include" steht das Zeichen der Tabulation)
"//#include <DistributionOfProfits.mqh>"passt nicht (das sind einfach Kommentaren)

Auch kann eine solche Variante sein, wenn nach "#include" ein Leerzeichen ist, sondern das Zeichen der Tabulation oder die einige Leerzeichen. Darauffolgend wurde ein regulärer Ausdruck erstellt: 

"(\\s+?#include|^#include)(.*?)(<DistributionOfProfits.mqh)"

So wird dieser Ausdruck entziffert (\\s+?#include|^#include): (einer oder mehr Leerzeichen, nicht geizig, weiter "#include") oder (die Zeile beginnt mit dem Wort "#include"). Für die Suche der Zeile ist die Funktion NumberRegulars() verantwortlich. Hier wird direkt die Variable "name_Object_CDistributionOfProfits" eingefügt — in der speichern wir den Namen des Objektes CDistributionOfProfits. Es kann später nützlich sein, wenn man die komplizierte Suche machen muss.

//+------------------------------------------------------------------+
//| Insert #include <DistributionOfProfits.mqh>                      |
//| Insert call graphical analysis of trade                          |
//+------------------------------------------------------------------+
bool ParsingEA()
  {
//--- find #include <DistributionOfProfits.mqh>
   int number=0;
   string name_Object_CDistributionOfProfits="ExtDistribution";   // CDistributionOfProfits object name
   string expressions="(\\s+?#include|^#include)(.*?)(<DistributionOfProfits.mqh)";
   if(!NumberRegulars(expert_short_name+".mq5",expressions,number))
      return(false);
   if(number==0) // a regular expression is not found
     {
      //--- add #include <DistributionOfProfits.mqh> 
      string array[];
      ArrayResize(array,2);
      array[0]="#include <DistributionOfProfits.mqh>";
      array[1]="CDistributionOfProfits "+name_Object_CDistributionOfProfits+";";
      if(!InsertLine(expert_short_name+".mq5",0,array))
         return(false);
      Print("Line \"#include\" is insert");

Wenn die Zeile nicht gefunden wurde, heißt es, dass sie in den EA (die Funktion InsertLine()) eingefügt werden muss. Das Prinzip der Arbeit sieht so aus: wir lesen pro Zeile die Datei des EAs im vorübergehenden Array. Wenn die Nummer der Zeile mit der aufgegeben Nummer übereinstimmt ("position"), dann wird ins Array das nötige Stück des Codes eingefügt (der Code wird aus dem Array "text" genommen). Nach dem vollen Durchlesen wird die Datei des EAs entfernt, dann entsteht direkt die neue Datei mit solchem Namen. In ihm wird die Information aus dem vorübergehenden Array geschrieben:

//+------------------------------------------------------------------+
//| Insert a line in a file                                          |
//+------------------------------------------------------------------+
bool InsertLine(const string name_file,const uint position,string &array_text[])
  {
   int handle;
   int size_arr=ArraySize(array_text);
//---
   handle=FileOpen(name_file,FILE_READ|FILE_ANSI|FILE_TXT|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      Print("Operation FileOpen file ",name_file," failed, error ",GetLastError());
      return(false);
     }
   int line=0;
   string arr_temp[];
   ArrayResize(arr_temp,0,1000);
   while(!FileIsEnding(handle))
     {
      string str_text=FileReadString(handle,-1);
      if(line==position)
        {
         for(int i=0;i<size_arr;i++)
           {
            int size=ArraySize(arr_temp);
            ArrayResize(arr_temp,size+1,1000);
            arr_temp[size]=array_text[i];
           }
        }
      int size=ArraySize(arr_temp);
      ArrayResize(arr_temp,size+1,1000);
      arr_temp[size]=str_text;
      line++;
     }
   FileClose(handle);
   FileDelete(name_file,FILE_COMMON);
//---
   handle=FileOpen(name_file,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      Print("Operation FileOpen file ",name_file," failed, error ",GetLastError());
      return(false);
     }
   int size=ArraySize(arr_temp);
   for(int i=0;i<size;i++)
     {
      FileWrite(handle,arr_temp[i]);
     }
   FileClose(handle);
//---
   return(true);
  }

3.3. Wir bauen "OnTester()" ein

Jetzt wird die Aufgabe viel komplizierter sein, da das Wort "OnTester" sich im Code des Programms in den verschiedensten Variationen treffen kann Zum Beispiel, es kann im Code ganz Abwesend sein — es ist wahrscheinlich die einfachste Variante. Man kann die klassische Variante sehen:

double OnTester()
  {

Es ist auch nicht so sehr kompliziert. Aber die Rebellen unter den Herstellern gibt es genug, deshalb können wir auf einen solchen Stil des Programmierens stoßen:

double OnTester() {

Und, jetzt vielleicht, eine der komplizierten Varianten:

/*
//+-------------------------------+
//|                               |
//+-------------------------------+
double OnTester()
  {
...
  }
...
*/

Also, um sich zu klären, ob die Erklärung der Funktion OnTetster im Code vorkommt, werden wir einen solchen regulären Ausdruck verwenden:

"(\\s+?double|^double)(.+?)(OnTester\\(\\))(.*)"
"(\\s+?double"
 \\s Leerzeichen, \\s+ das Leerzeichen, das mindestens einmal vorkommt, \\s+? das Leerzeichen, das mindestens einmal vorkommt, der ungeistige Operator, \\s+?double das Leerzeichen, das mindestens einmal vorkommt, der ungeistige Operator, und das Wort "double".
"|"
 | oder
     "^double)" Die Zeile beginnt mit dem Wort double
"(.+?)"
 . Jedes Symbol, außer Newline oder einem anderen Separator der Unicode-Zeile, .+ Jedes Symbol, außer Newline oder einem anderen Separator der Unicode-Zeile, die sich treffende einmal oder mehrmals, .+? Jedes Symbol, außer Newline oder einem anderen Separator der Unicode-Zeile, die sich treffende einmal oder mehr als einmal, nicht geizig
"(OnTester\\(\\))"
 OnTester\\(\\) das Wort OnTester()
"(.*)"
 . Jedes Symbol, außer Newline oder einem anderen Separator der Unicode-Zeile, .* Jedes Symbol, außer Newline oder einem anderen Separator der Unicode-Zeile, die sich treffende Null oder mehrmals

Für den einfachsten Fall, wenn die regulären Ausdrücke in der Suche Null zurückgeben, fügen wir den Aufruf der Funktion OnTester() ein:

//---
   expressions="(\\s+?double|^double)(.+?)(OnTester\\(\\))(.*)";
   if(!NumberRegulars(expert_short_name+".mq5",expressions,number))
      return(false);
   if(number==0) // a regular expression is not found
     {
      //--- add function OnTester  
      if(!InsertLine(expert_short_name+".mq5",2,
         "double OnTester()"+
         "  {"+
         "   double ret=0.0;"+
         "   ExtDistribution.AnalysisTradingHistory(0);"+
         "   ExtDistribution.ShowDistributionOfProfits();"+
         "   return(ret);"+
         "  }"))
         return(false);
      Print("Line \"OnTester\" is insert");
     }

Schließlich, wenn es im Code weder "#include <distributionofprofits.mqh>" noch die Funktion "OnTester()" gäbe, die Anfangsdatei wird nach dieser Art (auf dem Beispiel, wenn die Datei MACD Sample.mq5 gewählt wurde) aussehen:

#include <DistributionOfProfits.mqh>
CDistributionOfProfits ExtDistribution;
double OnTester()
  {
   double ret=0.0;
   ExtDistribution.AnalysisTradingHistory(0);
   ExtDistribution.ShowDistributionOfProfits();
   return(ret);
  }
//+------------------------------------------------------------------+
//|                                                  MACD Sample.mq5 |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2009-2016, MetaQuotes Software Corp."
#property link        "https://www.mql5.com"
#property version     "5.50"

Dieser Code sieht nicht sehr ästhetisch aus, aber nichtsdestoweniger erfüllt er die Aufgabe. In Punkten. 3.1 und 3.2 wurden die einfachen Fälle (leichen) betrachtet — wenn es im Code des EAs nicht weder der Erklärung der Bibliothek der graphischen Analyse, noch der Funktion OnTrade() gäbe. Weiter betrachten wir die komplizierteren Fälle — wenn es im Code auch die Erklärung der Bibliothek der graphischen Analyse, und\oder die Funktion OnTester() gibt. 

3.4. Ein komplizierter Fall: im Kode gibt es schon DistributionOfProfits.mqh, und\oder OnTester()

Die komplizierte Suche wird in der Funktion AdvancedSearch() durchgeführt:

//+------------------------------------------------------------------+
//| Advanced Search                                                  |
//|  only_ontester=true                                              |
//|   - search only function OnTester()                              |
//|  only_ontester=false                                             |
//|   - search #include <DistributionOfProfits.mqh>                  |
//|     and function OnTester()                                      |
//+------------------------------------------------------------------+
bool AdvancedSearch(const string name_file,const string name_object,const bool only_ontester)

Parameter:

  • name_file — der Dateiname des EAs
  • name_object — der Objektname der Klasse  CDistributionOfProfits
  • only_ontester — die Flagge der Suche, bei only_ontester=true werden wir nur die Funktion OnTester() suchen.

Direkt am Anfang wird die ganze Datei ins vorübergehende Array abgelesen

string arr_temp[];

— so wird es bequemer bei der Arbeit.

Dann werden konsequent einige dienstliche Codes aufgerufen:

RemovalMultiLineComments() — in diesem Code aus dem Array werden alle vielzeilige Kommentare entfernt;

RemovalComments() — hier werden einzeilige Kommentaren entfernt;

DeleteZeroLine() — aus diesem Array werden alle Zeilen mit der Nulllänge entfernt.

Wenn der Eingangsparameter only_ontester==false ist, dann starten wir die Suche der Zeile "#include <DistributionOfProfits.mqh> " — dafür ist die Funktion FindInclude() verantwortlich:

Die Funktion FindInclude() sucht den Eingang der Zeile "#include <DistributionOfProfits.mqh>" und speichert die Zeile-Nummer in der Variable "function_position" (Ich erinnere daran, was schon vorherig im Punkt 3.1 gab. Wir bauen "#include ein", wir haben mit Hilfe der regulären Ausdrücke schon bestimmt, dass es im Code die Zeile "#include <distributionofprofits.mqh>") garantiert gibt. Weiter wird es versucht, die Zeile "CDistributionOfProfits" zu finden. Wenn eine solche Zeile gefunden ist, so bekommen wir von ihr den Variabel-Namen für die Klasse "CDistributionOfProfits". Wenn eine solche Zeile nicht gefunden ist, so muss man sie in die Position einfügen, direkt nach "function_position".

Wenn der Eingangsparameter only_ontester==false ist, dann starten wir die Suche der Funktion Ontester(). Nach dem Befunden der Funktion, suchen wir in ihr die Zeilen der Aufrufe zur Bibliothek der graphischen Analyse — dafür ist die Funktion FindFunctionOnTester() verantwortlich.


4. Das Kopieren des Experten in die Ordner der untergeordneten Terminals

Das Kopieren des Experten wird in OnInit() durchgeführt:

      //--- parsing advisor file
      if(!ParsingEA())
         return(INIT_FAILED);

      //--- сopying an expert in the terminal folders
      ResetLastError();
      if(!CopyFileW(edited_expert,slaveTerminalDataPath1+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false))
        {
         PrintFormat("Failed CopyFileW #1 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

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

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

      if(!CopyFileW(edited_expert,slaveTerminalDataPath4+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false))
        {
         PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

 

5. Der Start der untergeordneten Terminals

Vor dem Start der untergeordneten Terminals prüfen Sie bitte das Folgende: das Handelskonto im Hauptterminal, auf dem unser EA gestartet wird, soll in allen untergeordneten Terminalen sein. Auch muss man in allen untergeordneten Terminalen dll erlauben:


 

Wenn das nicht gemacht wird, kann das untergeordnete Terminal den EA nicht starten (ich erinnere, in unserem EA werden die Aufrufe Win API sehr aktiv verwendet), und im Tester, im Titel "Zeitschrift" wird ungefähr eine solche Benachrichtigung vom Fehler auftauchen:

2016.10.13 11:28:57     Core 1  2016.02.03 00:00:00   DLL loading is not allowed

Mehr über die System-Funktion ShellExecuteW:  ShellExecuteW. Zwischen den Starts der Terminals wird eine Pause gemacht, und den unmittelbaren Start leitet die Funktion "LaunchSlaveTerminal". 

      if(!CopyFileW(edited_expert,slaveTerminalDataPath4+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false))
        {
         PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }
      //--- launching Slave Terminals
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_1,common_data_path+"\\Files\\myconfiguration1.ini");
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_2,common_data_path+"\\Files\\myconfiguration2.ini");
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_3,common_data_path+"\\Files\\myconfiguration3.ini");
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_4,common_data_path+"\\Files\\myconfiguration4.ini");
     }
//---
   return(INIT_SUCCEEDED);
  }

 

6. Der vergleichende Bericht

Wir bemühten uns für die Parsing des Codes des gewählten EAs nicht umsonst: wir führten in den Code des gewählten EAs den Bibliothek-Aufruf der graphischen Analyse der Positionen im Schnitt der Eröffnungszeit der Positionen ein (diese Bibliothek wurde im Artikel Lifehack für den Händler: "Stille" Optimierung oder die optische Auswertung des Handels" beschrieben). Dank dem eingeführten Code erstellte jeder EA im untergeordneten Terminal nach Abschluss des Tests und automatisch öffnet eine solche html-Seite:

scheme

Früher schrieben wir in der konfigurierten ini-Datei, im Block [Tester], einen solchen Parameter "Report":

[Tester]
Expert=D0E820_test
Symbol=GBPAUD
Period=PERIOD_H1
Deposit=100000
Leverage=1:100
Model=0
ExecutionMode=0
FromDate=2016.10.03
ToDate=2016.10.15
ForwardMode=0
Report=D0E820_test
ReplaceReport=1
Port=3000
ShutdownTerminal=0

Dieser Dateiname (D0E820_test.htm), in dem das Terminal den Bericht nach dem Test speichern wird. Aus diesem Bericht (für jedes untergeordnete Terminal) müssen wir solche Daten nehmen: der Name des Symbols und der Periode, auf den der EA getestet wurde, die Kennziffern aus dem Block "Backtest" und die Grafik der Bilanz. Aus allen untergeordneten Terminalen wird ein solcher vergleichende Bericht gebildet:

report

Ich erinnere, dass die untergeordneten Terminals die Berichte des Tests (in diesem Fall im Format htm) in die Wurzel ihrer Daten-Verzeichnisse speichern. Das bedeutet, dass unser EA die untergeordneten Terminals starten muss, und dann, in diesen Verzeichnissen die Berichte-Dateien des Tests periodisch suchen. Nachdem alle vier Berichte gefunden werden, kann man die Bildung des allgemeinen vergleichenden Berichtes beginnen. 

Zuerst fügen wir die Flagge "find_report" ein, die dem EA die Suche nach Berichte-Dateien erlauben wird:

string         slaveTerminalDataPath4=NULL;                                // the path to the Data Folder of the terminal #4
//---
string         arr_path[][2];
bool           find_report=false;
//+------------------------------------------------------------------+
//| Enumeration command to start the application                     |
//+------------------------------------------------------------------+
enum EnSWParam

fügen wir die Funktion OnTimer() hinzu:

int OnInit()
  {
//--- create timer
   EventSetTimer(9);

  
   ArrayFree(arr_path);
   find_report=false;                                                      // true - flag allows the search reports
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   if(!find_report)
      return;
  }

In OnTimer() werden wir die Datei "expert_short_name"+".htm" suchen. Die Suche wird gleichartig — nur in der Wurzel des Verzeichnisses der Daten jedes der untergeordneten Terminals. Diese Aufgabe wird die Funktion ListingFilesDirectory.mqh::FindFile() erledigen.

Da die Suche außerhalb "des Sandboxes" durchgeführt wird, werden wir Win API der Funktion FindFirstFileW verwenden. Mehr über FindFirstFileW siehe. im vorhergehenden Artikel: 

Und in diesem Code vergleichen wir den erhaltenden Namen der Datei, und wenn es mit dem gegebenen Code übereinstimmen, so geben wir true zurück, vorläufig den Handel der Suche geschlossen zu haben: 

//+------------------------------------------------------------------+
//| Find file                                                        |
//+------------------------------------------------------------------+
bool FindFile(const string path,const string name)
  {
//---
   WIN32_FIND_DATA ffd;
   long            hFirstFind_0;

   ArrayInitialize(ffd.cFileName,0);
   ArrayInitialize(ffd.cAlternateFileName,0);
//--- stage Search №0.
   string filter_0=path+"\\*.*"; // filter_0==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\*.*

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

//--- list all the files in the directory with some info about them
   bool rezult=0;
   do
     {
      string name_0="";
      for(int i=0;i<MAX_PATH;i++)
        {
         name_0+=ShortToString(ffd.cFileName[i]);
        }
      if(name_0==name)
        {
         WinAPI_FindClose(hFirstFind_0);
         return(true);
        }

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

Der EA prüft die Tatsache des Vorhandenseins aller vier Dateien der Berichte in den Ordern der untergeordneten Terminals: es wird ein Merkmal dafür, dass alle untergeordneten Terminals den Test beendet haben.

Jetzt muss man diese Informationen bearbeiten. Alle vier Dateien der Berichte und ihre vier graphischen Dateien — die Graphiken der Bilanz — werden in den Sandbox TERMINAL_COMMONDATA_PATH\Files kopiert:

//--- reports -> TERMINAL_COMMONDATA_PATH\Files\
   string path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);

   if(!CopyFileW(slaveTerminalDataPath1+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_1"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath1+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_1"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

   if(!CopyFileW(slaveTerminalDataPath2+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_2"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath2+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_2"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

   if(!CopyFileW(slaveTerminalDataPath3+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_3"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath3+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_3"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

   if(!CopyFileW(slaveTerminalDataPath4+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_4"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath4+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_4"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

Aber in den erhaltenden Dateien der Berichte gibt es viele überschüssige Informationen, und dies erschwert wesentlich die Anwendung der regulären Ausdrücke. Deshalb in der Funktion Compare multiple tests.mq5: ParsingReportToArray werden einige Manipulationen durchgeführt, aufgrund dessen die Dateien ungefähr eine solche Art übernehmen:


 

Auf eine solche Datei ist es schon leicht, den regulären Ausdruck "(>)(.*?)(<)" "zu hetzen" — das heißt, die Suche nach beliebigen Symbolen, die sich zwischen den Symbolen ">" und "<" befinden, dabei fängt die Zahl solcher Symbole von der Null an.

Die Arbeitsergebnisse der regulären Ausdrücke werden in vier Arrays unterbracht: arr_report_1, arr_report_2, arr_report_3 und arr_report_4. Die Informationen aus diesen Massiven werden für die Bildung des Codes des zusammenfassenden vergleichenden Berichtes verwendet. Nach der Bildung des zusammenfassenden Berichtes rufen wir WinAPI der Funktion ShellExecuteW auf (mehr über ShellExecuteW siehe hier ) und starten den Browser:

ShellExecuteW(hwnd,"open",path,NULL,NULL,SW_SHOWNORMAL);

Es öffnet sich die Seite des Browsers, auf der man die Ergebnisse der Tests des EAs direkt auf vier Symbolen vergleichen kann. 

 

Fazit

Im Artikel war noch eine Variante beschrieben, wie man die Ergebnisse der Tests des EAs auf vier Symbolen bewerten kann. Dabei geht der Test auf vier gewählten Symbolen parallel direkt auf vier Terminalen, und im Endeffekt haben wir die zusammengestellte Tabelle, in der die Ergebnisse dieser Tests gesammelt werden. 


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2731

Ein Beispiel für die Entwicklung einer Spread-Strategie basierend auf Futures der Moskauer Börse Ein Beispiel für die Entwicklung einer Spread-Strategie basierend auf Futures der Moskauer Börse
MetaTrader 5 erlaubt es, Roboter zu entwickeln und zu testen, die gleichzeitig auf mehreren Symbolen handeln. Der in die Plattform integrierte Strategietester lädt die Tickshistorie vom Server des Brokers automatisch herunter und berücksichtigt Kontraktspezifikationen: der Entwickler muss nichts manuell machen. Dies lässt einfach und präzise alle Bedingungen der MetaTrader 5 Handelsumgebung programmieren und Roboter testen, mit den Intervallen von bis zu Millisekunden zwischen dem Eingehen von Ticks auf verschiedenen Symbolen. In diesem Artikel zeigen wir, wie eine Spread-Strategie basierend auf zwei Futures der Moskauer Börse entwickelt und getestet werden kann.
Die statistische Verteilung in Form von einem Histogramm ohne Indikator-Puffer und Arrays Die statistische Verteilung in Form von einem Histogramm ohne Indikator-Puffer und Arrays
Im Artikel wird die Bildungsmöglichkeit der Histogramme der statistischen Verteilungen der Markt-Charakteristiken unter Verwendung des graphischen Gedächtnisses betrachtet, das heißt ohne Verwendung der Indikator-Puffer und Arrays. Es wurden die ausführlichen Beispiele des Aufbaus solcher Histogramme aufgeführt und wurde die sogenannte "verborgene" Funktional der graphischen Objekte der Sprache MQL5 vorgeführt.
Der universell Oszillator mit dem graphischen Interface Der universell Oszillator mit dem graphischen Interface
Im Artikel wird die Erstellung des universellen Indikators aufgrund aller Oszillators des Terminalen mit dem eigenen graphischen Interface beschrieben. Es ermöglicht schnell und bequem die Parameter jedes separaten Oszillators aus dem Chart-Fenster zu wechseln (und ohne Öffnung des Fensters der Eigenschaften), ihre Messwerte zu vergleichen und für sich die optimale Variante für eine konkrete Aufgabe zu wählen.
Die Trading-Strategie '80-20' Die Trading-Strategie '80-20'
Im Artikel wird die Erstellung der Instrumente (des Indikators und des EAs) für die Forschung der Handelsstrategie '80-20' beschrieben. Die Regeln der TS sind aus dem Buch Lindy Raschke und Lawrance Konnorsa "Street Smarts High Probability Short-Term Trading Strategies" genommen. In der Sprache MQL5 wurden die Regeln dieser Strategie formalisiert, und die auf ihrer Grundlage erstellten Indikatoren und der EA auf der aktuellen History des Marktes geprüft.