English Русский Español 日本語 Português
preview
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 24): Hinzufügen einer neuen Strategie (II)

Entwicklung eines Expertenberaters für mehrere Währungen (Teil 24): Hinzufügen einer neuen Strategie (II)

MetaTrader 5Tester |
10 19
Yuriy Bykov
Yuriy Bykov

Einführung

Wir setzen unsere Arbeit fort, die wir im vorigen Artikel begonnen haben. Wir möchten Sie daran erinnern, dass wir nach der Aufteilung des gesamten Projektcodes in den Bibliotheks- und den Projektteil beschlossen haben, zu prüfen, wie wir von der Modellhandelsstrategie SimpleVolumes zu einer anderen übergehen können. Was müssen wir dafür tun? Wie einfach wird es sein? Es versteht sich von selbst, dass es notwendig war, eine Klasse für eine neue Handelsstrategie zu schreiben. Doch dann traten einige unvorhersehbare Komplikationen auf.

Sie waren genau mit dem Wunsch verbunden, dass der Bibliotheksteil unabhängig vom Projektteil sein sollte. Hätten wir beschlossen, diese neu eingeführte Regel zu brechen, hätte es keine Schwierigkeiten gegeben. Schließlich wurde jedoch ein Weg gefunden, die Trennung des Codes beizubehalten und gleichzeitig die Integration der neuen Handelsstrategie zu ermöglichen. Dies erforderte Änderungen an den Bibliotheksdateien des Projekts, die zwar vom Umfang her nicht sehr groß, aber von der Bedeutung her bedeutend waren.

Infolgedessen konnten wir die Optimierung des EA der ersten Stufe (stage) mit einer neuen Strategie namens SimpleCandles kompilieren und ausführen. Die nächsten Schritte bestanden darin, das System mit dem automatischen Optimierungsförderer zum Laufen zu bringen. Für die vorherige Strategie haben wir den EA CreateProject.mq5 entwickelt, mit dem eine Datenbank zur Aufgabenoptimierung für die Ausführung auf dem Förderband erstellt werden konnte. In den EA-Parametern konnten wir angeben, welche Handelsinstrumente (Symbole) und Zeitrahmen wir optimieren wollten, die Namen der EA-Stufen und andere notwendige Informationen. Wenn die Optimierungsdatenbank vorher nicht existierte, wurde sie automatisch erstellt.

Schauen wir uns an, wie das mit der neuen Handelsstrategie funktionieren kann.


Der Weg ist vorgezeichnet

Wir beginnen die Hauptarbeit mit der Analyse des EA-Codes CreateProject.mq5. Unser Ziel ist es, Code zu identifizieren, der in verschiedenen Projekten gleich oder fast gleich ist. Dieser Code kann in einen Bibliotheksabschnitt aufgeteilt werden, der bei Bedarf in mehrere separate Dateien aufgeteilt wird. Wir werden den Teil des Codes, der für verschiedene Projekte unterschiedlich sein wird, im Projektabschnitt belassen und beschreiben, welche Änderungen daran vorgenommen werden müssen.

Doch zunächst wollen wir einen Fehler beheben, der beim Speichern von Testergebnisdaten in der Optimierungsdatenbank auftritt, die Makros für die Organisation von Zyklen verfeinern und uns ansehen, wie man einer bereits entwickelten Handelsstrategie neue Parameter hinzufügt.


Korrekturen in CDatabase

In den letzten Artikeln haben wir damit begonnen, relativ kurze Testintervalle für Optimierungsprojekte zu verwenden. Anstelle von Intervallen von 5 Jahren oder mehr haben wir begonnen, Intervalle von mehreren Monaten zu nehmen. Das lag daran, dass unsere Hauptaufgabe darin bestand, die Funktionsweise des Fördermechanismus für die automatische Optimierung zu testen, und durch die Verringerung des Intervalls konnten wir die Zeit für einen einzelnen Testdurchlauf und damit die Gesamtoptimierungszeit erheblich reduzieren.

Um Informationen über Durchläufe in der Optimierungsdatenbank zu speichern, sendet jeder Testagent (lokal, remote oder in der Cloud) sie als Teil eines Datenrahmens an das Terminal, auf dem der Optimierungsprozess läuft. In diesem Terminal wird nach Beginn der Optimierung eine zusätzliche Instanz des optimierten EA in einem speziellen Modus gestartet – dem Datenrahmen-Sammelmodus. Diese Instanz wird nicht im Tester gestartet, sondern auf einer separaten Terminal-Chart. Er empfängt und speichert alle Informationen, die von Testagenten kommen.

Obwohl der Code für den Event-Handler für die Ankunft neuer Datenrahmen von Testagenten keine asynchronen Operationen enthält, begannen während der Optimierung Meldungen über Fehler beim Einfügen in die Datenbank zu erscheinen, die darauf zurückzuführen waren, dass die Datenbank durch eine andere Operation gesperrt war. Dieser Fehler war relativ selten. Bei mehreren Dutzend von mehreren Tausend Durchläufen konnten die Ergebnisse jedoch nicht in die Optimierungsdatenbank aufgenommen werden.

Die Ursache für diese Fehler scheint die zunehmende Zahl von Situationen zu sein, in denen mehrere Testagenten gleichzeitig einen Lauf durchführen und einen Datenrahmen an den EA im Hauptterminal senden. Und dieser EA versucht, einen neuen Eintrag in die Datenbank schneller einzufügen, als der vorherige Einfügevorgang auf der Datenbankseite abgeschlossen werden kann.

Um dies zu beheben, werden wir einen separaten Handler für diese Kategorie von Fehlern hinzufügen. Wenn die Ursache des Fehlers darin liegt, dass die Datenbank oder die Tabelle durch eine andere Operation gesperrt ist, müssen wir die fehlgeschlagene Operation einfach nach einiger Zeit wiederholen. Wenn nach einer bestimmten Anzahl von Versuchen, die Daten wieder einzufügen, derselbe Fehler erneut auftritt, sollten die Versuche abgebrochen werden.

Zum Einfügen verwenden wir die Methode CDatabase::ExecuteTransaction(), die wir wie folgt ändern. Hinzufügen des Zählers für die Ausführungsversuche der Anfrage zu den Methodenargumenten. Tritt ein solcher Fehler auf, pausieren wir für eine zufällige Anzahl von Millisekunden (0 – 50) und rufen die gleiche Funktion mit einem erhöhten Wert des Zählers auf. 

//+------------------------------------------------------------------+
//| Execute multiple DB queries in one transaction                   |
//+------------------------------------------------------------------+
bool CDatabase::ExecuteTransaction(string &queries[], int attempt = 0) {
// Open a transaction
   DatabaseTransactionBegin(s_db);

   s_res = true;
// Send all execution requests
   FOREACH(queries, {
      s_res &= DatabaseExecute(s_db, queries[i]);
      if(!s_res) break;
   });

// If an error occurred in any request, then
   if(!s_res) {
      // Cancel transaction
      DatabaseTransactionRollback(s_db);
      if((_LastError == ERR_DATABASE_LOCKED || _LastError == ERR_DATABASE_BUSY) && attempt < 20) {
         PrintFormat(__FUNCTION__" | ERROR: ERR_DATABASE_LOCKED. Repeat Transaction in DB [%s]",
                     s_fileName);
         Sleep(rand() % 50);
         ExecuteTransaction(queries, attempt + 1);

      } else {
         // Report it
         PrintFormat(__FUNCTION__" | ERROR: Transaction failed in DB [%s], error code=%d",
                     s_fileName, _LastError);
      }

   } else {
      // Otherwise, confirm transaction
      DatabaseTransactionCommit(s_db);
      //PrintFormat(__FUNCTION__" | Transaction done successfully");
   }
   return s_res;
}

Vorsichtshalber nehmen wir die gleichen Änderungen an der Methode CDatabase::Execute() vor, um eine SQL-Abfrage ohne Transaktion auszuführen.

Eine weitere kleine Änderung, die für uns in Zukunft nützlich sein wird, war das Hinzufügen einer statischen booleschen Variable zur CDatabase-Klasse. Es wird sich daran erinnern, dass bei der Ausführung von Anfragen ein Fehler aufgetreten ist:

//+------------------------------------------------------------------+
//| Class for handling the database                                  |
//+------------------------------------------------------------------+
class CDatabase {
   // ...
   static bool       s_res;         // Query execution result

public:
   static int        Id();          // Database connection handle
   static bool       Res();         // Query execution result

   // ...
};

bool   CDatabase::s_res      =  true;

Speichern Sie die an der Datei Database/Database.mqh vorgenommenen Änderungen im Bibliotheksordner.


Korrekturen in Macros.h

Lassen Sie uns eine Änderung erwähnen, die schon lange überfällig war. Wie Sie sich vielleicht erinnern, haben wir das Makro FOREACH(A, D) geschaffen, um das Schreiben der Kopfzeilen von Schleifen zu vereinfachen, die über alle Werte in einem bestimmten Array iterieren sollen:

#define FOREACH(A, D)   { for(int i=0, im=ArraySize(A);i<im;i++) {D;} }

Hier ist A ein Array-Name, während D ein Schleifenkörper ist. Diese Implementierung hatte den Nachteil, dass es unmöglich war, die schrittweise Ausführung des Codes innerhalb des Schleifenkörpers beim Debuggen richtig zu verfolgen. Obwohl dies nur selten erforderlich war, war es sehr lästig. Als ich eines Tages in der Dokumentation stöberte, sah ich eine andere Möglichkeit, ein ähnliches Makro zu implementieren. Das Makro gab nur den Schleifenkopf an, und der Körper wurde außerhalb des Makros verschoben. Es gab jedoch einen weiteren Parameter, der den Namen der Schleifenvariablen angab.

In unserer früheren Implementierung war der Name der Schleifenvariablen (der Index des Array-Elements) fest vorgegeben (i), was nirgendwo Probleme verursachte. Selbst an der Stelle, an der eine Doppelschleife erforderlich war, konnte man aufgrund der unterschiedlichen Geltungsbereiche dieser Indizes mit denselben Namen auskommen. Daher erhielt die neue Implementierung auch einen festen Indexnamen. Der einzige Parameter, der übergeben wird, ist der Name des Arrays, über das in der Schleife iteriert werden soll:

#define FOREACH(A)       for(int i=0, im=ArraySize(A);i<im;i++)

Um auf die neue Version umzusteigen, mussten Änderungen an allen Stellen vorgenommen werden, an denen dieses Makro verwendet wurde. Zum Beispiel:

//+------------------------------------------------------------------+
//| OnTick event handler                                             |
//+------------------------------------------------------------------+
void CAdvisor::Tick(void) {
// Call OnTick handling for all strategies
   //FOREACH(m_strategies, m_strategies[i].Tick();)
   FOREACH(m_strategies) m_strategies[i].Tick();
}

Zusammen mit diesem Makro haben wir ein weiteres Makro hinzugefügt, das die Erstellung eines Schleifenkopfes ermöglicht. In dem Makro wird jedes Element des Arrays A nacheinander in das Array E (das im Voraus angekündigt werden sollte) eingefügt. Vor dem Schleifenkopf wird das erste Element des Arrays, falls vorhanden, in diese Variable gesetzt. Als Schleifenvariable verwenden wir eine Variable mit einem Namen, der aus dem Buchstaben i und dem Variablennamen E besteht. Im dritten Teil des Schleifenkopfes wird die Schleifenvariable inkrementiert, während die Variable E den Wert des Arrayelements A mit dem erhöhten Index erhält. Indem wir einen Index modulo der Anzahl der Arrayelemente nehmen, können wir vermeiden, dass wir bei der letzten Iteration der Schleife über die Array-Grenzen hinausgehen: 

#define FOREACH_AS(A, E) if(ArraySize(A)) E=A[0]; \
   for(int i##E=0, im=ArraySize(A);i##E<im;E=A[++i##E%im])

Wir speichern die Änderungen in der Datei Utils/Macros.h im Bibliotheksordner.


Hinzufügen eines Parameters zu einer Handelsstrategie

Wie fast der gesamte Code ist auch die Umsetzung einer Handelsstrategie Änderungen unterworfen. Wenn diese Änderungen die Änderung der Zusammensetzung der Eingabeparameter einer einzelnen Instanz einer Handelsstrategie betreffen, dann müssen nicht nur Änderungen an der Handelsstrategieklasse, sondern auch an einigen anderen Stellen vorgenommen werden. Schauen wir uns ein Beispiel an, um zu sehen, was dafür getan werden muss.

Nehmen wir an, dass wir beschließen, der Handelsstrategie einen Parameter für den maximalen Spread hinzuzufügen. Seine Verwendung besteht darin, dass, wenn zum Zeitpunkt des Empfangs eines Signals zur Eröffnung einer Position der aktuelle Spread den in diesem Parameter eingestellten Wert überschreitet, die Position nicht eröffnet wird.

Zunächst fügen wir der ersten Stage-EA einen Eingang hinzu, über den wir diesen Wert bei der Ausführung des Testers einstellen können. Dann fügen wir in der Funktion zur Bildung des Initialisierungsstrings die Ersetzung des neuen Parameterwerts in den Initialisierungsstring ein:

//+------------------------------------------------------------------+
//| 4. Strategy inputs                                               |
//+------------------------------------------------------------------+
sinput string     symbol_              = "";    // Symbol
sinput ENUM_TIMEFRAMES period_         = PERIOD_CURRENT;   // Timeframe for candles

input group "===  Opening signal parameters"
input int         signalSeqLen_        = 6;     // Number of unidirectional candles
input int         periodATR_           = 0;    // ATR period (if 0, then TP/SL in points)

input group "===  Pending order parameters"
input double      stopLevel_           = 25000;  // Stop Loss (in ATR fraction or points)
input double      takeLevel_           = 3630;   // Take Profit (in ATR fraction or points)

input group "===  Money management parameters"
input int         maxCountOfOrders_    = 9;     // Max number of simultaneously open orders
input int         maxSpread_           = 10;    // Max acceptable spread (in points)

//+------------------------------------------------------------------+
//| 5. Strategy initialization string generation function            |
//|    from the inputs                                               |
//+------------------------------------------------------------------+
string GetStrategyParams() {
   return StringFormat(
             "class CSimpleCandlesStrategy(\"%s\",%d,%d,%d,%.3f,%.3f,%d,%d)",
             (symbol_ == "" ? Symbol() : symbol_), period_,
             signalSeqLen_, periodATR_, stopLevel_, takeLevel_,
             maxCountOfOrders_, maxSpread_
          );
}

Der Initialisierungsstring enthält nun einen Parameter mehr als zuvor. Die nächste Änderung besteht also darin, die neue Eigenschaft der Klasse hinzuzufügen und die Werte aus der Initialisierungszeichenkette im Konstruktor in sie einzulesen:

//+------------------------------------------------------------------+
//| Trading strategy using unidirectional candlesticks               |
//+------------------------------------------------------------------+
class CSimpleCandlesStrategy : public CVirtualStrategy {
protected:
   // ...

   //---  Money management parameters
   int               m_maxCountOfOrders;  // Max number of simultaneously open positions
   int               m_maxSpread;         // Max acceptable spread (in points)

   // ...
   
};

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSimpleCandlesStrategy::CSimpleCandlesStrategy(string p_params) {
// Read the parameters from the initialization string
   m_params = p_params;
   m_symbol = ReadString(p_params);
   m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params);
   m_signalSeqLen = (int) ReadLong(p_params);
   m_periodATR = (int) ReadLong(p_params);
   m_stopLevel = ReadDouble(p_params);
   m_takeLevel = ReadDouble(p_params);
   m_maxCountOfOrders = (int) ReadLong(p_params);
   m_maxSpread = (int) ReadLong(p_params);

   // ...
}

Nun kann der neue Parameter nach Belieben in den Methoden der Handelsstrategieklasse verwendet werden. Je nach Verwendungszweck kann der folgende Code der Methode zum Empfang des Positionsöffnungssignals hinzugefügt werden.

//+------------------------------------------------------------------+
//| Signal for opening pending orders                                |
//+------------------------------------------------------------------+
int CSimpleCandlesStrategy::SignalForOpen() {
// By default, there is no signal
   int signal = 0;

   MqlRates rates[];
// Copy the quote values (candles) to the destination array.
// To check the signal we need m_signalSeqLen of closed candles and the current candle,
// so in total m_signalSeqLen + 1
   int res = CopyRates(m_symbol, m_timeframe, 0, m_signalSeqLen + 1, rates);

// If the required number of candles has been copied
   if(res == m_signalSeqLen + 1) {
      signal = 1; // buy signal

      // Go through all closed candles
      for(int i = 1; i <= m_signalSeqLen; i++) {
         // If at least one upward candle occurs, cancel the signal
         if(rates[i].open < rates[i].close ) {
            signal = 0;
            break;
         }
      }

      if(signal == 0) {
         signal = -1; // otherwise, sell signal

         // Go through all closed candles
         for(int i = 1; i <= m_signalSeqLen; i++) {
            // If at least one downward candle occurs, cancel the signal
            if(rates[i].open > rates[i].close ) {
               signal = 0;
               break;
            }
         }
      }
   }

// If there is a signal, then
   if(signal != 0) {
      // If the current spread is greater than the maximum allowed, then
      if(rates[0].spread > m_maxSpread) {
         PrintFormat(__FUNCTION__" | IGNORE %s Signal, spread is too big (%d > %d)",
                     (signal > 0 ? "BUY" : "SELL"),
                     rates[0].spread, m_maxSpread);
         signal = 0; // Cancel the signal
      }
   }

   return signal;
}

In ähnlicher Weise können wir weitere neue Parameter zu Handelsstrategien hinzufügen oder überflüssig gewordene Parameter loswerden.


Analyse von CreateProject.mq5

Beginnen wir mit der Analyse des EA-Codes für die Projekterstellung CreateProject.mq5. In seiner Initialisierungsfunktion haben wir den Code bereits in einzelne Funktionen aufgeteilt. Der Zweck der einzelnen Programme ist aus dem Namen ersichtlich:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
// Connect to the database
   DB::Connect(fileName_);

// Create a project
   CreateProject(projectName_,
                 projectVersion_,
                 StringFormat("%s - %s",
                              TimeToString(fromDate_, TIME_DATE),
                              TimeToString(toDate_, TIME_DATE)
                             )
                );
// Create project stages
   CreateStages();

// Creating jobs and tasks
   CreateJobs();

// Queueing the project for execution
   QueueProject();

// Close the database
   DB::Close();

// Successful initialization
   return(INIT_SUCCEEDED);
}

Diese Aufteilung ist nicht sehr praktisch, denn die ausgewählten Funktionen erwiesen sich als recht umständlich und lösen ganz unterschiedliche Probleme. In der Funktion CreateJobs() werden zum Beispiel Eingabedaten vorverarbeitet, Parametervorlagen für Aufträge erstellt, Informationen in die Datenbank eingefügt und dann ähnliche Aktionen durchgeführt, um Optimierungsaufgaben in der Datenbank zu erstellen. Es wäre besser, wenn es andersherum wäre: Die Funktionen wären einfacher und würden ein kleines Problem lösen.

Um die neue Strategie in der aktuellen Implementierung zu verwenden, müssten wir die Vorlage der Parameter der ersten Stufe und möglicherweise auch die Anzahl der Aufgaben mit Optimierungskriterien dafür ändern. Die Parametervorlage für die erste Stufe der vorherigen Handelsstrategie wurde in der globalen Variable paramsTemplate1 angegeben:

// Template of optimization parameters at the first stage
string paramsTemplate1 =
   "; ===  Open signal parameters\n"
   "signalPeriod_=212||12||40||240||Y\n"
   "signalDeviation_=0.1||0.1||0.1||2.0||Y\n"
   "signaAddlDeviation_=0.8||0.1||0.1||2.0||Y\n"
   "; ===  Pending order parameters\n"
   "openDistance_=10||0||10||250||Y\n"
   "stopLevel_=16000||200.0||200.0||20000.0||Y\n"
   "takeLevel_=240||100||10||2000.0||Y\n"
   "ordersExpiration_=22000||1000||1000||60000||Y\n"
   "; ===  Capital management parameters\n"
   "maxCountOfOrders_=3||3||1||30||N\n";

Glücklicherweise war dies bei allen Optimierungsaufträgen der ersten Stufe der Fall. Dies ist jedoch nicht immer der Fall. In der neuen Strategie haben wir zum Beispiel die Symbolwerte und den Zeitrahmen, mit dem die Strategie arbeiten soll, in die Parameter aufgenommen. Das bedeutet, dass in verschiedenen Optimierungsaufträgen der ersten Stufe, die für unterschiedliche Symbole und Zeiträume erstellt wurden, die Parametervorlage variable Teile haben wird. Um die Werte festzulegen, müssen Sie jedoch in die Tiefen des Codes der Aufgabenerstellungsfunktion eindringen und ihn ändern. Dann ist es nicht mehr möglich, sie in den Bibliotheksbereich zu bringen.

Darüber hinaus erstellt unser EA zur Erstellung von Optimierungsprojekten jetzt ein Projekt mit drei festen Stufen. Wir sind während der Entwicklung zu diesem einfachen Satz von Stufen gekommen, obwohl wir versucht haben, weitere Stufen hinzuzufügen (siehe zum Beispiel Teil 18 und Teil 19). Zusätzliche Schritte führten nicht zu einer signifikanten Verbesserung des Endergebnisses, obwohl dies bei anderen Handelsstrategien möglicherweise nicht der Fall ist. Wenn wir also den aktuellen Code in den Bibliotheksteil verschieben, können wir die Zusammensetzung der Stufen in Zukunft nicht mehr ändern, wenn wir dies wünschen.

So sehr wir uns auch wünschen, mit wenig Aufwand auszukommen, so ist es doch besser, jetzt eine ernsthafte Überarbeitung des Codes vorzunehmen, als sie auf später zu verschieben. Wir wollen versuchen, den EA-Code für die Projekterstellung in mehrere Klassen aufzuteilen. Die Klassen werden in den Bibliotheksbereich verschoben, und im Projektbereich werden wir sie verwenden, um Projekte mit der gewünschten Zusammensetzung von Stufen und deren Inhalt zu erstellen. Gleichzeitig wird dies auch als Vorlage für die künftige Anzeige von Informationen über den Fortschritt des Förderers dienen.

Zunächst versuchten wir zu schreiben, wie der endgültige Code aussehen könnte. Diese vorläufige Fassung blieb bis zur Veröffentlichung der endgültigen Fassung praktisch unverändert. Den Methodenaufrufen wurden nur bestimmte Parameterzusammensetzungen hinzugefügt. Sehen wir uns also an, wie die neue Version der Initialisierungsfunktion für die Erstellung des Optimierungsprojekts EA aussieht. Um nicht durch kleine Details abzulenken, werden die Argumente der Methoden nicht gezeigt:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
// Create an optimization project object for the given database
   COptimizationProject p;

// Create a new project in the database
   p.Create(...);


// Add the first stage
   p.AddStage(...);
              
// Adding the first stage jobs
   p.AddJobs(...);
   
// Add tasks for the first stage jobs
   p.AddTasks(...);

   
// Add the second stage
   p.AddStage(...);
              
// Add the second stage jobs
   p.AddJobs(...);
   
// Add tasks for the second stage jobs
   p.AddTasks(...);


// Add the third stage
   p.AddStage(...);
              
// Add the third stage job
   p.AddJobs(...);
   
// Add a task for the third stage job
   p.AddTasks(...);


// Put the project in the execution queue
   p.Queue();

// Delete the EA
   ExpertRemove();

// Successful initialization
   return(INIT_SUCCEEDED);
}

Mit dieser Codestruktur können wir leicht neue Stufen hinzufügen und ihre Parameter flexibel ändern. Aber im Moment sehen wir nur eine neue Klasse, die wir auf jeden Fall brauchen werden – die Optimierungsprojektklasse COptimizationProject. Schauen wir uns den Code an.


Klasse COptimizationProject

Bei der Entwicklung dieser Klasse wurde schnell klar, dass wir für alle Arten von Entitäten, die wir in der Optimierungsdatenbank speichern, separate Klassen benötigen würden. Es folgen die Klassen COptimizationStage für die Projektphasen, COptimizationJob für die Projektphasenaufträge und COptimizationTask für die Aufgaben der einzelnen Projektphasenaufträge.

Da die Objekte dieser Klassen im Wesentlichen eine Darstellung von Einträgen aus verschiedenen Tabellen der Optimierungsdatenbank sind, wird die Zusammensetzung der Klassenfelder die Zusammensetzung der Felder der entsprechenden Tabellen wiederholen. Zusätzlich zu diesen Feldern werden wir weitere Felder und Methoden zu diesen Klassen hinzufügen, die für die Ausführung der ihnen zugewiesenen Aufgaben erforderlich sind.

Der Einfachheit halber werden wir alle Eigenschaften und Methoden der erstellten Klassen öffentlich machen. Jede Klasse hat ihre eigene Methode, um einen neuen Eintrag in der Optimierungsdatenbank zu erstellen. In Zukunft werden wir Methoden zum Ändern eines bestehenden Eintrags und zum Lesen eines Eintrags aus der Datenbank hinzufügen, da wir sie beim Erstellen des Projekts nicht benötigen.

Anstelle der bisher verwendeten Vorlagen der Tester-Parameter werden wir separate Funktionen erstellen, die bereits gefüllte Parameter entsprechend der Vorlage zurückgeben. Auf diese Weise werden die Parametervorlagen innerhalb dieser Funktionen verschoben. Diese Funktionen nehmen einen Projektzeiger als Parameter entgegen und können damit auf die erforderlichen Projektinformationen zugreifen, die in der Vorlage ersetzt werden sollen. Wir werden die Deklaration dieser Funktionen in den Projektabschnitt verschieben, und im Bibliotheksabschnitt werden wir nur einen neuen Typ deklarieren – einen Zeiger auf die Funktion des folgenden Typs:

// Create a new type - a pointer to a string generation function
// for optimization job parameters (job) accepting the pointer
// to the optimization project object as an argument
typedef string (*TJobsTemplateFunc)(COptimizationProject*);

Dadurch können wir die Funktionen zur Erzeugung der Stufenparameter in der Klasse COptimizationProject nutzen. Es gibt sie noch nicht, aber in Zukunft werden wir sie im Designteil auf jeden Fall hinzufügen müssen.

So sieht die Beschreibung dieser Klasse aus:

//+------------------------------------------------------------------+
//| Optimization project class                                       |
//+------------------------------------------------------------------+
class COptimizationProject {
public:
   string            m_fileName;    // Database name

   // Properties stored directly in the database
   ulong             id_project;    // Project ID
   string            name;          // Name
   string            version;       // Version
   string            description;   // Description
   string            status;        // Status

   // Arrays of all stages, jobs and tasks
   COptimizationStage* m_stages[];  // Project stages
   COptimizationJob*   m_jobs[];    // Jobs of all project stages
   COptimizationTask*  m_tasks[];   // Tasks of all jobs of project stages

   // Properties for the current state of the project creation
   string            m_symbol;      // Current symbol
   string            m_timeframe;   // Current timeframe

   COptimizationStage* m_stage;     // Last created stage (current stage)
   COptimizationJob*   m_job;       // Last created job (current job)
   COptimizationTask*  m_task;      // Last created task (current task)

   // Methods
                     COptimizationProject(string p_fileName);  // Constructor
                    ~COptimizationProject();                   // Destructor

   // Create a new project in the database
   COptimizationProject* COptimizationProject::Create(string p_name,
         string p_version = "", string p_description = "", string p_status = "Done");

   void              Insert();   // Insert an entry into the database
   void              Update();   // Update an entry in the database

   // Add a new stage to the database
   COptimizationProject* AddStage(COptimizationStage* parentStage, string stageName,
                                  string stageExpertName,
                                  string stageSymbol, string stageTimeframe, 
                                  int stageOptimization, int stageModel,
                                  datetime stageFromDate, datetime stageToDate,
                                  int stageForwardMode, datetime stageForwardDate,
                                  int stageDeposit = 10000, string stageCurrency = "USD",
                                  int stageProfitInPips = 0, int stageLeverage = 200,
                                  int stageExecutionMode = 0, int stageOptimizationCriterion = 7,
                                  string stageStatus = "Done");

   // Add new jobs to the database for the specified symbols and timeframes
   COptimizationProject* AddJobs(string p_symbols, string p_timeframes, 
                                 TJobsTemplateFunc p_templateFunc);
   COptimizationProject* AddJobs(string &p_symbols[], string &p_timeframes[],
                                 TJobsTemplateFunc p_templateFunc);

   // Add new tasks to the database for the specified optimization criteria
   COptimizationProject* AddTasks(string p_criterions);
   COptimizationProject* AddTasks(string &p_criterions[]);

   void              Queue();    // Put the project in the execution queue

   // Convert a string name to a timeframe
   static ENUM_TIMEFRAMES   StringToTimeframe(string s);
};

Am Anfang stehen die Eigenschaften, die direkt in der Optimierungsdatenbank in der Tabelle Projekte gespeichert sind. Als Nächstes folgen Arrays aller Projektphasen, Aufträge und Aufgaben, und dann Eigenschaften für den aktuellen Stand der Projekterstellung.

Da diese Klasse derzeit nur eine Aufgabe hat (Anlegen eines Projekts in der Optimierungsdatenbank), stellen wir im Konstruktor sofort eine Verbindung zur gewünschten Datenbank her und öffnen eine Transaktion. Der Abschluss dieser Transaktion erfolgt im Destruktor. An dieser Stelle kommt das statische Klassenfeld CDatabase::s_res zum Einsatz. Sein Wert kann verwendet werden, um festzustellen, ob beim Einfügen von Einträgen in die Optimierungsdatenbank beim Erstellen eines Projekts ein Fehler aufgetreten ist. Wenn keine Fehler aufgetreten sind, wird die Transaktion bestätigt, andernfalls wird sie storniert. Außerdem wird der Speicher für erstellte dynamische Objekte im Destruktor freigegeben.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
COptimizationProject::COptimizationProject(string p_fileName) :
   m_fileName(p_fileName), id_project(0) {
// Connect to the database
   if (DB::Connect(m_fileName)) {
      // Start a transaction
      DatabaseTransactionBegin(DB::Id());
   }
}

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
COptimizationProject::~COptimizationProject() {
// If no errors occurred, then
   if(DB::Res()) {
      // Confirm the transaction
      DatabaseTransactionCommit(DB::Id());
   } else {
      // Otherwise, cancel the transaction
      DatabaseTransactionRollback(DB::Id());
   }
// Close connection to the database
   DB::Close();

// Delete created task, job, and stage objects
   FOREACH(m_tasks)  {
      delete m_tasks[i];
   }
   FOREACH(m_jobs)   {
      delete m_jobs[i];
   }
   FOREACH(m_stages) {
      delete m_stages[i];
   }
}

Die Methoden zum Hinzufügen von Aufträgen und Aufgaben werden in zwei Varianten angegeben. Im ersten Fall werden die Listen mit den Symbolen, Zeitrahmen und Kriterien in String-Parametern durch Kommas getrennt an sie übergeben. Innerhalb der Methode werden diese Zeichenfolgen in Arrays von Werten umgewandelt und beim Aufruf der zweiten Version der Methode, die Arrays akzeptiert, als Argumente ersetzt.

Hier sind die Methoden zum Hinzufügen von Aufgaben:

//+------------------------------------------------------------------+
//| Add new jobs to the database for the specified                   |
//| symbols and timeframes in strings                                |
//+------------------------------------------------------------------+
COptimizationProject* COptimizationProject::AddJobs(string p_symbols, string p_timeframes, 
                                                    TJobsTemplateFunc p_templateFunc) {
// Array of symbols for strategies
   string symbols[];
   StringReplace(p_symbols, ";", ",");
   StringSplit(p_symbols, ',', symbols);

// Array of timeframes for strategies
   string timeframes[];
   StringReplace(p_timeframes, ";", ",");
   StringSplit(p_timeframes, ',', timeframes);

   return AddJobs(symbols, timeframes, p_templateFunc);
}

//+------------------------------------------------------------------+
//| Add new jobs to the database for the specified                   |
//| symbols and timeframes in arrays                                 |
//+------------------------------------------------------------------+
COptimizationProject* COptimizationProject::AddJobs(string &p_symbols[], string &p_timeframes[],
                                                    TJobsTemplateFunc p_templateFunc) {
   // For each symbol
   FOREACH_AS(p_symbols, m_symbol) {
      // For each timeframe
      FOREACH_AS(p_timeframes, m_timeframe) {
         // Get the parameters for work for a given symbol and timeframe
         string params = p_templateFunc(&this);
         
         // Create a new job object
         m_job = new COptimizationJob(0, m_stage, m_symbol, m_timeframe, params);
         
         // Insert it into the optimization database
         m_job.Insert();

         // Add it to the array of all jobs
         APPEND(m_jobs, m_job);
         
         // Add it to the array of current stage jobs
         APPEND(m_stage.jobs, m_job);
      }
   }
   
   return &this;
}

Das dritte Argument ist der Zeiger auf die Funktion zur Erstellung von Optimierungsparametern für die Stage-EAs.


Die Klasse COptimizationStage

Diese Klassenbeschreibung hat viele Eigenschaften im Vergleich zu anderen Klassen, aber das liegt nur daran, dass es mehrere Felder in der Stufentabelle der Optimierungsdatenbank gibt. Für jede von ihnen gibt es eine entsprechende Eigenschaft in dieser Klasse. Beachten Sie auch, dass der Zeiger auf das Projektobjekt (das diese Stufe enthält) und der Zeiger auf das vorherige Stufenobjekt an den Stufenkonstruktor übergeben werden. Für die erste Stufe gibt es keine vorherige Stufe, daher wird in diesem Parameter NULL übergeben.

//+------------------------------------------------------------------+
//| Optimization stage class                                         |
//+------------------------------------------------------------------+
class COptimizationStage {
public:
   ulong             id_stage;
   ulong             id_project;
   ulong             id_parent_stage;
   string            name;
   string            expert;
   string            symbol;
   string            period;
   int               optimization;
   int               model;
   datetime          from_date;
   datetime          to_date;
   int               forward_mode;
   datetime          forward_date;
   int               deposit;
   string            currency;
   int               profit_in_pips;
   int               leverage;
   int               execution_mode;
   int               optimization_criterion;
   string            status;

   COptimizationProject* project;
   COptimizationStage* parent_stage;
   COptimizationJob* jobs[];

                     COptimizationStage(ulong p_idStage, COptimizationProject* p_project, 
                      COptimizationStage* parentStage,
                      string p_name, string p_expertName, 
                      string p_symbol = "GBPUSD", string p_timeframe = "H1",
                      int p_optimization = 0, int p_model = 0,
                      datetime p_fromDate = 0, datetime p_toDate = 0,
                      int p_forwardMode = 0, datetime p_forwardDate = 0,
                      int p_deposit = 10000, string p_currency = "USD",
                      int p_profitInPips = 0, int p_leverage = 200,
                      int p_executionMode = 0, int p_optimizationCriterion = 7,
                      string p_status = "Done") :
                     id_stage(p_idStage),
                     project(p_project),
                     id_project(!!p_project ? p_project.id_project : 0),
                     parent_stage(parentStage),
                     id_parent_stage(!!parentStage ? parentStage.id_stage : 0),
                     name(p_name), expert(p_expertName), symbol(p_symbol),
                     period(p_timeframe), optimization(p_optimization), model(p_model),
                     from_date(p_fromDate), to_date(p_toDate), forward_mode(p_forwardMode),
                     forward_date(p_forwardDate), deposit(p_deposit), currency(p_currency),
                     profit_in_pips(p_profitInPips), leverage(p_leverage), 
                     execution_mode(p_executionMode),
                     optimization_criterion(p_optimizationCriterion), status(p_status) {}

   // Create a stage in the database
   void              Insert();
};

//+------------------------------------------------------------------+
//| Create a stage in the database                                   |
//+------------------------------------------------------------------+
void COptimizationStage::Insert() {
   string query = StringFormat("INSERT INTO stages VALUES("
                               "%s,"  // id_stage
                               "%I64u," // id_project
                               "%s,"    // id_parent_stage
                               "'%s',"  // name
                               "'%s',"  // expert
                               "'%s',"  // symbol
                               "'%s',"  // period
                               "%d,"    // optimization
                               "%d,"    // model
                               "'%s',"  // from_date
                               "'%s',"  // to_date
                               "%d,"    // forward_mode
                               "%s,"    // forward_date
                               "%d,"    // deposit
                               "'%s',"  // currency
                               "%d,"    // profit_in_pips
                               "%d,"    // leverage
                               "%d,"    // execution_mode
                               "%d,"    // optimization_criterion
                               "'%s'"   // status
                               ");",
                               (id_stage == 0 ? "NULL" : (string) id_stage), // id_stage
                               id_project,                           // id_project
                               (id_parent_stage == 0 ?
                                "NULL" : (string) id_parent_stage),  // id_parent_stage
                               name,                            // name
                               expert,                          // expert
                               symbol,                          // symbol
                               period,                          // period
                               optimization,                    // optimization
                               model,                           // model
                               TimeToString(from_date, TIME_DATE),  // from_date
                               TimeToString(to_date, TIME_DATE),    // to_date
                               forward_mode,                    // forward_mode
                               (forward_mode == 4 ?
                                "'" + TimeToString(forward_date, TIME_DATE) + "'"
                                : "NULL"),                      // forward_date
                               deposit,                         // deposit
                               currency,                        // currency
                               profit_in_pips,                  // profit_in_pips
                               leverage,                        // leverage
                               execution_mode,                  // execution_mode
                               optimization_criterion,          // optimization_criterion
                               status                           // status
                              );
   PrintFormat(__FUNCTION__" | %s", query);
   id_stage = DB::Insert(query);
}

Die Aktionen, die im Konstruktor und in der Methode zum Einfügen eines neuen Eintrags in die Stufentabelle durchgeführt werden, sind sehr einfach: Sie merken sich die übergebenen Argumentwerte in den Objekteigenschaften und verwenden sie, um eine SQL-Abfrage zum Einfügen eines Eintrags in die gewünschte Tabelle der Optimierungsdatenbank zu bilden.


Die Klasse COptimizationJob

Diese Klasse ist von der Struktur her identisch mit der Klasse COptimizationStage. Der Konstruktor merkt sich die Parameter, während die Methode Insert() eine neue Zeile in die Jobtabelle der Optimierungsdatenbank einfügt. Außerdem wird der Zeiger auf das Stage-Objekt (das das aktuelle Auftragsobjekt enthalten soll) bei der Erstellung an jedes Auftragsobjekt übergeben. 

//+------------------------------------------------------------------+
//| Optimization job class                                           |
//+------------------------------------------------------------------+
class COptimizationJob {
public:
   ulong             id_job;     // job ID
   ulong             id_stage;   // stage ID
   string            symbol;     // Symbol
   string            timeframe;  // Timeframe
   string            params;     // Optimizer operation parameters
   string            status;     // Status

   COptimizationStage* stage;    // Stage a job belongs to
   COptimizationTask* tasks[];   // Array of tasks related to the job

   // Constructor
                     COptimizationJob(ulong p_jobId, COptimizationStage* p_stage,
                    string p_symbol, string p_timeframe,
                    string p_params, string p_status = "Done");

   // Create a job in the database
   void              Insert();
};

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
COptimizationJob::COptimizationJob(ulong p_jobId,
                                   COptimizationStage* p_stage,
                                   string p_symbol, string p_timeframe,
                                   string p_params, string p_status = "Done") :
   id_job(p_jobId),
   stage(p_stage),
   id_stage(!!p_stage ? p_stage.id_stage : 0),
   symbol(p_symbol),
   timeframe(p_timeframe),
   params(p_params),
   status(p_status) {}

//+------------------------------------------------------------------+
//| Create a job in the database                                     |
//+------------------------------------------------------------------+
void COptimizationJob::Insert() {
// Request to create a second stage job for a given symbol and timeframe
   string query = StringFormat("INSERT INTO jobs "
                               " VALUES (NULL,%I64u,'%s','%s','%s','%s');",
                               id_stage, symbol, timeframe, params, status);
   id_job = DB::Insert(query);
   PrintFormat(__FUNCTION__" | %s -> %I64u", query, id_job);
}

Die letzte verbleibende Klasse COptimizationTask ist auf die gleiche Weise konstruiert, sodass ich ihren Code hier nicht bereitstellen werde.



Umschreiben von CreateProject.mq5

Kehren wir zur Datei CreateProject.mq5 zurück und sehen wir uns ihre wichtigsten Parameter an. Diese Datei befindet sich im Projektbereich, sodass wir für jedes einzelne Projekt die gewünschten Standardparameterwerte darin angeben können, um sie beim Start nicht zu ändern.

Zunächst geben wir den Namen der Optimierungsdatenbank an:

input string fileName_  = "article.17328.db.sqlite"; // - Optimization database file

In der nächsten Gruppe von Parametern geben wir die durch Komma getrennten Symbole und Zeitrahmen an, für die die erste und zweite Stufe der EA-Optimierung durchgeführt werden soll:

input string  symbols_ = "GBPUSD,EURUSD,EURGBP";     // - Symbols
input string  timeframes_ = "H1,M30";                // - Timeframes

Bei dieser Auswahl werden für jede der möglichen Kombinationen aus drei Symbolen und zwei Zeitrahmen sechs Aufträge erstellt.

Als Nächstes folgt die Auswahl des Intervalls, in dem die Optimierung stattfinden soll:

input group "::: Project parameters - Optimization interval"
input datetime fromDate_ = D'2022-09-01';             // - Start date
input datetime toDate_ = D'2023-01-01';               // - End date

In der Gruppe der Kontoparameter wählen wir das Hauptsymbol aus, das in der dritten Phase verwendet wird, wenn der EA mit mehreren Symbolen im Tester arbeiten wird. Die Auswahl wird wichtig, wenn sich unter den Symbolen solche befinden, die auch am Wochenende gehandelt werden (z. B. Kryptowährungen). In diesem Fall müssen wir diesen als den wichtigsten auswählen, da er sonst während des Testlaufs nicht an allen Wochenenden Ticks erzeugt.

input group "::: Project parameters - Account"
input string   mainSymbol_ = "GBPUSD";                // - Main symbol
input int      deposit_ = 10000;                      // - Initial deposit

In der Parametergruppe für die erste Stufe wird der Name der ersten Stage-EA angegeben, der jedoch gleich bleiben kann. Als Nächstes legen wir die Optimierungskriterien fest, die in der ersten Phase für jeden Auftrag verwendet werden sollen. Dies sind nur Zahlen, die durch Kommata getrennt sind. Der Wert 6 entspricht dem Kriterium der Nutzeroptimierung.

input group "::: Stage 1. Search"
input string   stage1ExpertName_ = "Stage1.ex5";      // - Stage EA
input string   stage1Criterions_ = "6,6,6";           // - Optimization criteria for tasks

In diesem Fall haben wir das Nutzerkriterium dreimal angegeben, sodass jeder Auftrag drei Optimierungsprobleme mit dem angegebenen Kriterium enthält.

In der Gruppe der Parameter für die zweite Stufe haben wir die Möglichkeit hinzugefügt, alle Werte der EA-Parameter für die zweite Stufe anzugeben, und nicht nur den Namen und die Anzahl der Strategien in der Gruppe. Diese Parameter beeinflussen die Auswahl der Durchgänge der ersten Stufe, deren Parameter für die Auswahl der Gruppen in der zweiten Stufe verwendet werden.

input group "::: Stage 2. Grouping"
input string   stage2ExpertName_ = "Stage2.ex5";      // - Stage EA
input string   stage2Criterion_  = "6";               // - Optimization criterion for tasks
//input bool     stage2UseClusters_= false;           // - Use clustering?
input double   stage2MinCustomOntester_ = 500;        // - Min value of norm. profit
input uint     stage2MinTrades_  = 20;                // - Min number of trades
input double   stage2MinSharpeRatio_ = 0.7;           // - Min Sharpe ratio
input uint     stage2Count_      = 8;                 // - Number of strategies in the group

Wenn beispielsweise stage2MinTrades_ = 20 ist, können nur die einzelnen Handelsstrategie-Instanzen, die in der ersten Phase mindestens 20 Handelsgeschäfte getätigt haben, der Gruppe beitreten. Der Parameter stage2UseClusters_ wurde vorerst auskommentiert, da wir derzeit keine Clusterung der Ergebnisse der zweiten Stufe verwenden. Sie sollte daher durch false ersetzt werden.

Wir haben auch die Parametergruppe der dritten Stufe um einige Dinge erweitert. Zusätzlich zum Namen des EA der dritten Stufe (der bei einem Projektwechsel ebenfalls nicht geändert werden muss) wurden zwei Parameter hinzugefügt, die die Bildung des Namens der Datenbank des endgültigen EA steuern. Im endgültigen EA selbst wird dieser Name in der Funktion CVirtualAdvisor::FileName() nach der folgenden Vorlage gebildet:

<Project name>-<Magic>.test.db.sqlite // To run in the tester
<Project name>-<Magic>.db.sqlite      // To run on a trading account

Daher verwendet die dritte Stage-EA die gleiche Vorlage: <Projektname> wird durch Projektname_ ersetzt, <Magie> durch stage3Magic_. Der Parameter stage3Tester_ ist für das Hinzufügen des Suffixes „.test“ verantwortlich.

input group "::: Stage 3. Result"
input string   stage3ExpertName_ = "Stage3.ex5";      // - Stage EA
input ulong    stage3Magic_      = 27183;             // - Magic
input bool     stage3Tester_     = true;              // - For the tester?

Im Prinzip wäre es möglich, einen Parameter zu erstellen, der einfach den vollständigen Namen der endgültigen EA-Datenbank angibt. Nach Abschluss der dritten Stufe kann die resultierende Datei dieser Datenbank vor der weiteren Verwendung sicher nach Wunsch umbenannt werden.

Jetzt müssen wir nur noch Funktionen zur Erzeugung von Parametern für Stage-EAs unter Verwendung der vorgegebenen Vorlagen erstellen. Da wir drei Stufen verwenden, benötigen wir drei Funktionen.

In der ersten Phase sieht die Funktion wie folgt aus:

// Template of optimization parameters at the first stage
string paramsTemplate1(COptimizationProject *p) {
   string params = StringFormat(
                      "symbol_=%s\n"
                      "period_=%d\n"
                      "; ===  Open signal parameters\n"
                      "signalSeqLen_=4||2||1||8||Y\n"
                      "periodATR_=21||7||2||48||Y\n"
                      "; ===  Pending order parameters\n"
                      "stopLevel_=2.34||0.01||0.01||5.0||Y\n"
                      "takeLevel_=4.55||0.01||0.01||5.0||Y\n"
                      "; ===  Capital management parameters\n"
                      "maxCountOfOrders_=15||1||1||30||Y\n",
                      p.m_symbol, p.StringToTimeframe(p.m_timeframe));
   return params;
}

Sie basiert auf den Optimierungsparametern der ersten Stage-EA, die vom Strategietester kopiert wurden, wobei die gewünschten Bereiche für die Iteration über die einzelnen Eingabeparameter festgelegt wurden. Diese Zeichenkette wird mit den Werten des Symbols und des Zeitrahmens gefüllt, für die zum Zeitpunkt des Aufrufs dieser Funktion ein Auftragsobjekt im Projekt erstellt wird. Wenn es zum Beispiel für einen bestimmten Zeitraum notwendig ist, andere Bereiche von Eingaben zu verwenden, über die iteriert werden soll, dann kann diese Logik in dieser Funktion implementiert werden.

Beim Wechsel zu einem anderen Projekt mit einer anderen Handelsstrategie sollte diese Funktion durch eine andere ersetzt werden, die für die neue Handelsstrategie und ihre Eingaben geschrieben wurde.

Für die zweite und dritte Stufe haben wir diese Funktionen ebenfalls in der Datei CreateProject.mq5 implementiert. Wenn Sie jedoch zu einem anderen Projekt wechseln, müssen sie höchstwahrscheinlich nicht geändert werden. Aber wir sollten sie nicht gleich in die Bibliotheksabteilung bringen. Sie sollen erst einmal hier bleiben:

// Template of optimization parameters for the second stage
string paramsTemplate2(COptimizationProject *p) {

   // Find the parent job ID for the current job
   // by matching the symbol and timeframe at the current and parent stages
   int i;
   SEARCH(p.m_stage.parent_stage.jobs,
          (p.m_stage.parent_stage.jobs[i].symbol == p.m_symbol
           && p.m_stage.parent_stage.jobs[i].timeframe == p.m_timeframe),
          i);

   ulong parentJobId = p.m_stage.parent_stage.jobs[i].id_job;
   string params = StringFormat(
                      "idParentJob_=%I64u\n"
                      "useClusters_=%s\n"
                      "minCustomOntester_=%f\n"
                      "minTrades_=%u\n"
                      "minSharpeRatio_=%.2f\n"
                      "count_=%u\n",
                      parentJobId,
                      (string) false, //(string) stage2UseClusters_,
                      stage2MinCustomOntester_,
                      stage2MinTrades_,
                      stage2MinSharpeRatio_,
                      stage2Count_
                   );
   return params;
}

// Template of optimization parameters at the third stage
string paramsTemplate3(COptimizationProject *p) {
   string params = StringFormat(
                      "groupName_=%s\n"
                      "advFileName_=%s\n"
                      "passes_=\n",
                      StringFormat("%s_v.%s_%s",
                                   p.name, p.version, TimeToString(toDate_, TIME_DATE)),
                      StringFormat("%s-%I64u%s.db.sqlite",
                                   p.name, stage3Magic_, (stage3Tester_ ? ".test" : "")));
   return params;
}

Als Nächstes folgt der Code für die Initialisierungsfunktion, die die gesamte Arbeit erledigt und den EA vor der Beendigung aus dem Chart entfernt. Zeigen wir es nun mit den Parametern der aufgerufenen Funktionen:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
// Create an optimization project object for the given database
   COptimizationProject p(fileName_);

// Create a new project in the database
   p.Create(projectName_, projectVersion_,
            StringFormat("%s - %s",
                         TimeToString(fromDate_, TIME_DATE),
                         TimeToString(toDate_, TIME_DATE)));


// Add the first stage
   p.AddStage(NULL, "First", stage1ExpertName_, mainSymbol_, "H1", 2, 2,
              fromDate_, toDate_, 0, 0, deposit_);

// Adding the first stage jobs
   p.AddJobs(symbols_, timeframes_, paramsTemplate1);

// Add tasks for the first stage jobs
   p.AddTasks(stage1Criterions_);


// Add the second stage
   p.AddStage(p.m_stages[0], "Second", stage2ExpertName_, mainSymbol_, "H1", 2, 2,
              fromDate_, toDate_, 0, 0, deposit_);

// Add the second stage jobs
   p.AddJobs(symbols_, timeframes_, paramsTemplate2);

// Add tasks for the second stage jobs
   p.AddTasks(stage2Criterion_);


// Add the third stage
   p.AddStage(p.m_stages[1], "Save to library", stage3ExpertName_, mainSymbol_,
              "H1", 0, 2, fromDate_, toDate_, 0, 0, deposit_);

// Add the third stage job
   p.AddJobs(mainSymbol_, "H1", paramsTemplate3);

// Add a task for the third stage job
   p.AddTasks("0");


// Put the project in the execution queue
   p.Queue();

// Delete the EA
   ExpertRemove();

// Successful initialization
   return(INIT_SUCCEEDED);
}

Dieser Teil des Codes muss auch beim Wechsel zu einem anderen Projekt nicht geändert werden, es sei denn, wir wollen die Zusammensetzung der Förderstufen für die automatische Optimierung ändern. Mit der Zeit werden wir sie auch verbessern. Zum Beispiel enthält der Code derzeit numerische Konstanten, die zur besseren Lesbarkeit durch benannte Konstanten ersetzt werden sollten. Wenn sich herausstellt, dass dieser Code wirklich nicht geändert werden muss, werden wir ihn in den Bibliotheksbereich verschieben.

Damit ist der EA für die Erstellung von Optimierungsprojekten in der Datenbank fertig. Lassen Sie uns nun die Stage-EAs erstellen.



Stage-EAs

Wir haben Stage1.mq5 bereits im vorigen Artikel implementiert, sodass wir jetzt nur noch Änderungen vorgenommen haben, die sich auf das Hinzufügen des neuen Parameters maxSpread_ in die Handelsstrategie beziehen. Diese Änderungen wurden bereits oben erörtert.

// 1. Define a constant with the EA name
#define  __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME)

// 2. Connect the required strategy
#include "Strategies/SimpleCandlesStrategy.mqh";

// 3. Connect the general part of the first stage EA from the Advisor library
#include <antekov/Advisor/Experts/Stage1.mqh>

//+------------------------------------------------------------------+
//| 4. Strategy inputs                                               |
//+------------------------------------------------------------------+
sinput string     symbol_              = "";    // Symbol
sinput ENUM_TIMEFRAMES period_         = PERIOD_CURRENT;   // Timeframe for candles

input group "===  Opening signal parameters"
input int         signalSeqLen_        = 6;     // Number of unidirectional candles
input int         periodATR_           = 0;     // ATR period (if 0, then TP/SL in points)

input group "===  Pending order parameters"
input double      stopLevel_           = 25000// Stop Loss (in ATR fraction or points)
input double      takeLevel_           = 3630;  // Take Profit (in ATR fraction or points)

input group "===  Money management parameters"
input int         maxCountOfOrders_    = 9;     // Max number of simultaneously open orders
input int         maxSpread_           = 10;    // Max acceptable spread (in points)


//+------------------------------------------------------------------+
//| 5. Strategy initialization string generation function            |
//|    from the inputs                                               |
//+------------------------------------------------------------------+
string GetStrategyParams() {
   return StringFormat(
             "class CSimpleCandlesStrategy(\"%s\",%d,%d,%d,%.3f,%.3f,%d,%d)",
             (symbol_ == "" ? Symbol() : symbol_), period_,
             signalSeqLen_, periodATR_, stopLevel_, takeLevel_,
             maxCountOfOrders_, maxSpread_
          );
}

In den EAs der zweiten und dritten Stufe müssen wir nur die Konstante __NAME__ mit dem eindeutigen EA-Namen definieren und die Datei oder Dateien der verwendeten Handelsstrategien verbinden. Der Rest des Codes wird aus der Bibliotheksdatei der entsprechenden Stufe übernommen. So könnte der Code für den EA der zweiten Stufe aussehen Stage2.mq5:

// 1. Define a constant with the EA name
#define  __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME)

// 2. Connect the required strategy
#include "Strategies/SimpleCandlesStrategy.mqh";

#include <antekov/Advisor/Experts/Stage2.mqh>

und die dritte Stufe Stage3.mq5:

// 1. Define a constant with the EA name
#define  __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME)

// 2. Connect the required strategy
#include "Strategies/SimpleCandlesStrategy.mqh";

#include <antekov/Advisor/Experts/Stage3.mqh>


Endgültiger EA

Im endgültigen EA müssen wir nur noch die Verbindung zur verwendeten Strategie hinzufügen. Die Konstante __NAME__ brauchen wir hier nicht zu deklarieren, da in diesem Fall sowohl die Konstante als auch die Funktion zur Erzeugung der Initialisierungszeichenfolge in der eingebundenen Datei aus dem Bibliotheksteil deklariert werden. Im folgenden Code haben wir in den Kommentaren gezeigt, wie der EA-Name und die Funktion zur Erzeugung des Initialisierungsstrings in diesem Fall aussehen:

// 1. Define a constant with the EA name
//#define  __NAME__ MQLInfoString(MQL_PROGRAM_NAME)

// 2. Connect the required strategy
#include "Strategies/SimpleCandlesStrategy.mqh";

#include <antekov/Advisor/Experts/Expert.mqh>

//+------------------------------------------------------------------+
//| Function for generating the strategy initialization string       |
//| from the default inputs (if no name was specified).              |
//| Import the initialization string from the EA database            |
//| by the strategy group ID                                         |
//+------------------------------------------------------------------+
//string GetStrategyParams() {
//// Take the initialization string from the new library for the selected group
//// (from the EA database)
//   string strategiesParams = CVirtualAdvisor::Import(
//                                CVirtualAdvisor::FileName(__NAME__, magic_),
//                                groupId_
//                             );
//
//// If the strategy group from the library is not specified, then we interrupt the operation
//   if(strategiesParams == NULL && useAutoUpdate_) {
//      strategiesParams = "";
//   }
//
//   return strategiesParams;
//}

Wenn wir plötzlich etwas daran ändern wollen, dann genügt es, die Kommentare aus diesem Code zu entfernen und die notwendigen Änderungen vorzunehmen.

Im Projektteil befinden sich also die folgenden Dateien:

Kompilieren wir alle Dateien des Projektteils so, dass für jede Datei mit der Endung mq5 eine Datei mit der Endung ex5 erstellt wird.


Alles zusammenfügen

Schritt 1: Ein Projekt erstellen

Ziehen wir den EA CreateProject.ex5 auf einen beliebigen Chart im Terminal (dieser EA muss nicht im Tester ausgeführt werden!). Im EA-Quellcode haben wir bereits versucht, die aktuellen Werte für alle Eingaben anzugeben, sodass wir im Dialog einfach auf OK klicken können.

Abb. 1. Starten der Projekterstellung EA in der Optimierungsdatenbank

Als Ergebnis erhalten wir die Datei article.17328.db.sqlite mit der Optimierungsdatenbank.

Schritt 2: Beginn der Optimierung

Ziehen wir den EA Optimization.ex5 (auch dieser EA muss nicht im Tester ausgeführt werden!) auf einen beliebigen Chart. In dem sich öffnenden Dialogfeld aktivieren wir die Verwendung der DLL und vergewissern uns, dass wir den richtigen Namen der Optimierungsdatenbank angegeben haben:

Abb. 2. Starten des automatischen Optimierungs-EAs

Wenn alles in Ordnung ist, sollten wir etwa Folgendes sehen: Im Testgerät beginnt die Optimierung des EA der ersten Stufe mit dem ersten Symbol-Zeitrahmen-Paar, während wir auf dem Chart mit dem EA Optimization.ex5 Folgendes sehen werden: „Gesamte Aufgaben in der Warteschlange: ..., Aktuelle Aufgaben-ID: ...“.

Abb. 3. Automatische Optimierung des EA-Betriebs.

Anschließend müssen wir einige Zeit warten, bis alle Optimierungsaufgaben abgeschlossen sind. Diese Zeitspanne kann bei einem langen Testintervall und einer großen Anzahl von Symbolen und Zeitfenstern beträchtlich sein. Mit den derzeitigen Standardeinstellungen von 33 Agenten dauerte der gesamte Vorgang etwa vier Stunden. 

Auf der letzten Stufe wird die Optimierung nicht mehr durchgeführt, sondern ein einzelner Durchlauf der dritten Stage-EAs gestartet. Als Ergebnis wird eine Datei mit der Datenbank des endgültigen EA erstellt. Da wir bei der Projekterstellung den Projektnamen „SimpleCandles“ gewählt haben, die magische Zahl 27183 ist und stage3Tester_=true, wird eine Datei mit dem Namen SimpleCandles-27183.test.db.sqlite im gemeinsamen Terminal erstellt. 

Schritt 3: Starten des endgültigen EAs im Tester

Versuchen wir, den endgültigen EA im Testprogramm auszuführen. Da der Code nun vollständig aus dem Bibliotheksteil stammt, werden die Standardparameterwerte auch dort definiert. Wenn wir also den EA SimpleCandles.ex5 im Tester starten, ohne die Werte der Eingaben zu ändern, verwendet er die zuletzt hinzugefügte Strategiegruppe (groupId_= 0) mit aktivierten automatischen Aktualisierungen (useAutoUpdate_= true) aus der Datenbank mit dem Namen SimpleCandles-27183.test.db.sqlite (SimpleCandles EA-Dateiname plus die standardmäßige magische Zahl magic_= 27183 und plus „.test“-Suffix aufgrund der Ausführung im Tester).

Leider haben wir noch keine speziellen Tools entwickelt, die es uns ermöglichen, die vorhandenen Strategiegruppen-IDs in der Datenbank des endgültigen EA einzusehen. Wir können nur die Datenbank selbst in einem beliebigen SQLite-Editor öffnen und sie in der Tabelle strategy_groups anzeigen.

Wenn jedoch nur ein Optimierungsprojekt erstellt und einmal ausgeführt wurde, erscheint in der endgültigen EA-Datenbank nur eine Strategiegruppe mit der ID 1. Daher macht es aus Sicht der Gruppenauswahl keinen Unterschied, ob wir eine bestimmte groupId_= 1 angeben oder groupId_= 0 belassen. In jedem Fall wird die einzige vorhandene Gruppe geladen. Wenn wir dasselbe Projekt noch einmal durchführen (dies kann durch Änderung des Projektstatus direkt in der Datenbank geschehen) oder ein anderes, ähnliches Projekt erstellen und durchführen, werden neue Strategiegruppen in der Datenbank des endgültigen EA erscheinen. In diesem Fall werden verschiedene Gruppen für verschiedene Parameterwerte groupId_ verwendet.

Der Parameter zur Aktivierung der automatischen Aktualisierung (useAutoUpdate_= true) erfordert ebenfalls unsere Aufmerksamkeit. Auch wenn es nur eine Gruppe gibt, wirkt sich dieser Parameter auf die Funktionsweise des endgültigen EA aus. Dies äußert sich darin, dass bei aktivierter automatischer Aktualisierung nur die Strategiegruppen zur Arbeit geladen werden können, deren Erscheinungsdatum kleiner als das aktuelle simulierte Datum ist.

Das heißt, wenn wir den endgültigen Berater in demselben Intervall ausführen, das wir für die Optimierung verwendet haben (2022.09.01 – 2023.01.01), wird unsere einzige Strategiegruppe nicht geladen, da sie das Gründungsdatum 2023.01.01 hat. Daher müssen wir entweder die automatischen Aktualisierungen deaktivieren (useAutoUpdate_= false) und die spezifische ID der verwendeten Handelsstrategiegruppe (groupId_= 1) in den Eingaben angeben, wenn wir den endgültigen EA starten, oder ein anderes Intervall wählen, das nach dem Enddatum des Optimierungsintervalls liegt.

Solange wir noch nicht entschieden haben, welche Strategien im endgültigen EA verwendet werden sollen, und noch nicht das Ziel haben, sie auf die Durchführbarkeit einer regelmäßigen Neuoptimierung zu testen, kann dieser Parameter auf false gesetzt werden und die spezifische ID der verwendeten Handelsstrategiegruppe angeben.

Der letzte Satz wichtiger Parameter ist dafür verantwortlich, welchen Datenbanknamen der endgültige EA verwenden wird. In der Standardeinstellung ist die magische Zahl dieselbe, die wir bei der Erstellung des Projekts in den Einstellungen angegeben haben. Außerdem haben wir den Namen der endgültigen EA-Datei an den Namen des Projekts angepasst. Beim Erstellen des Projekts war der Wert des Parameters stage3Tester_ gleich true, sodass der Dateiname der erstellten Datenbank des endgültigen EA SimpleCandles-27183.test.db.sqlite lautet. Sie stimmt vollständig mit derjenigen überein, die der endgültige SimpleCandles.ex5 EA verwenden wird.

Schauen wir uns die Ergebnisse der Ausführung des endgültigen EA im Optimierungsintervall an:

Abb. 4. Der automatische Optimierungs-EA-Betrieb im Zeitraum 2022.09.01 – 2023.01.01

Wenn wir es in einem anderen Zeitintervall durchführen, werden die Ergebnisse wahrscheinlich nicht so schön sein:

Abb. 5. Die automatische Optimierung EA-Betrieb auf dem Intervall 2023.01.01 – 2023.02.01

Wir haben das Intervall von einem Monat unmittelbar nach dem Optimierungsintervall als Beispiel genommen. In der Tat überstieg der Drawdown leicht den erwarteten Wert von 10 %, und der normalisierte Gewinn verringerte sich um etwa das Fünffache. Ist es möglich, die Optimierung für die letzten drei Monate erneut durchzuführen, um ein ähnliches Bild vom Verhalten des EA im nächsten Monat zu erhalten? Diese Frage bleibt vorerst offen. 

Schritt 4: Starten des fertigen EA auf einem Handelskonto

Um den fertigen EA auf einem Handelskonto auszuführen, müssen wir den Namen der resultierenden Datenbankdatei anpassen. Wir sollten die Endung „.test“ entfernen. Mit anderen Worten, wir benennen SimpleCandles-27183.test.db.sqlite einfach um und kopieren es in SimpleCandles-27183.db.sqlite. Sein Speicherort bleibt derselbe – im gemeinsamen Terminalordner.

Ziehen Sie den fertigen EA SimpleCandles.ex5 auf ein beliebiges Terminal-Chart und legen Sie ihn dort ab. In den Eingaben könnten wir alles mit den Standardwerten belassen, da wir mit dem Laden der letzten Gruppe von Strategien zufrieden sind und das aktuelle Datum natürlich größer ist als das Erstellungsdatum dieser Gruppe.

Abb. 6. Standardeingaben für den endgültigen EA

Während der Vorbereitung des Artikels wurde der fertige EA etwa eine Woche lang auf einem Demokonto getestet und zeigte die folgenden Ergebnisse: 

Abb. 7. Ergebnisse der letzten EA-Operation auf dem Handelskonto

Es war eine ziemlich gute Woche für die EA. Bei einem Drawdown von 1,27 % lag der Gewinn bei etwa 2 %. Der EA wurde aufgrund eines Neustarts des Computers einige Male neu gestartet, stellte aber erfolgreich Informationen über offene virtuelle Positionen wieder her und arbeitete weiter.


Schlussfolgerung

Schauen wir mal, was wir haben. Wir haben endlich die Ergebnisse eines ziemlich langen Entwicklungsprozesses zu etwas zusammengefasst, das einem kohärenten System ähnelt. Das daraus resultierende Tool zur Organisation der Auto-Optimierung und des Testens von Handelsstrategien ermöglicht eine signifikante Verbesserung der Testergebnisse selbst einfacher Handelsstrategien durch Diversifikation über verschiedene Handelsinstrumente.

Außerdem kann die Zahl der Vorgänge, die zum Erreichen der gleichen Ziele einen manuellen Eingriff erfordern, erheblich reduziert werden. Jetzt müssen wir nicht mehr den Abschluss einer weiteren Optimierung verfolgen, bevor wir die nächste starten, und wir müssen nicht mehr darüber nachdenken, wie wir die Zwischenergebnisse der Optimierung speichern und in einen Handels-EA integrieren können. Stattdessen können wir uns direkt auf die Entwicklung der Logik hinter unseren Handelsstrategien konzentrieren.

Natürlich gibt es noch viel zu tun, um dieses Instrument zu verbessern und bequemer zu machen. Die Idee einer vollwertigen Weboberfläche, die nicht nur die Erstellung, den Start und die Überwachung laufender Optimierungsprojekte verwaltet, sondern auch den Betrieb von EAs, die auf verschiedenen Terminals laufen, und die Anzeige ihrer Statistiken, liegt noch in weiter Ferne. Dies ist eine sehr große Aufgabe, aber rückblickend kann man dasselbe über die Aufgabe sagen, die heute bereits mehr oder weniger vollständig gelöst ist.

Vielen Dank für Ihre Aufmerksamkeit! Bis bald!


Wichtige Warnung

Alle in diesem Artikel und in allen vorangegangenen Artikeln dieser Reihe vorgestellten Ergebnisse beruhen lediglich auf historischen Testdaten und sind keine Garantie für zukünftige Gewinne. Die Arbeiten im Rahmen dieses Projekts haben Forschungscharakter. Alle veröffentlichten Ergebnisse können von jedermann auf eigenes Risiko verwendet werden.


Inhalt des Archivs

#
 Name
Version  Beschreibung  Jüngste Änderungen
  MQL5/Experts/Article.17328   Arbeitsordner des Projekts  
1 CreateProject.mq5 1.02
EA-Skript zur Erstellung eines Projekts mit Phasen, Aufträgen und Optimierungsaufgaben.
Teil 25
2 Optimization.mq5
1.00 EA für Projekte Auto-Optimierung  Teil 23
3 SimpleCandles.mq5
1.01 Endgültiger EA für den Parallelbetrieb mehrerer Gruppen von Modellstrategien. Die Parameter werden aus der integrierten Gruppenbibliothek übernommen.
Teil 25
4 Stage1.mq5 1.02  Handelsstrategie Einzelinstanzoptimierung EA (Phase 1)
Teil 25
5 Stage2.mq5
1.01 Handelsstrategien Instanzen Gruppe Optimierung EA (Phase 2)
Teil 25
Stage3.mq5
1.01 Der EA, der eine generierte standardisierte Gruppe von Strategien in einer EA-Datenbank mit einem bestimmten Namen speichert.
Teil 25
  MQL5/Experts/Article.17328/Strategies   Ordner Projektstrategien  
7 SimpleCandlesStrategy.mqh 1.01 SimpleCandles Handelsstrategie Klasse Teil 25
  MQL5/Include/antekov/Advisor/Base
  Basisklassen, von denen andere Projektklassen erben    
8 Advisor.mqh 1.04 EA-Basisklasse Teil 10
9 Factorable.mqh
1.05
Basisklasse von Objekten, die aus einer Zeichenkette erstellt werden
Teil 24
10 FactorableCreator.mqh
1.00   Teil 24
11 Interface.mqh 1.01
Basisklasse zur Visualisierung verschiedener Objekte
Teil 4
12 Receiver.mqh
1.04  Basisklasse für die Umwandlung von offenen Volumina in Marktpositionen
Teil 12
13 Strategy.mqh
1.04
Handelsstrategie-Basisklasse
Teil 10
  MQL5/Include/antekov/Advisor/Database
  Dateien für den Umgang mit allen Arten von Datenbanken, die von Projekt-EAs verwendet werden
 
14 Database.mqh 1.12 Klasse für den Umgang mit der Datenbank Teil 25
15 db.adv.schema.sql 1.00
Endgültige Datenbankstruktur von EA Teil 22
16 db.cut.schema.sql
1.00 Struktur der verkürzten Optimierungsdatenbank
Teil 22
17 db.opt.schema.sql
1.05  Optimierung der Datenbankstruktur
Teil 22
18 Storage.mqh   1.01
Klasse zur Handhabung der Schlüssel-Wert-Speicherung für den endgültigen EA in der EA-Datenbank
Teil 23
  MQL5/Include/antekov/Advisor/Experts
  Dateien mit gemeinsamen Teilen der verwendeten EAs verschiedener Typen
 
19 Expert.mqh  1.22 Die Bibliotheksdatei für den endgültigen EA. Gruppenparameter können aus der EA-Datenbank übernommen werden
Teil 23
20 Optimization.mqh  1.04 Bibliotheksdatei für den EA, der den Start von Optimierungsaufgaben verwaltet
Teil 23
21 Stage1.mqh
1.19 Bibliotheksdatei für die Einzelinstanz der Handelsstrategieoptimierung EA (Stage 1)
Teil 23
22 Stage2.mqh 1.04 Bibliotheksdatei für den EA, der eine Gruppe von Handelsstrategieinstanzen optimiert (Stage 2)   Teil 23
23 Stage3.mqh
1.04 Bibliotheksdatei für den EA, die eine generierte standardisierte Gruppe von Strategien in einer EA-Datenbank mit einem bestimmten Namen speichert. Teil 23
  MQL5/Include/antekov/Advisor/Optimization
  Für die automatische Optimierung zuständige Klassen
 
24 OptimizationJob.mqh 1.00 Optimierung der Projektphase Jobklasse
Teil 25
25 OptimizationProject.mqh 1.00 Optimierungsprojekt Klasse Teil 25
26 OptimizationStage.mqh 1.00 Optimierungsprojekt Stage Klasse Teil 25
27 OptimizationTask.mqh 1.00 Optimierungs-Aufgabenklasse (Erstellung) Teil 25
28 Optimizer.mqh
1.03  Klasse für den Projektautooptimierungsmanager
Teil 22
29 OptimizerTask.mqh
1.03
Optimierungs-Aufgabenklasse (Förderer)
Teil 22
  MQL5/Include/antekov/Advisor/Strategies    Beispiele für Handelsstrategien, die die Funktionsweise des Projekts veranschaulichen
 
30 HistoryStrategy.mqh 
1.00 Klasse der Handelsstrategie für die Wiederholung der Handelshistorie
Teil 16
31 SimpleVolumesStrategy.mqh
1.11
Klasse der Handelsstrategie mit Tick-Volumen
Teil 22
  MQL5/Include/antekov/Advisor/Utils
  Hilfsprogramme, Makros zur Code-Reduzierung

32 ExpertHistory.mqh 1.00 Klasse für den Export der Handelshistorie in eine Datei Teil 16
33 Macros.mqh 1.06 Nützliche Makros für Array-Operationen Teil 25
34 NewBarEvent.mqh 1.00  Klasse zur Definition eines neuen Balkens für ein bestimmtes Symbol  Teil 8
35 SymbolsMonitor.mqh  1.00 Klasse zur Beschaffung von Informationen über Handelsinstrumente (Symbole) Teil 21
  MQL5/Include/antekov/Advisor/Virtual
  Klassen zur Erstellung verschiedener Objekte, die durch ein System virtueller Handelsaufträge und -positionen verbunden sind
 
36 Money.mqh 1.01  Basisklasse Geldmanagement
Teil 12
37 TesterHandler.mqh  1.07 Klasse zur Behandlung von Optimierungsereignissen  Teil 23
38 VirtualAdvisor.mqh  1.10  Klasse des EA, der virtuelle Positionen (Aufträge) bearbeitet Teil 24
39 VirtualChartOrder.mqh  1.01  Grafische virtuelle Positionsklasse Teil 18
40 VirtualHistoryAdvisor.mqh 1.00  Die Klasse des EA zur Wiederholung des Handelsverlaufs  Teil 16
41 VirtualInterface.mqh  1.00  EA GUI-Klasse  Teil 4
42 VirtualOrder.mqh 1.09  Klasse der virtuellen Aufträge und Positionen  Teil 22
43 VirtualReceiver.mqh 1.04 Klasse für die Umwandlung von offenen Volumina in Marktpositionen (Empfänger)  Teil 23
44 VirtualRiskManager.mqh  1.05 Klasse Risikomanagement (Risikomanager)  Teil 24
45 VirtualStrategy.mqh 1.09  Klasse einer Handelsstrategie mit virtuellen Positionen  Teil 23
46 VirtualStrategyGroup.mqh  1.03  Klasse der Handelsstrategien Gruppe(n) Teil 24
47 VirtualSymbolReceiver.mqh  1.00 Symbol-Empfängerklasse  Teil 3

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

Beigefügte Dateien |
MQL5.zip (109.25 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (19)
Rashid Umarov
Rashid Umarov | 10 Juli 2025 in 10:33
Alexey Viktorov #:
Zunächst einmal möchte ich wissen, in welcher Sprache dies ist.

Es ist Koreanisch. Ihr Browser zeigt es aus irgendeinem Grund nicht an.


Alexey Viktorov
Alexey Viktorov | 10 Juli 2025 in 11:20
Rashid Umarov #:

Es ist koreanisch. Ihr Browser zeigt es aus irgendeinem Grund nicht an.


Ganz genau. Ich habe an diesem Tag, dem 2025.07.08, von Anfang an nichts in diesem Thread gepostet. Wenn du dem Link zum Thema folgst, wird ein Beitrag mit einem anderen Datum angezeigt. Wahrscheinlich ist es auch die Schuld meines Browsers, dass Ihre übrigen Programmierer nicht mithalten können.

Rashid Umarov
Rashid Umarov | 11 Juli 2025 in 11:50
Alexey Viktorov #:

Ganz genau. Ich habe an diesem Tag, dem 2025.07.08, von Anfang an nichts in diesem Thread gepostet. Wenn Sie diesem Link zum Thema folgen, sehen Sie einen Beitrag mit einem anderen Datum. Wahrscheinlich ist es auch die Schuld meines Browsers, dass Ihre übrigen Programmierer nicht mithalten können.

Danke für Ihre Hartnäckigkeit, ich habe es behoben.

Alexey Viktorov
Alexey Viktorov | 11 Juli 2025 in 15:50
Rashid Umarov #:

Danke für Ihre Beharrlichkeit, korrigiert.

Sorry für die Hartnäckigkeit, ich sehe keine Lösung. Der Link führt immer noch zu einer seltsamen Meldung, die ich nicht geschrieben habe. Nun, selbst wenn wir davon ausgehen, dass ich sie geschrieben habe, warum steht dann keine Meldung auf Russisch daneben? Oder glaubst du, wenn ich kein Englisch kann, habe ich Koreanisch gelernt und habe Spaß....

Das ist der Unterschied bei einer Diskussion in verschiedenen Sprachen.

Das ist der Link.

Das ist die russische Übersetzung.


Und das ist der Inhalt der russischen Version des Artikels.

In welcher Sprache habe ich also versucht zu schreiben????

Es ist alles ein einziges Thema. Und wenn Sie sich die anderen Themen ansehen, werden Sie Nachrichten seltsamen Ursprungs in Sprachen finden, die ich mir nie hätte träumen lassen.

Alexey Viktorov
Alexey Viktorov | 11 Juli 2025 in 16:10

Vielleicht habe ich überreagiert. Ich habe nur eine weitere ähnliche Meldung gefunden, auf Englisch und wahrscheinlich eine echte Übersetzung.

Bitte löschen Sie die obige Meldung in allen Sprachversionen, und sie wird wahrscheinlich korrigiert werden. Vielleicht nicht ganz so wie beim letzten Mal.......

Forum über Handel, automatisierte Handelssysteme und das Testen von Handelsstrategien

Diskussion des Artikels "Entwicklung eines Multicurrency Expert Advisors (Teil 25): Einfügen einer neuen Strategie (II)"

Rashid Umarov, 2025.07.06 14:04

Danke, wir werden es herausfinden.

Wir haben dieses Problem bereits gelöst, aber es scheint nicht vollständig gelöst zu sein.


Die View- und Controller-Komponenten für Tabellen im MQL5 MVC-Paradigma: Einfache Steuerung Die View- und Controller-Komponenten für Tabellen im MQL5 MVC-Paradigma: Einfache Steuerung
Der Artikel behandelt einfache Steuerelemente als Komponenten von komplexeren grafischen Elementen der View-Komponente im Rahmen der Tabellenimplementierung im MVC-Paradigma (Model-View-Controller). Die Grundfunktionalität des Controllers ist für die Interaktion der Elemente mit dem Nutzer und untereinander implementiert. Dies ist der zweite Artikel über die Komponente View und der vierte in einer Reihe von Artikeln über die Erstellung von Tabellen für das MetaTrader 5 Client Terminal.
Die View Komponente für Tabellen im MQL5 MVC Paradigma: Grafisches Basiselement Die View Komponente für Tabellen im MQL5 MVC Paradigma: Grafisches Basiselement
Der Artikel behandelt den Prozess der Entwicklung eines grafischen Basiselements für die View-Komponente als Teil der Implementierung von Tabellen im MVC-Paradigma (Model-View-Controller) in MQL5. Dies ist der erste Artikel über die Komponente View und der dritte in einer Reihe von Artikeln über die Erstellung von Tabellen für das MetaTrader 5 Client Terminal.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Vom Neuling zum Experten: Automatisierung der Handelsdisziplin mit einem MQL5 Risk Enforcement EA Vom Neuling zum Experten: Automatisierung der Handelsdisziplin mit einem MQL5 Risk Enforcement EA
Für viele Händler ist die Lücke zwischen der Kenntnis einer Risikoregel und deren konsequenter Befolgung der Punkt, an dem die Konten sterben. Emotionale Übertreibungen, Kompensationshandel und einfaches Versehen können selbst die beste Strategie zunichte machen. Heute werden wir die MetaTrader 5-Plattform in einen unnachgiebigen Vollstrecker Ihrer Handelsregeln verwandeln, indem wir einen Risk Enforcement Expert Advisor entwickeln. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.