LifeHack für Trader: Der vergleichende Bericht über einige Tests
Inhalt
- Einführung
- Die notwendigen Aktionen
- 1. Eingangsparameter. Die Auswahl des Expert Advisors (EA) für den Test
- 2. Und wieder über common.ini
- 2.1. common.ini -> original.ini
- 2.2. Die Suche des Teiles [Common] mit Hilfe der regulären Ausdrücke
- 2.3. Wir erstellen vier Dateien: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini und myconfiguration4.ini
- 2.4. Die Editierung der ini-Dateien (Wir kopieren darin den allgemeinen Teil [Common] und die individuellen Teile [Tester])
- 3. Die Parsing und die Editierung der mq5-Datei des gewählten EAs
- 3.1 Geheimnis №3
- 3.2. Wir bauen "#include" ein
- 3.3. Wir bauen "double OnTester()"ein
- 3.4. Ein komplizierter Fall: im Kode gibt es schon DistributionOfProfits.mqh, und\oder OnTester()
- 4. Das Kopieren des Experten in die Ordner der untergeordneten Terminals
- 5. Der Start der untergeordneten Terminals
- 6. Der vergleichende Bericht
- Fazit
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:
- LifeHack für Trader: ein back-Test ist gut, und vier – ist besser
- LifeHack für den Händler: "Stille" Optimierung oder die optische Auswertung des Handels
- Reguläre Ausdrücke für Traders
Der Arbeitsplan wird es so:
- Wir bestimmen den EA, der getestet wird (Win API)
- 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)
- 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)
- Wir kopieren die individuellen common.ini nach Ordern der Terminale (Win API)
- Wir kopieren die individuellen common.ini nach Ordern der Terminale (Win API)
- Wir machen die Parsing der Berichte der untergeordneten Terminale
- 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.
- Sowohl im Hauptterminal, als auch in den untergeordneten Terminalen soll eines und das gleiche Handelskonto gestartet werden.
- 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.
- 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:
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:
- Die Verbindung zwischen dem Ordner der Installierung und dem Verzeichnis der Daten im Ordner AppData
- Wir stellen den Ordner der Installierung und den Ordner AppData der untergeordneten Terminals gegenüber
Wir wählen den EA mit Hilfe des Systemdialoges "Datei Öffnen" (die Funktion GetOpenFileNameW):
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 Zeile | Das Ablesen der ganzen Datei in eine Variable |
---|---|
|
|
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 Zeile | Das 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 gegeben | Und 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 Ergebnisse | Und 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:
- Die Suche der Zeile "[Common]" (MQL5);
- Nach dem Befunden — schreiben der gefundenen Zeile ins Array;
- 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:
- Den Aufruf include der Datei der graphischen Analyse der Handelshistory einzubauen, (ausführlicher siehe in der Start der Analytik-Graphiken aus dem Strategie-Tester);
- Der Einbau in den EA der Funktion OnTester()mit dem Aufruf der graphischen Analyse.
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:
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:
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
//--- 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:
//--- с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
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:
Varianten | Passt/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:
Früher schrieben wir in der konfigurierten ini-Datei, im Block [Tester], einen solchen Parameter "Report":
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:
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 arr_path[][2];
bool find_report=false;
//+------------------------------------------------------------------+
//| Enumeration command to start the application |
//+------------------------------------------------------------------+
enum EnSWParam
fügen wir die Funktion OnTimer() hinzu:
{
//--- 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:
- FindFirstFileW, FindNextFileW
- Die beispielsweise Verwendung FindFirstFileW, FindNextFileW
- Wir schauen in die Ordner der Terminals hinein ).
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:
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:
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
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.