English Русский 中文 Español 日本語 Português
preview
Kontinuierliche Walk-Forward-Optimierung (Teil 3): Eine Roboters für Autoadaptierung anpassen

Kontinuierliche Walk-Forward-Optimierung (Teil 3): Eine Roboters für Autoadaptierung anpassen

MetaTrader 5Beispiele | 3 April 2020, 09:49
725 0
Andrey Azatskiy
Andrey Azatskiy

Einführung

Dies ist der dritte Artikel aus einer Serie, die sich mit der Erstellung eines automatischen Optimierungsprogramms für die kontinuierliche Vorwärtsoptimierung befasst. Die beiden vorherigen Artikel sind unter den folgenden Links verfügbar:

  1. Kontinuierliche Walk-Forward-Optimierung (Teil 1): Arbeiten mit Optimierungsberichten
  2. Kontinuierliche Walk-Forward-Optimierung (Teil 2): Mechanismus zur Erstellung eines Optimierungsberichts für einen beliebigen Roboter

Der erste Artikel in dieser Serie ist der Schaffung eines Mechanismus für die Arbeit und die Bildung von Handelsberichtsdateien gewidmet, die für den Betrieb des automatischen Optimierers erforderlich sind. Der zweite Artikel stellte die Schlüsselobjekte vor, die das Herunterladen der Handelshistorie implementieren und Handelsberichte auf der Grundlage der heruntergeladenen Daten erstellen. Der aktuelle Artikel dient als Brücke zwischen den beiden vorhergehenden Teilen: er beschreibt den Mechanismus der Interaktion mit der DLL, der im ersten Artikel betrachtet wurde, und die Objekte zum Herunterladen von Berichten, die im zweiten Artikel beschrieben wurden.

Wir werden den Prozess der Wrapper-Erstellung für eine Klasse analysieren, die aus der DLL importiert wird und die eine XML-Datei mit der Handelshistorie bildet. Wir werden auch eine Methode für die Interaktion mit diesem Wrapper in Betracht ziehen. Der Artikel enthält auch Beschreibungen von zwei Funktionen, die die detaillierte und verallgemeinerte Handelsgeschichte zur weiteren Analyse laden. Zum Schluss werde ich eine gebrauchsfertige Vorlage vorstellen, die mit dem Autooptimierer arbeiten kann. Ich werde auch ein Beispiel für einen Standard-Algorithmus aus dem Standard-Expertensatz zeigen, der zeigt, wie jeder bestehende Algorithmus geändert werden kann, um mit dem Autooptimierer zu arbeiten. 

Kumulierte Handelshistorie laden

Manchmal müssen wir die Handelshistorie in eine Datei laden, um die Historie weiter zu analysieren und für andere Zwecke zu verwenden. Leider ist eine solche Schnittstelle im Terminal noch nicht verfügbar, aber diese Aufgabe kann mit den im vorigen Artikel beschriebenen Klassen implementiert werden. Das Verzeichnis, in dem sich die Dateien der beschriebenen Klassen befinden, hat zwei weitere Dateien, "ShortReport.mqh" und "DealsHistory.mqh", die allgemeine und detaillierte Daten herunterladen.

Beginnen wir mit der Datei ShortReport.mqh. Diese Datei enthält Funktionen und Makros, von denen die wichtigste die Funktion SaveReportToFile ist. Betrachten wir zunächst die Funktion 'write', die Daten in eine Datei schreibt. 

//+------------------------------------------------------------------+
//| File writer                                                      |
//+------------------------------------------------------------------+
void writer(string fileName,string headder,string row)
  {
   bool isFile=FileIsExist(fileName,FILE_COMMON); // Flag of whether the file exists
   int file_handle=FileOpen(fileName,FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_SHARE_WRITE|FILE_SHARE_READ); // Open file
   if(file_handle) // If the file has opened
     {
      FileSeek(file_handle,0,SEEK_END); // Move cursor to the file end
      if(!isFile) // If it is a newly created file, write a header
         FileWrite(file_handle,headder);
      FileWrite(file_handle,row); // Write message
      FileClose(file_handle); // Close the file
     }
  }

Die Daten werden in die Datei-Sandbox Terminal/Common/Files geschrieben. Die Idee der Funktion besteht darin, Daten in eine Datei zu schreiben, indem Zeilen hinzugefügt werden. Deshalb öffnen wir die Datei, holen uns ihr Handle und bewegen uns zum Dateiende. Wenn die Datei gerade erstellt wurde, schreiben Sie die übergebenen Header, ansonsten ignorieren Sie diesen Parameter.

Was das Makro betrifft, so ist es nur für das einfache Hinzufügen von Roboterparametern zur Datei vorgesehen.

#define WRITE_BOT_PARAM(fileName,param) writer(fileName,"",#param+";"+(string)param);

In diesem Makro nutzen wir den Vorteil von Makros und bilden einen String, der den Makronamen und seinen Wert enthält. Ein Parameter wird der Funktion übergeben. Weitere Details werden im Makro-Anwendungsbeispiel gezeigt. 

Die Hauptmethode SaveReportToFile ist ziemlich lang, deshalb bespreche ich nur einige Teile des Codes. Die Methode erzeugt eine Instanz der Klasse CDealHistoryGetter und erhält ein Array mit der kumulierten Handelshistorie, in dem eine Zeile ein Geschäft anzeigt.

DealDetales history[];
CDealHistoryGetter dealGetter(_comission_manager);
dealGetter.getDealsDetales(history,0,TimeCurrent());

Dann verifiziert sie, dass die Historie nicht leer ist, erstellt die Klasseninstanz CReportCreator und übernimmt die Strukturen mit den Hauptkoeffizienten:

if(ArraySize(history)==0)
   return;

CReportCreator reportCreator(_comission_manager);
reportCreator.Create(history,0);

TotalResult totalResult;
reportCreator.GetTotalResult(totalResult);
PL_detales pl_detales;
reportCreator.GetPL_detales(pl_detales);

Dann speichert sie historische Daten in einer Schleife, indem es die Funktion 'write' verwendet. Am Ende der Schleife werden Felder mit den folgenden Koeffizienten und Werten hinzugefügt:

  • Gewinn und Verlust
  • Trades gesamt
  • aufeinanderfolgende Gewinner
  • aufeinanderfolgend Drawdowns
  • Erholungsfaktor
  • Profit Faktor
  • Payoff
  • Drawdown durch pl

writer(fileName,"","==========================================================================================================");
writer(fileName,"","PL;"+DoubleToString(totalResult.total.PL)+";");
int total_trades=pl_detales.total.profit.orders+pl_detales.total.drawdown.orders;
writer(fileName,"","Total trdes;"+IntegerToString(total_trades));
writer(fileName,"","Consecutive wins;"+IntegerToString(pl_detales.total.profit.dealsInARow));
writer(fileName,"","Consecutive DD;"+IntegerToString(pl_detales.total.drawdown.dealsInARow));
writer(fileName,"","Recovery factor;"+DoubleToString(totalResult.total.recoveryFactor)+";");
writer(fileName,"","Profit factor;"+DoubleToString(totalResult.total.profitFactor)+";");
double payoff=MathAbs(totalResult.total.averageProfit/totalResult.total.averageDD);
writer(fileName,"","Payoff;"+DoubleToString(payoff)+";");
writer(fileName,"","Drawdown by pl;"+DoubleToString(totalResult.total.maxDrawdown.byPL)+";");

Die Operation der Methode wird hier abgeschlossen. Nun wollen wir eine einfache Möglichkeit zum Laden der Historie in Betracht ziehen: Fügen wir diese Funktion zu einem Expert Advisor aus dem Standardlieferpaket Experts/Examples/Moving Average/Moving Average.mq5 hinzu. Zuerst müssen wir unsere Datei verbinden:

#include <History manager/ShortReport.mqh>

Fügen wir dann zu den Eingaben die Variablen hinzu, die die individuelle Provision und die Slippage festlegen:

input double custom_comission = 0; // Custom commission;
input int custom_shift = 0; // Custom shift;

Wenn wir wollen, dass unsere Provision und Slippage direkt und nicht bedingt festgelegt werden (siehe die Beschreibung der Klasse CDealHistoryGetter im vorherigen Artikel), müssen wir vor dem Verbinden der Datei den Parameter ONLY_CUSTOM_COMISSION bestimmen, wie er unten gezeigt wird:

#define ONLY_CUSTOM_COMISSION
#include <History manager/ShortReport.mqh>

Erstellen wir dann das Klassenbeispiel CCCM, fügen in der OnInit-Methode Provision und Slippage zu dieser Klasseninstanz hinzu, die die Provisionen speichert.

CCCM _comission_manager_;

...

int OnInit(void)
  {
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);  

...

  }

Fügen wir dann die folgenden Codezeilen in der OnDeinit-Methode hinzu:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string file_name = __FILE__+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);

      WRITE_BOT_PARAM(file_name,MaximumRisk);      // Maximum Risk in percentage
      WRITE_BOT_PARAM(file_name,DecreaseFactor);   // Decrease factor
      WRITE_BOT_PARAM(file_name,MovingPeriod);     // Moving Average period
      WRITE_BOT_PARAM(file_name,MovingShift);      // Moving Average shift
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift
     }
  }

Dieser Code führt nach dem Löschen der Roboterinstanz eine Prüfung durch: Die Bedingung, ob der er im Tester läuft. Wenn der Roboter im Tester läuft, wird eine Funktion zur Speicherung der Roboter-Handelshistorie in eine Datei mit dem Titel "Compiled_file_name Report.csv" aufgerufen. Nach allem werden die Daten in die Datei geschrieben werden und 6 weitere Zeilen für die Eingabeparameter dieser Datei hinzugefügt. Jedes Mal, wenn der Expert Advisor im Strategy Tester im Testmodus gestartet wird, erhalten wir eine Datei mit der Beschreibung der von der EA durchgeführten Transaktionen. Diese Datei wird jedes Mal überschrieben, wenn wir einen neuen Test starten. Die Datei wird in der Datei-Sandbox unter dem Verzeichnis Common/Files gespeichert.

Laden der Handelshistorie, aufgesplittet nach den Deals

Sehen wir uns nun an, wie man einen detaillierten Handelsbericht laden kann, d.h. einen Bericht, in dem alle Deals in Positionen gruppiert sind. Zu diesem Zweck werden wir die Datei DealsHistory.mqh verwenden, die bereits die Verbindung zur Datei ShortReport.mqh enthält. Das bedeutet, dass wir durch die Verbindung der einzigen DealsHistory.mqh-Datei beide Methoden gleichzeitig verwenden können.

Die Datei enthält zwei Funktionen. Die erste ist eine gewöhnliche Funktion, die eine schöne Summierung ermöglicht:

void AddRow(string item, string &str)
  {
   str += (item + ";");
  }

Die zweite Funktion schreibt Daten in eine Datei, unter Verwendung der o.a. Funktion 'writer'. Die vollständige Implementierung ist unten dargestellt.

void WriteDetalesReport(string fileName,CCCM *_comission_manager)
  {

   if(FileIsExist(fileName,FILE_COMMON))
     {
      FileDelete(fileName,FILE_COMMON);
     }

   CDealHistoryGetter dealGetter(_comission_manager);

   DealKeeper deals[];
   dealGetter.getHistory(deals,0,TimeCurrent());

   int total= ArraySize(deals);

   string headder = "Asset;From;To;Deal DT (Unix seconds); Deal DT (Unix miliseconds);"+
                    "ENUM_DEAL_TYPE;ENUM_DEAL_ENTRY;ENUM_DEAL_REASON;Volume;Price;Comission;"+
                    "Profit;Symbol;Comment";

   for(int i=0; i<total; i++)
     {
      DealKeeper selected = deals[i];
      string asset = selected.symbol;
      datetime from = selected.DT_min;
      datetime to = selected.DT_max;

      for(int j=0; j<ArraySize(selected.deals); j++)
        {
         string row;
         AddRow(asset,row);
         AddRow((string)from,row);
         AddRow((string)to,row);

         AddRow((string)selected.deals[j].DT,row);
         AddRow((string)selected.deals[j].DT_msc,row);
         AddRow(EnumToString(selected.deals[j].type),row);
         AddRow(EnumToString(selected.deals[j].entry),row);
         AddRow(EnumToString(selected.deals[j].reason),row);
         AddRow((string)selected.deals[j].volume,row);
         AddRow((string)selected.deals[j].price,row);
         AddRow((string)selected.deals[j].comission,row);
         AddRow((string)selected.deals[j].profit,row);
         AddRow(selected.deals[j].symbol,row);
         AddRow(selected.deals[j].comment,row);

         writer(fileName,headder,row);

        }

      writer(fileName,headder,"");
     }


  }

Nachdem wir die Handelsdaten erhalten und den Header erstellt haben, fahren wir mit dem Schreiben des detaillierten Handelsberichts fort. Zu diesem Zweck implementieren Sie eine Schleife mit einer verschachtelten Schleife: die Hauptschleife arbeitet mit Positionen, und die innere Schleife läuft durch die Deals innerhalb dieser Positionen. Nach dem Schreiben jeder neuen Position (d.h. der Reihe von Geschäften, die die Position bilden), werden die Positionen mit einem Leerzeichen getrennt. Dies gewährleistet ein effizientes Lesen der resultierenden Datei. Wir müssen keine dramatischen Änderungen vornehmen, um diese Funktion in den Roboter zu integrieren, während wir nur einen Aufruf in OnDeinit durchführen müssen:

void OnDeinit(const int reason)
  {
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string file_name = __FILE__+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);

      WRITE_BOT_PARAM(file_name,MaximumRisk);      // Maximum Risk in percentage
      WRITE_BOT_PARAM(file_name,DecreaseFactor);   // Descrease factor
      WRITE_BOT_PARAM(file_name,MovingPeriod);     // Moving Average period
      WRITE_BOT_PARAM(file_name,MovingShift);      // Moving Average shift
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(__FILE__+" Deals Report.csv", &_comission_manager_);
     }
  }

Um im Detail zu demonstrieren, wie das Herunterladen der Daten durchgeführt wird, finden Sie hier eine leere EA-Vorlage mit den hinzugefügten Methoden zum Herunterladen des Berichts:

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define ONLY_CUSTOM_COMISSION
#include <History manager/DealsHistory.mqh>

input double custom_comission   = 0;       // Custom commission;
input int    custom_shift       = 0;       // Custom shift;

CCCM _comission_manager_;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string arr[];
      StringSplit(__FILE__,'.',arr);
      string file_name = arr[0]+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(arr[0]+" Deals Report.csv", &_comission_manager_);
     }
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

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

Durch Hinzufügen einer beliebigen Logik zur obigen Vorlage können wir die Erstellung eines Handelsberichts nach Abschluss des EA-Tests im Strategy Tester ermöglichen.

Wrapper für DLL zur Erstellung der kumulierten Handelshistorie

Der erste Artikel in dieser Reihe war der Erstellung einer DLL in der Sprache C# für die Arbeit mit Optimierungsberichten gewidmet. Da das bequemste Format für die kontinuierliche Walk-Forward-Optimierung XML ist, haben wir eine DLL erstellt, die den resultierenden Bericht lesen, schreiben und sortieren kann. Im Expert Advisor benötigen wir nur die Funktionalität zum Schreiben von Daten. Da Operationen mit reinen Funktionen jedoch weniger komfortabel sind als Objekte, wurde eine Wrapper-Klasse für die Daten-Download-Funktion erstellt. Dieses Objekt befindet sich in der Datei XmlHistoryWriter.mqh, es heißt СXmlHistoryWriter. Zusätzlich zum Objekt definiert es die Struktur der EA-Parameter. Diese Struktur wird verwendet, wenn die Liste der EA-Parameter an das Objekt übergeben wird. Betrachten wir alle Details der Implementierung. 

Um die Erstellung des Optimierungsberichts zu ermöglichen, verbinden wir die Datei ReportCreator.mqh. Um die statischen Methoden der Klasse aus der im ersten Artikel beschriebenen DLL zu verwenden, importieren wir sie (die Bibliothek muss bereits im Verzeichnis MQL5/Libraries verfügbar sein).

#include "ReportCreator.mqh"
#import "ReportManager.dll"

Nachdem wir die erforderlichen Links hinzugefügt haben, stellen wir sicher, dass die Parametersammlung, die dann an die Zielklasse übergeben wird, bequem um die Roboterparameter erweitert wird. 

struct BotParams
  {
   string            name,value;
  };

#define ADD_TO_ARR(arr, value) \
{\
   int s = ArraySize(arr);\
   ArrayResize(arr,s+1,s+1);\
   arr[s] = value;\
}

#define APPEND_BOT_PARAM(Var,BotParamArr) \
{\
   BotParams param;\
   param.name = #Var;\
   param.value = (string)Var;\
   \
   ADD_TO_ARR(BotParamArr,param);\
}

Da wir mit einer Kollektion von Objekten arbeiten werden, wird es einfacher sein, mit dynamischen Arrays zu arbeiten. Der Makro ADD_TO_ARR wurde für das bequeme Hinzufügen von Elementen zu dem dynamischen Array erstellt. Der Makro ändert die Größe der Kollektion und fügt dann das übergebene Element hinzu. Der Makro bietet eine universelle Lösung. Und so können wir jetzt schnell Werte jeden Typs zum dynamischen Array hinzufügen.

Der nächste Makro arbeitet direkt mit den Parametern. Dieser Makro erzeugt eine Instanz der BotParams-Struktur und fügt sie dem Array hinzu, indem er nur das Array, dem die Parameterbeschreibung hinzugefügt werden soll, und eine Variable, die diesen Parameter speichert, eingibt. Der Makro weist dem Parameter auf der Grundlage des Variablennamens einen entsprechenden Namen zu und weist den Parameterwert in ein String-Format umgewandelt zu.

Das String-Format gewährleistet die korrekte Übereinstimmung der Einstellungen in *.set-Dateien und der Daten, die in der *.xml-Datei gespeichert werden. Wie bereits in früheren Artikeln beschrieben, speichern Set-Dateien EA-Eingabeparameter im Schlüssel-Wert-Format, wobei der Variablenname wie im Code als Schlüssel und der dem Eingabeparameter zugewiesene Wert als Wert akzeptiert wird. Alle Aufzählungen (num) müssen als int Typ und nicht als Ergebnis der Funktion EnumToString() angegeben werden. Das beschriebene Makro konvertiert alle Parameter in die benötigte Zeichenkette, während alle Enumerationen zunächst in int und dann in das erforderliche Zeichenformat konvertiert werden.

Wir haben auch eine Funktion deklariert, die das Kopieren des Arrays von Roboterparametern in ein anderes Array ermöglicht.

void CopyBotParams(BotParams &dest[], const BotParams &src[])
  {
   int total = ArraySize(src);
   for(int i=0; i<total; i++)
     {
      ADD_TO_ARR(dest,src[i]);
     }
  }

Wir brauchen dies, weil die Standardfunktion ArrayCopy nicht mit dem Array von Strukturen funktioniert. 

Die Wrapper-Klasse wird wie folgt deklariert:

class CXmlHistoryWriter
  {
private:
   const string      _path_to_file,_mutex_name;
   CReportCreator    _report_manager;

   string            get_path_to_expert();//

   void              append_bot_params(const BotParams  &params[]);//
   void              append_main_coef(PL_detales &pl_detales,
                                      TotalResult &totalResult);//
   double            get_average_coef(CoefChartType type);
   void              insert_day(PLDrawdown &day,ENUM_DAY_OF_WEEK day);//
   void              append_days_pl();//

public:
                     CXmlHistoryWriter(string file_name,string mutex_name,
                     CCCM *_comission_manager);//
                     CXmlHistoryWriter(string mutex_name,CCCM *_comission_manager);
                    ~CXmlHistoryWriter(void) {_report_manager.Clear();} //

   void              Write(const BotParams &params[],datetime start_test,datetime end_test);//
  };

Es werden zwei 'constant' Zeichenfelder für das Schreiben in eine Datei deklariert:

  • _path_to_file
  • _mutex_name

Das erste Feld enthält den Pfad zu einer Datei, in die Daten geschrieben werden. Das zweite Feld enthält den Namen des verwendeten Mutex. Die Implementierung des benannten Mutex wird in der C#-DLL bereitgestellt. Wir brauchen dieses Mutex, weil der Optimierungsprozess in verschiedenen Threads, auf verschiedenen Kernen und in verschiedenen Prozessen implementiert wird (ein Start des Roboters = ein Prozess). Daher kann eine Situation auftreten, in der zwei Optimierungen abgeschlossen sind und zwei oder mehr Prozesse gleichzeitig versuchen, das Ergebnis in dieselbe Datei zu schreiben, was nicht akzeptabel ist. Um diese Situation zu eliminieren, verwenden wir ein Synchronisationsobjekt, das auf dem Kern des Betriebssystems basiert, d.h. einen benannten Mutex. 

Die Instanz der Klasse CReportCreator wird als Feld benötigt, da andere Funktionen auf dieses Objekt zugreifen, und es daher unlogisch wäre, es jedes Mal neu zu erstellen. Betrachten wir nun die Implementierung jeder Methode.

Beginnen wir mit dem Klassenkonstruktor.

CXmlHistoryWriter::CXmlHistoryWriter(string file_name,
                                     string mutex_name,
                                     CCCM *_comission_manager) : _mutex_name(mutex_name),
   _path_to_file(TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\"+file_name),
   _report_manager(_comission_manager)
  {
  }
CXmlHistoryWriter::CXmlHistoryWriter(string mutex_name,
                                     CCCM *_comission_manager) : _mutex_name(mutex_name),
   _path_to_file(TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\"+MQLInfoString(MQL_PROGRAM_NAME)+"_"+"Report.xml"),
   _report_manager(_comission_manager)
  {
  }

Die Klasse enthält zwei Konstruktoren. Achten Sie auf den zweiten Konstruktor, der den Namen der Datei setzt, in der der Optimierungsreport gespeichert ist. Im Autooptimierer, der im nächsten Artikel betrachtet wird, wird es möglich sein, nutzerdefinierte Optimierungsmanager zu setzen. Der standardmäßige fest programmierte Manager hat bereits eine implementierte Vereinbarung über die Benennung der vom Roboter erzeugten Berichtsdateien. Daher setzt der zweite Konstruktor diese Vereinbarung. Demnach muss der Dateiname mit dem EA-Namen beginnen, gefolgt von einem Unterstrich und dem Postfix "_Report.xml". Obwohl eine DLL eine Reportdatei überall auf dem PC schreiben kann, um die Information zu erhalten, dass die Datei zum Terminalbetrieb gehört, werden wir sie immer unter dem Verzeichnis Common in der MetaTrader 5 Sandbox speichern. 

Die Methode, die den Pfad zum Expert Advisor erhält:

string CXmlHistoryWriter::get_path_to_expert(void)
  {
   string arr[];
   StringSplit(MQLInfoString(MQL_PROGRAM_PATH),'\\',arr);
   string relative_dir=NULL;

   int total= ArraySize(arr);
   bool save= false;
   for(int i=0; i<total; i++)
     {
      if(save)
        {
         if(relative_dir== NULL)
            relative_dir=arr[i];
         else
            relative_dir+="\\"+arr[i];
        }

      if(StringCompare("Experts",arr[i])==0)
         save=true;
     }

   return relative_dir;
  }

Der EA-Pfad wird für den automatisierten Start des ausgewählten Expert Advisors benötigt. Zu diesem Zweck sollte sein Pfad in der ini-Datei angegeben werden, die beim Terminalstart übergeben wird. Der Pfad sollte relativ zum Experten-Ordner angegeben werden und nicht der volle Pfad, der durch die Funktion erhalten wird, die den Pfad zum aktuellen EA erhält. Daher müssen wir den erhaltenen Pfad zunächst in seine Komponenten aufteilen, wobei das Trennzeichen ein Schrägstrich ist. Dann, in einer Schleife, search nach dem Verzeichnis Experts, beginnend mit dem allerersten Verzeichnis. Sobald es gefunden wurde, bilden wir den Pfad zum Roboter, beginnend mit dem nächsten Verzeichnis (oder der EA-Datei, wenn sie sich direkt in der Wurzel des gewünschten Verzeichnisses befindet).

Die Methode append_bot_params:

Diese Methode ist ein Wrapper für eine importierte Methode mit dem gleichen Namen. Ihre Implementierung ist wie folgt:

void CXmlHistoryWriter::append_bot_params(const BotParams &params[])
  {

   int total= ArraySize(params);
   for(int i=0; i<total; i++)
     {
      ReportWriter::AppendBotParam(params[i].name,params[i].value);
     }
  }

Das oben erwähnte Array mit den EA-Parametern wird in diese Methode eingegeben. Dann rufen wir in einer Schleife die importierte Methode aus unserer DLL für jeden der EA-Parameter auf.

Die Implementierung der Methode append_main_coef ist einfach, so dass wir sie hier nicht berücksichtigen werden. Sie akzeptiert Strukturen aus der Klasse CReportCreator als Eingabeparameter.

Die Methode get_average_coef ist für die Berechnung von Durchschnittswerten von Koeffizienten durch eine einfache MA-Methode auf der Grundlage der übergebenen Koeffiziententabellen vorgesehen. Sie wird für die Berechnung des durchschnittlichen Gewinnfaktors und des durchschnittlichen Erholungsfaktors verwendet.  

Die Methode insert_day ist ein einfach zu benennender Wrapper für die importierte Methode ReportWriter::AppendDay. Die Methode append_days_pl verwendet bereits den oben erwähnten Wrapper.

Unter all diesen Wrapper-Methoden gibt es eine öffentliche Methode, die als Hauptmethode fungiert - es ist die Methode 'Write', die den gesamten Mechanismus zur Speicherung der Daten auslöst.

void CXmlHistoryWriter::Write(const BotParams &params[],datetime start_test,datetime end_test)
  {
   if(!_report_manager.Create())
     {
      Print("##################################");
      Print("Can`t create report:");
      Print("###################################");
      return;
     }
   TotalResult totalResult;
   _report_manager.GetTotalResult(totalResult);
   PL_detales pl_detales;
   _report_manager.GetPL_detales(pl_detales);

   append_bot_params(params);
   append_main_coef(pl_detales,totalResult);

   ReportWriter::AppendVaR(totalResult.total.VaR_absolute.VAR_90,
                           totalResult.total.VaR_absolute.VAR_95,
                           totalResult.total.VaR_absolute.VAR_99,
                           totalResult.total.VaR_absolute.Mx,
                           totalResult.total.VaR_absolute.Std);

   ReportWriter::AppendMaxPLDD(pl_detales.total.profit.totalResult,
                               pl_detales.total.drawdown.totalResult,
                               pl_detales.total.profit.orders,
                               pl_detales.total.drawdown.orders,
                               pl_detales.total.profit.dealsInARow,
                               pl_detales.total.drawdown.dealsInARow);
   append_days_pl();

   string error_msg=ReportWriter::MutexWriter(_mutex_name,get_path_to_expert(),AccountInfoString(ACCOUNT_CURRENCY),
                    _report_manager.GetBalance(),
                    (int)AccountInfoInteger(ACCOUNT_LEVERAGE),
                    _path_to_file,
                    _Symbol,(int)Period(),
                    start_test,
                    end_test);
   if(StringCompare(error_msg,"")!=0)
     {
      Print("##################################");
      Print("Error while creating (*.xml) report file:");
      Print("_________________________________________");
      Print(error_msg);
      Print("###################################");
     }
  }

Wenn ein Versuch, einen Bericht zu erstellen, fehlschlägt, wird ein entsprechender Eintrag zu den Protokollen hinzugefügt. Wenn der Bericht erfolgreich erstellt wurde, gehen wir weiter, um die gewünschten Koeffizienten zu erhalten und rufen dann die oben genannten Methoden nacheinander auf. Gemäß dem ersten Artikel fügen diese Methoden die gewünschten Parameter einer C#-Klasse hinzu. Dann wird eine Methode zum Schreiben von Daten in eine Datei aufgerufen. Wenn das Schreiben fehlschlägt, enthält error_msg den Text des Fehlers, der in die Testerprotokolle geschrieben wird. 

Die daraus resultierende Klasse kann den Handelsbericht generieren, sowie beim Aufruf der 'Write'-Methode Daten in eine Datei schreiben. Ich möchte den Prozess jedoch weiter vereinfachen, so dass wir uns nur mit den Eingabeparametern befassen und mit nichts anderem. Zu diesem Zweck wurde die folgende Klasse geschaffen.

Die Klasse CAutoUpLoader erzeugt nach Abschluss der Tests automatisch einen Handelsbericht. Dieser befindet sich in der Datei AutoLoader.mqh. Der Klassenoperation sollten wir das Einbinden der zuvor beschriebenen Klasse hinzufügen, die den Bericht im XML-Format generiert.

#include <History manager/XmlHistoryWriter.mqh>

Die Klassensignatur ist einfach:

class CAutoUploader
  {
private:

   datetime          From,Till;
   CCCM              *comission_manager;
   BotParams         params[];
   string            mutexName;

public:
                     CAutoUploader(CCCM *comission_manager, string mutexName, BotParams &params[]);
   virtual          ~CAutoUploader(void);

   virtual void      OnTick();

  };

Die Klasse verfügt über die überladene Methode OnTick sowie einen virtuellen Destruktor. Dadurch wird sichergestellt, dass die Klasse sowohl über Aggregation als auch über Vererbung angewendet werden kann. Ich meine Folgendes. Der Zweck dieser Klasse ist es, die Testabschlußzeit bei jedem Tick zu überschreiben und sich die Teststartzeit zu merken. Diese beiden Parameter sind für die Verwendung des zuvor beschriebenen Objekts erforderlich. Es gibt mehrere Ansätze für seine Anwendung: Wir können dieses Objekt entweder irgendwo in der Roboterklasse instanziieren (wenn der Roboter mit OOP entwickelt wird), oder im globalen Bereich — diese Lösung kann für die C-ähnliche Programmierung verwendet werden.

Danach wird in dieser Funktion die Methode OnTick() der Klasseninstanz aufgerufen. Nach dem Zerstören des Klassenobjekts wird der Handelsbericht in seinem Destruktor freigegeben. Die zweite Möglichkeit, die Klasse anzuwenden, besteht darin, eine EA-Klasse von ihr abzuleiten. Zu diesem Zweck wurden der virtuelle Destruktor und die überladene Methode OnTick() erstellt. Als Ergebnis der Anwendung der zweiten Methode werden wir direkt mit dem Expert Advisor arbeiten. Die Implementierung dieser Klasse ist einfach, da sie den Betrieb an die Klasse CXmlHistoryWriter delegiert:

void CAutoUploader::OnTick(void)
  {
   if(MQLInfoInteger(MQL_OPTIMIZATION)==1 ||
      MQLInfoInteger(MQL_TESTER)==1)
     {
      if(From == 0)
         From = iTime(_Symbol,PERIOD_M1,0);
      Till=iTime(_Symbol,PERIOD_M1,0);
     }
  }
CAutoUploader::CAutoUploader(CCCM *_comission_manager,string _mutexName,BotParams &_params[]) : comission_manager(_comission_manager),
   mutexName(_mutexName)
  {
   CopyBotParams(params,_params);
  }
CAutoUploader::~CAutoUploader(void)
  {
   if(MQLInfoInteger(MQL_OPTIMIZATION)==1 ||
      MQLInfoInteger(MQL_TESTER)==1)
     {
      CXmlHistoryWriter historyWriter(mutexName,
                                      comission_manager);

      historyWriter.Write(params,From,Till);
     }
  }

Fügen wir die beschriebene Funktionsweise zu unserem EA-Schema hinzu.

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define ONLY_CUSTOM_COMISSION
#include <History manager/DealsHistory.mqh>
#include <History manager/AutoLoader.mqh>

class CRobot;

input double custom_comission   = 0;       // Custom commission;
input int    custom_shift       = 0;       // Custom shift;

CCCM _comission_manager_;
CRobot *bot;
const string my_mutex = "My Mutex Name for this expert";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);

   BotParams params[];

   APPEND_BOT_PARAM(custom_comission,params);
   APPEND_BOT_PARAM(custom_shift,params);

   bot = new CRobot(&_comission_manager_,my_mutex,params);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string arr[];
      StringSplit(__FILE__,'.',arr);
      string file_name = arr[0]+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(arr[0]+" Deals Report.csv", &_comission_manager_);
     }

   delete bot;
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   bot.OnTick();
  }
//+------------------------------------------------------------------+


//+------------------------------------------------------------------+
//| Main Robot class                                                 |
//+------------------------------------------------------------------+
class CRobot : CAutoUploader
  {
public:
                     CRobot(CCCM *_comission_manager, string _mutexName, BotParams &_params[]) : CAutoUploader(_comission_manager,_mutexName,_params)
     {}

   void              OnTick() override;
  };

//+------------------------------------------------------------------+
//| Robot logic triggering method                                    |
//+------------------------------------------------------------------+
void CRobot::OnTick(void)
  {
   CAutoUploader::OnTick();

   Print("This should be the robot logic start");
  }
//+------------------------------------------------------------------+

Als Erstes muss also eine Referenz zu einer Datei hinzugefügt werden, in der die Wrapper-Klasse für das automatische Herunterladen von Berichten in XML gespeichert ist. Die Roboterklasse ist vorbestimmt, da sie am Ende des Projekts leichter zu implementieren und zu beschreiben ist. Normalerweise erstelle ich Algorithmen als MQL5-Projekte, wobei dies viel bequemer ist als der einseitige Ansatz, da die Klasse mit dem Roboter und verwandte Klassen in Dateien aufgeteilt sind. Aus Gründen der Bequemlichkeit wurde jedoch alles in eine Datei geschrieben.
Dann wird die Klasse beschrieben. In diesem Beispiel ist es eine leere Klasse mit einer überladenen OnTick-Methode. Es wird also der zweite CAutoUploader-Anwendungsweg, d.h. die Vererbung, verwendet. Bitte beachten Sie, dass bei der überladenen OnTick-Methode explizit die OnTick-Methode der Basisklasse aufgerufen werden muss, damit die Berechnung der Daten nicht abbricht. Dies ist für den gesamten Betrieb des Auto-Optimierers unerlässlich.

Der nächste Schritt ist das Erstellen eines Zeigers auf die Klasse mit dem Roboter, da es bequemer ist, ihn aus der OnInit-Methode statt aus dem globalen Bereich zu füllen. Außerdem wird eine Variable, die den Mutex-Namen speichert, erstellt.

Der Roboter wird in der OnInit-Methode instantiiert, und er wird wieder in OnDeinit gelöscht. Damit der Rückruf des Eintreffens eines neuen Ticks an unseren Roboter gesendet werden kann, rufen wir die überladene OnTick()-Methode über den Zeiger auf den Roboter auf. Sobald alle diese Aktionen abgeschlossen sind, schreiben wir unseren Roboter in die Klasse CRobot. Sobald dies geschehen ist, schreiben wir den Roboter in die Klasse CRobot.

Die Varianten des Berichts-Downloads durch Aggregation oder durch die Erstellung einer CAutoUpLoader-Instanz im globalen Bereich sind ähnlich. Sollten Sie Fragen haben, können Sie sich gerne an mich wenden. 

Durch die Verwendung dieses Robotertemplates oder durch Hinzufügen der entsprechenden Aufrufe zu Ihren bestehenden Algorithmen können Sie diese zusammen mit dem Autooptimierer verwenden, der im nächsten Artikel besprochen wird.

Schlussfolgerung

Im ersten Artikel wurde der Mechanismus des Betriebs mit XML-Berichtsdateien und die Erstellung der Dateistruktur analysiert. Das Erstellen von Berichten wurde im zweiten Artikel betrachtet. Der Mechanismus der Berichterstellung wurde untersucht, beginnend mit dem Objekt zum Herunterladen der Geschichte und endend mit den Objekten, die den Bericht generieren. Bei der Untersuchung der Objekte, die am Prozess der Berichtserstellung beteiligt sind, wurde der Berechnungsteil detailliert analysiert. Der Artikel enthielt auch die wichtigsten Koeffizientenformeln sowie die Beschreibung möglicher Berechnungsprobleme.

Wie bereits in der Einführung zu diesem Artikel erwähnt, dienen die in diesem Teil beschriebenen Objekte als Brücke zwischen dem Mechanismus zum Herunterladen von Daten und dem Mechanismus zur Erstellung des Berichts. Zusätzlich zu den Funktionen, mit denen Handelsberichtsdateien gespeichert werden können, enthält der Artikel die Beschreibung der Klassen, die am Entladen von XML-Berichten teilnehmen, sowie die Beschreibung von Robotervorlagen, die diese Funktionen automatisch nutzen können. Der Artikel beschreibt auch, wie die erstellten Funktionen zu einem bestehenden Algorithmus hinzugefügt werden können. Das bedeutet, dass Nutzer des Autooptimierungsprogramms sowohl die alten als auch die neuen Algorithmen optimieren können.   

Zwei Ordner sind im angehängten Archiv verfügbar. Entpacken Sie beide in das Verzeichnis MQL/Include. Die Bibliothek ReportManager.dll muss in das Verzeichnis MQL5/Libraries kopiert werden. Sie können Sie vom vorherigen Artikel herunterladen.

Die folgenden Dateien sind im Anhang enthalten:

  1. CustomGeneric
    • GenericSorter.mqh
    • ICustomComparer.mqh
  2. History manager
    • AutoLoader.mqh
    • CustomComissionManager.mqh
    • DealHistoryGetter.mqh
    • DealsHistory.mqh
    • ReportCreator.mqh
    • ShortReport.mqh
    • XmlHistoryWriter

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

Beigefügte Dateien |
Include.zip (29.82 KB)
Ökonometrischer Ansatz zur Ermittlung von Marktmustern: Autokorrelation, Heatmaps und Streudiagramme Ökonometrischer Ansatz zur Ermittlung von Marktmustern: Autokorrelation, Heatmaps und Streudiagramme
Der Artikel stellt eine erweiterte Studie über jahreszeitliche Merkmale vor: Autokorrelations-Heatmaps und Streudiagramme. Der Zweck des Artikels ist es zu zeigen, dass das "Marktgedächtnis" saisonaler Natur ist, was durch eine maximale Korrelation von Zuwächsen beliebiger Ordnung ausgedrückt wird.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXX): Schwebende Handelsanfragen - die Verwaltung der Anfrageobjekte Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXX): Schwebende Handelsanfragen - die Verwaltung der Anfrageobjekte
Im vorigen Artikel haben wir die Klassen der schwebenden Anfrageobjekte erstellt, die dem allgemeinen Konzept der Bibliotheksobjekte entsprechen. Dieses Mal werden wir uns mit der Klasse befassen, die die Verwaltung von schwebenden Anfrageobjekten ermöglicht.
Die Handelssignale mehrerer Währungen überwachen (Teil 1): Entwicklung der Anwendungsstruktur Die Handelssignale mehrerer Währungen überwachen (Teil 1): Entwicklung der Anwendungsstruktur
In diesem Artikel werden wir die Idee der Schaffung eines Mehrwährungsüberwachung für Handelssignale erörtern und eine zukünftige Anwendungsstruktur zusammen mit dem Prototyp entwickeln sowie den Rahmen für den weiteren Einsatz schaffen. Der Artikel stellt eine schrittweise Erstellung einer flexiblen Mehrwährungsanwendung vor, die die Erzeugung von Handelssignalen ermöglicht und die Händler bei der Suche nach den gewünschten Signalen unterstützt.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXIX): Schwebende Handelsanfrage - die Klasse der Anfrageobjekte Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXIX): Schwebende Handelsanfrage - die Klasse der Anfrageobjekte
In den vorhergehenden Artikeln haben wir das Konzept der schwebenden Handelsanfragen geprüft. Eine schwebende Anfrage ist in der Tat ein gewöhnlicher Handelsauftrag, der unter einer bestimmten Bedingung ausgeführt wird. In diesem Artikel werden wir vollwertige Klassen von Objekten für hängige Anfragen erstellen — ein Objekt für eine Basisanfrage und seine Nachkommen.