Entwicklung eines Multi-Currency Expert Advisors (Teil 28): Hinzufügen eines Managers zum Schließen von Positionen
Einführung
In Teil 12 haben wir dem Multi-Currency EA ein Risikomanager-Modul hinzugefügt, um den täglichen und den gesamten Drawdown zu begrenzen. Es erhöht zwar nicht die Gewinne, ist aber entscheidend für den Schutz der Gelder unter widrigen Bedingungen. Es basiert auf Prop-Trading-Regeln mit flexiblen Einstellungen: Drawdown in Währung, als Prozentsatz des Guthabens oder ab Tagesbeginn.
Das Modul ist als Klasse CVirtualRiskManager implementiert, mit Methoden zur Verfolgung von Guthaben, und Gewinn und zur Überprüfung von Begrenzungen. Eine Gewinnmitnahmefunktion ist ebenfalls vorhanden: Sobald das Ziel erreicht ist, werden alle Positionen geschlossen und der Handel gestoppt.
Für reguläre Konten wäre es vorzuziehen, wenn der Handel automatisch neu gestartet würde, sobald das Gewinnziel erreicht ist. Derzeit erfordert dies ein manuelles Eingreifen. Es ist an der Zeit, auch dies zu automatisieren.
Ich habe zwei Optionen für den Neustart von Handelsstrategien beim Erreichen des Zielgewinns in Betracht gezogen:
- Erweiterung des aktuellen Risikomanagers,
- Erstellung eines separaten Moduls.
Ich habe mich für den zweiten Weg entschieden, da der aktuelle Risikomanager unabhängig von den Strategien arbeitet: Er schließt nur reale Positionen, ohne die virtuellen zu beeinflussen. Eine Änderung dieser Logik würde die Architektur verkomplizieren und die modulare Unabhängigkeit verletzen.
Der Risikomanager erzeugt zudem zusätzlichen Testaufwand, daher ist es besser, die neue Funktionalität in ein separates Modul zu verschieben – sie kann auch ohne den laufenden Risikomanager verwendet werden.
Das Ziel ist ein Modul, das alle Strategien automatisch neu starten kann, sobald bestimmte Bedingungen (Gewinn, Verlust, Zeit usw.) erfüllt sind, ohne auf die Handelshistorie angewiesen zu sein und ohne manuelles Eingreifen. Ich werde das neue Modul Closing-Manager nennen, da es ein separates, optionales Modul ist, dessen Hinzufügung jedoch die Ergebnisse verbessern kann und das den Prozess des vollständigen Schließens aller Positionen, sowohl realer als auch virtueller, verwaltet.
Grundanforderungen
Lassen Sie uns die Verantwortlichkeiten und Parameter des Closing-Managers klarer formulieren.
Der Closing-Manager sollte:
- Gewinnmitnahme bedeutet, dass alle virtuellen Positionen geschlossen werden, wenn der festgelegte Gewinn erreicht ist. In diesem Fall werden reale Positionen ebenfalls automatisch geschlossen. Dafür führen wir drei Parameter ein:
- Basisguthaben: Der Geldbetrag auf dem Handelskonto, der als Referenzguthaben für die Berechnung von Gewinn oder Verlust dient.
- Gewinnberechnungsmethode: Sie kann einen von mehreren möglichen Werten annehmen, zum Beispiel als Prozentsatz des Basisguthabens oder als fester Betrag in der Kontowährung .
- Gewinnwert: Der Wert, der zur Berechnung des Gewinns mit der gewählten Methode verwendet wird.
- Verlustbegrenzung, was bedeutet, dass alle virtuellen Positionen geschlossen werden, wenn ein festgelegter Verlust erreicht ist. Dieser Prozess erfordert ebenfalls drei Parameter, von denen einer oder zwei mit den Parametern für die Gewinnmitnahme geteilt werden können:
- Basisguthaben: Der Geldbetrag auf dem Handelskonto, der als Referenzguthaben für die Berechnung von Gewinn oder Verlust dient.
- Verlustberechnungsmethode: Sie kann ebenfalls einen von mehreren möglichen Werten annehmen, wie bei der Methode zur Gewinnberechnung.
- Verlustwert: Der Wert, der zur Berechnung des Verlusts mit der gewählten Methode verwendet wird.
- Profit-Trailing aktivieren – beim Erreichen des festgelegten Gewinns werden virtuelle Positionen nicht geschlossen, sondern ein bestimmtes niedrigeres Gewinnniveau wird gesichert, bei dem die Positionen tatsächlich geschlossen werden. Wenn die Gewinne steigen, sollte auch dieses Niveau steigen. Der Anstieg kann entweder kontinuierlich oder in Schritten mit einem bestimmten Inkrement erfolgen. Für diesen Prozess können die folgenden Parameter hinzugefügt werden:
- Trailing aktivieren (Ja / Nein).
- Niveaueinstellungsmethode: In diesem Parameter können wir die bevorzugte Methode zur Einstellung des Trailing-Aktivierungsniveaus auswählen. Zum Beispiel kann das Niveau als Prozentsatz des festen Gewinns oder als absoluter Wert in der Währung des Handelskontos festgelegt werden.
- Trailing-Startniveau: Die Zahl, die zur Berechnung des Trailing-Startniveaus für die gewählte Methode verwendet wird.
- Schrittweite: Das Niveau, bei dem die Trailing-Schwelle verschoben wird. Für die Berechnung können wir dieselbe Methode wie für das Trailing-Startniveau verwenden.
- Breakeven-Niveau aktivieren – wenn der Gewinn diesen Wert erreicht, sichern wir ein bestimmtes kleines positives Gewinnniveau, bei dem die Positionen geschlossen werden. Bei einem weiteren Gewinnanstieg wird dieses Niveau im Gegensatz zum Trailing nicht steigen. Die Parameter, die diesen Prozess steuern, können wie folgt lauten:
- Breakeven aktivieren (Ja / Nein).
- Niveaueinstellungsmethode: Dieser Parameter ähnelt dem gleichnamigen Parameter für Trailing, was bedeutet, dass er ebenfalls entweder relativ oder absolut sein kann.
- Breakeven-Aktivierungsniveau: Die Zahl, die zur Berechnung des Breakeven-Niveaus für die gewählte Methode verwendet wird.
Projekt-Repository
In Teil 25 haben wir eine neue Strategie hinzugefügt und uns angesehen, wie man ein Projekt erstellt, um die ausgewählte Strategie automatisch zu optimieren und einen endgültigen EA zu erstellen, der mehrere Instanzen von Handelsstrategien mit unterschiedlichen Parametern enthält. Der gesamte Code wurde in zwei Teile unterteilt – Bibliothek und Projekt. Für den Bibliotheksteil enthält Teil 26 bereits das öffentliche Code-Repository Adwizard im Speicher von MQL5-Algo-Forge. Für das Projekt wurde dies jedoch noch nicht getan.
Korrigieren wir dies nun und erstellen das neue Repository SimpleCandles. Dieses Repository wird den Projektteil für die Erstellung des endgültigen EA unter Verwendung von Strategien mit demselben Namen enthalten. Neben dem Branch main werden wir auch einen Entwicklungs-Branch namens develop einführen. Wenn dieses Projekt mehreren Artikeln gewidmet ist, werden die Bearbeitungen, die sich auf verschiedene Artikel beziehen, auf verschiedene Branches verteilt, die aus dem Branch develop generiert werden. Sobald sie fertig sind, werden sie wieder in die Branches develop und main zusammengeführt.
Erstellen wir einen lokalen Ordner, der den Projektordner enthält, zum Beispiel MQL5/Experts/Articles/17608. Wir klonen dieses Repository in den ausgewählten Ordner und erstellen darin den Ordner Include. In diesem Ordner platzieren wir das Repository des Bibliotheksteils, von dem dieses Projekt abhängt. Der Ordner Include enthält den Klon des Repositorys der Adwizard-Bibliothek.
Schließlich erhalten wir ungefähr die folgende Ordnerstruktur im Terminalordner:

Abb. 1. Die Ordnerstruktur im Projekt-Repository nach dem Klonen der Projekt- und Bibliotheksteile
Wechseln wir im geklonten Ordner des Adwizard-Repositorys, zum Branch develop. Dieser Branch wird gemeinsam von allen Artikeln verwendet. Da wir jedoch an diesem Projekt arbeiten, werden wir Änderungen an der Adwizard-Bibliothek vornehmen. Daher werden wir in diesem Repository einen neuen Branch erstellen, der aus dem develop-Branch generiert wird.
Erstellen wir danach einen separaten Branch für die Arbeit an diesem Artikel im SimpleCandles-Projekt-Repository und beginnen Sie mit der Entwicklung.
Vorbereiten des Bibliothekscodes
Bereiten wir den Boden für die Implementierung des Closing-Managers. Zunächst sollten wir anmerken, dass die neuesten MetaTrader-Builds eine strengere Überprüfung der Variablentypen hinzugefügt haben, weshalb zuvor kompilierter Code nun Fehler des folgenden Typs erzeugt:
parameter convertion type 'short[260]' to 'ushort[] &' is not allowed MTTester.mqh int user32::GetClassNameW(long,ushort&[],int) winuser.mqh
Glücklicherweise trat dies im verwendeten Code nur einmal auf und wurde durch Ändern des Array-Typs behoben:
static string GetClassName( const HANDLE Handle ) { string Str = NULL; ushort Buffer[MAX_PATH] = {0}; if (user32::GetClassNameW(Handle, Buffer, ::ArraySize(Buffer))) Str = ::ShortArrayToString(Buffer); return(Str); }
Nach dem nächsten Terminal-Update wurde diese Datei jedoch vollständig durch die neueste Version aus der MultiTester-Bibliothek ersetzt, um ein fehlerhaftes Verhalten zu beheben, das eine andere Ursache hat.
Die nächste Änderung bezieht sich auf die Notwendigkeit für den Closing-Manager, das Schließen aller Positionen zu initiieren. Fügen wir der EA-Klasse CVirtualAdvisor eine separate Methode zum Schließen aller Positionen hinzu, damit der Closing-Manager sie bei Bedarf aufrufen kann.
Um diese Methode zu implementieren, haben wir bereits alles, was wir brauchen: Jede von CVirtualStrategy abgeleitete Strategie verfügt über eine Methode zum Schließen all ihrer virtuellen Positionen. Daher müssen wir in der EA-Klasse diese Methode nur für jede Strategie aufrufen:
//+------------------------------------------------------------------+ //| Close positions of all strategies | //+------------------------------------------------------------------+ void CVirtualAdvisor::Close(void) { // For all strategies, we call the method for closing virtual positions FOREACH(m_strategies) ((CVirtualStrategy *)m_strategies[i]).Close(); }
In Teil 27 haben wir eine Komponente zur Anzeige von mehrzeiligem Text in einem Fenster erstellt, das sich über das gesamte Chart erstreckt, an das der EA angehängt ist. Sie wurde als Teil eines anderen Projekts erstellt, wird uns hier aber ebenfalls nützlich sein. Verschieben wir sie also in die Adwizard-Bibliothek und legen die Datei mit der CConsoleDialog-Klasse im Ordner Adwizard/Utils ab. Um sie zu verwenden, fügen wir die Erstellung eines Objekts dieser Klasse in der Datei Adwizard/Experts/Expert.mqh der EA-Klasse hinzu:
CConsoleDialog *dialog; // Dialog for displaying text with results //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // ... // Create and launch a dialog to display the results dialog = new CConsoleDialog(); dialog.Create(__NAME__ + ":" + (string) magic_); dialog.Run(); // Successful initialization return(INIT_SUCCEEDED); }
In der Funktion zur Verarbeitung eines neuen Ticks in derselben Datei fügen wir das Setzen von neuem Text für dieses Objekt hinzu. Den Text selbst erhalten wir aus der CVirtualAdvisor-Klasse, indem wir deren Text()-Methode aufrufen, die wir im weiteren Verlauf implementieren werden:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { expert.Tick(); // ... // Display text with information about the EA operation if (IsNewBar(Symbol(), PERIOD_M1)) { dialog.Text(expert.Text()); } }
Um zu verhindern, dass die Linien für das Öffnen virtueller Positionen vor dem Hintergrund des Textes gezeichnet werden, deaktivieren wir vorübergehend deren Visualisierung, indem wir die CVirtualChartOrder::Show()-Methode leer lassen:
//+------------------------------------------------------------------+ //| Show virtual position (order) | //+------------------------------------------------------------------+ void CVirtualChartOrder::Show() { return; // ... }
IsActive-Eigenschaft für alle Nachfahren von CFactorable
Wenn eine Optimierung bei Handelsinstrumenten durchgeführt wird, die Kryptowährungen beinhalten, und der Start bei einem Broker erfolgt, der keine Kryptowährungen unterstützt, kann beim Starten des endgültigen EAs ein Fehler auftreten. Dies beinhaltet den Versuch, die Handelshistorie und die Eigenschaften eines Symbols abzurufen, das nicht in der Marktübersicht enthalten ist. In diesem Fall, wenn der endgültige EA eine große Anzahl von Handelsstrategie-Instanzen enthält, die auf den verfügbaren Symbolen arbeiten, können wir einfach Strategien für die Instrumente deaktivieren, die nicht in der Marktübersicht enthalten sind.
Derzeit sind alle Handelsstrategien Nachfahren der Klasse CFactorable, was es ermöglicht, Objekte dieser Strategien aus dem Initialisierungs-String zu erstellen. Diese Klasse berücksichtigt die Möglichkeit, dass der Initialisierungs-String möglicherweise nicht vollständig korrekt ist. Dann werden dieses Objekt und alle vorherigen Objekte aus dem gemeinsamen Initialisierungs-String als ungültig betrachtet. In dieser Situation wird der EA nicht in der Lage sein, zu initialisieren und seine Arbeit fortzusetzen.
Was wir uns wünschen, ist, dass eine bestimmte Art von „Fehler“ im Initialisierungs-String es uns ermöglicht, einen Teil des Initialisierungs-Strings einfach zu ignorieren und letztendlich ein EA-Objekt aus dem vollständigen Initialisierungs-String zu erstellen. Um dies zu erreichen, fügen wir der Klasse CFactorable eine neue Eigenschaft namens m_isActive sowie die Methode IsActive() zum Lesen ihres Wertes hinzu:
//+------------------------------------------------------------------+ //| Base class of objects created from a string | //+------------------------------------------------------------------+ class CFactorable { private: // ... protected: // ... bool m_isActive; // Is the object active? // ... public: // ... bool IsActive(); // Is the object active? // ... };
Eine solche Eigenschaft existierte bereits für einige Klassen, wie z. B. die CVirtualRiskManager Risikomanager-Klasse. Daher werden wir in diesen Klassen ihre Deklaration entfernen, da sie in der Basisklasse vorgenommen wird. Dies gilt auch für die zukünftige Closing-Manager-Klasse, die diese Eigenschaft ebenfalls verwenden wird, um zu prüfen, ob sie aktiv ist.
Gleichzeitig haben wir die Angabe des Risikomanagers und des Closing-Managers im Initialisierungs-String optional gemacht, indem wir eine Überprüfung auf deren Vorhandensein beim Starten des EA im Konstruktor der Klasse CVirtualAdvisor hinzugefügt haben:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CVirtualAdvisor::CVirtualAdvisor(string p_params) { // Save the initialization string m_params = p_params; // Read the initialization string of the strategy group object string groupParams = ReadObject(p_params); // Read the initialization string of the risk manager object string riskManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualRiskManager")) { riskManagerParams = ReadObject(p_params); } // Read the initialization string of the closing manager object string closeManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualCloseManager")) { closeManagerParams = ReadObject(p_params); } // Read the magic number ulong p_magic = ReadLong(p_params); // Read the EA name string p_name = ReadString(p_params); // Read the work flag only at the bar opening m_useOnlyNewBar = (bool) ReadLong(p_params); // If there are no read errors, if(IsValid()) { // Create a strategy group CREATE(CVirtualStrategyGroup, p_group, groupParams); // Initialize the symbol monitor with a static symbol monitor m_symbols = CSymbolsMonitor::Instance(); // Initialize the receiver with the static receiver m_receiver = CVirtualReceiver::Instance(p_magic); // Initialize the interface with the static interface m_interface = CVirtualInterface::Instance(p_magic); // Form the name of the EA database file for saving the state from the EA name and parameters m_fileName = FileName(p_name, p_magic); // Save the work (test) start time m_fromDate = TimeCurrent(); // Reset the last save time m_lastSaveTime = 0; // Add the contents of the group to the EA Add(p_group); // Remove the group object delete p_group; // Create the risk manager object if(riskManagerParams != NULL) { m_riskManager = NEW(riskManagerParams); } // Create the closing manager object if(closeManagerParams != NULL) { m_closeManager = NEW(closeManagerParams); m_closeManager.Expert(&this); } } }
Nachdem wir die Änderungen vorgenommen haben, kommen wir zum Hauptteil – der Erstellung des Closing-Managers.
Erstellung des Closing-Managers
Zunächst wollen wir einige mögliche Zustände hervorheben, in denen sich der Closing-Manager befinden kann. In einem normalen Zustand wurden weder der geplante Gewinn noch der maximale Verlust erreicht. Während er sich in diesem Zustand befindet, muss der Closing-Manager nur auf den Übergang in einen von mehreren nachfolgenden Zuständen warten. Beim Erreichen eines festgelegten Gewinns oder Verlusts wird ein Übergang in zwei entsprechende Zustände durchgeführt. In diesen Zuständen sollte der Closing-Manager alle Positionen schließen, sich die neuen Niveaus des festgelegten Gewinns und Verlusts merken und in den normalen Zustand zurückkehren.
Wenn das Trailing aktiviert ist, schaltet der Closing-Manager beim Erreichen des festgelegten Gewinnniveaus in einen anderen Zustand um. Der Übergang zurück in einen normalen Zustand erfordert komplexere Aktionen, daher werden wir diese vorerst nicht im Detail beschreiben.
Wir implementieren alle Zustände als Aufzählungstyp ENUM_CM_STATE.
Um die Methoden zur Berechnung von geplantem Gewinn und Verlust zu definieren, erstellen wir außerdem zwei separate Aufzählungstypen: ENUM_CM_CALC_LOSS und ENUM_CM_CALC_PROFIT. Betrachten wir zwei Optionen: einen festen Wert in monetären Einheiten und einen relativen Wert als Prozentsatz eines bestimmten Basisguthabens.
// Possible states of the closing manager enum ENUM_CM_STATE { CM_STATE_OK, // Limits are not exceeded CM_STATE_LOSS, // Overall limit exceeded CM_STATE_PROFIT, // Total profit reached CM_STATE_TRAIL_PROFIT // Profit trailing }; // Possible methods for calculating total loss enum ENUM_CM_CALC_LOSS { CM_CALC_LOSS_MONEY_BB, // [$] Fixed Money CM_CALC_LOSS_PERCENT_BB, // [%] of Base Balance }; // Possible methods for calculating total profit enum ENUM_CM_CALC_PROFIT { CM_CALC_PROFIT_MONEY_BB, // [$] Fixed Money CM_CALC_PROFIT_PERCENT_BB, // [%] of Base Balance };
Die Closing-Manager-Klasse selbst wird von der Basisklasse CFactorable abgeleitet, um die Möglichkeit zu bieten, das Closing-Manager-Objekt aus der Initialisierungszeichenfolge zu erstellen. Gleichzeitig verfügt sie sofort über eine vererbte Aktivitätseigenschaft, um den Closing-Manager einfach zu aktivieren oder zu deaktivieren.
Um diese Aufgabe ausführen zu können, muss sich der Closing-Manager das Niveau des Basisguthabens merken, von dem aus der resultierende Gewinn oder Verlust berechnet wird. Beim Realisieren eines Gewinns oder Verlusts sollte sich dieses Niveau ebenfalls auf den Wert des aktuellen Guthabens ändern, der nach dem Schließen aller Positionen erreicht wurde. Dies ist der Unterschied zwischen diesem Parameter und dem gleichnamigen Parameter im Risikomanager. Dort bleibt das Niveau des Basisguthabens immer unverändert.
Die nächste Gruppe von Eigenschaften wird verwendet, um die Berechnungsmethode und die Berechnung des geplanten Gewinns und Verlusts selbst auszuwählen. Sie werden in den Berechnungsmethoden LossMoney() und ProfitMoney() verwendet, die einen Wert in monetären Einheiten zurückgeben.
Um Positionen zu schließen, sollte der Closing-Manager in der Lage sein, das Objekt des EAs zum Schließen aufzufordern. Daher fügen wir der Liste der Eigenschaften des Closing-Managers den Zeiger auf das EA-Objekt und die Methode zu dessen Festlegung hinzu.
Wir fügen eine weitere Eigenschaft hinzu, um den aktuellen Status des Closing-Manager-Objekts zu speichern.
Die Erstellung auf Basis von CFactorable erfordert, dass der Konstruktor in einem privaten Bereich platziert wird und zwei spezielle Makros hinzugefügt werden, wie in Teil 24 beschrieben.
Als Ergebnis erhalten wir eine Beschreibung der Closing-Manager-Klasse, die in etwa so aussieht:
//+------------------------------------------------------------------+ //| Closing manager class (profit and loss taking) | //+------------------------------------------------------------------+ class CVirtualCloseManager : public CFactorable { protected: // Main constructor parameters double m_baseBalance; // Base balance ENUM_CM_CALC_LOSS m_calcLossLimit; // Method of calculating the maximum overall loss double m_maxLossLimit; // Parameter of calculating the maximum total loss ENUM_CM_CALC_PROFIT m_calcProfitLimit; // Method for calculating maximum overall profit double m_maxProfitLimit; // Parameter for calculating the maximum overall profit CVirtualAdvisor* m_expert; // Pointer to the EA object // Current state ENUM_CM_STATE m_state; // State // Updated values double m_balance; // Current balance double m_equity; // Current equity double m_profit; // Current floating profit double m_overallProfit; // Current total profit relative to base balance // Protected methods double LossMoney(); // Maximum total loss double ProfitMoney(); // Maximum profit void UpdateProfit(); // Update current profit values void CheckLimits(); // Check whether acceptable profit/loss levels have been achieved CVirtualCloseManager(string p_params); // Private constructor public: STATIC_CONSTRUCTOR(CVirtualCloseManager); // Static object creation method virtual void Tick(); // Handle tick in the closing manager virtual string Text(); // Information about the current state // Bind the EA to the closing manager void Expert(CVirtualAdvisor* p_expert); virtual bool Save(); // Save status virtual bool Load(); // Load status virtual string operator~() override; // Convert object to string }; REGISTER_FACTORABLE_CLASS(CVirtualCloseManager); // Register a new CFactorable child
Betrachten wir die beiden Hauptmethoden der Klasse: den Konstruktor und die Methode zur Tick-Verarbeitung.
Im Konstruktor lesen wir wie üblich die Parameterwerte nacheinander aus der Initialisierungszeichenfolge aus und weisen sie den entsprechenden Eigenschaften zu, setzen den aktuellen Status auf normal, aktualisieren die aktuellen Gewinnwerte und speichern den aktuellen Guthaben als Basiswert, falls dieser nicht direkt festgelegt wurde:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CVirtualCloseManager::CVirtualCloseManager(string p_params) { // Save the initialization string m_params = p_params; // Read the initialization string and set the property values m_isActive = (bool) ReadLong(p_params); m_baseBalance = ReadDouble(p_params); m_calcLossLimit = (ENUM_CM_CALC_LOSS) ReadLong(p_params); m_maxLossLimit = ReadDouble(p_params); m_calcProfitLimit = (ENUM_CM_CALC_PROFIT) ReadLong(p_params); m_maxProfitLimit = ReadDouble(p_params); // Set the state: Limits are not exceeded m_state = CM_STATE_OK; // Update the current profit values UpdateProfit(); // Adjust the base balance if it is not set if(m_baseBalance == 0) { m_baseBalance = m_balance; } }
In der grundlegenden Tick-Verarbeitungsmethode analysieren wir den aktuellen Status und prüfen je nach Status entweder, ob die Zielgewinn- oder Verlustniveaus erreicht wurden, wenn sich der Manager in einem normalen Status befand, oder wir leiten das Schließen aller Positionen ein und gehen anschließend in einen normalen Status über:
//+------------------------------------------------------------------+ //| Tick processing in the risk manager | //+------------------------------------------------------------------+ void CVirtualCloseManager::Tick() { // If the risk manager is inactive, exit if(!m_isActive) { return; } // Update the current profit values UpdateProfit(); // If the manager is in the trailing state, if(m_state == CM_STATE_TRAIL_PROFIT) { // immediately take the profit // switching the manager to the corresponding state if(true) { m_state = CM_STATE_PROFIT; } } // If the manager is in normal condition, if(m_state == CM_STATE_OK) { // Check for exceeding loss and profit limits CheckLimits(); } // If the manager is in a state of achieved loss or profit, if(m_state == CM_STATE_LOSS || m_state == CM_STATE_PROFIT) { // Close all positions m_expert.Close(); // If all positions are closed, if(PositionsTotal() == 0) { // Switch to normal state m_state = CM_STATE_OK; // Update the base balance value m_baseBalance = m_balance; } else { // Wait for all positions to close } // Save the EA state m_expert.Save(); } }
Zunächst haben wir beschlossen, uns auf diese Funktionalität des Closing-Managers zu beschränken, daher wird der Profit-Trailing-Status vorerst nicht verwendet.
Eingabeparameter übergeben
Nachdem wir die Closing-Manager-Klasse erstellt haben, müssen wir sie mit dem EA verbinden. Dazu müssen wir Eingaben hinzufügen, über die wir die Erstellung der Initialisierungszeichenfolge für den Closing-Manager steuern können. Dies muss in der Datei Adwizard/Experts/Expert.mqh erfolgen:
// ... //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "::: Use a strategy group" sinput int groupId_ = 0; // - ID of the group from the new library (0 - last) sinput bool useAutoUpdate_ = true; // - Use auto update? input group "::: Money management" sinput double expectedDrawdown_ = 10; // - Maximum risk (%) sinput double fixedBalance_ = 10000; // - Used deposit (0 - use all) in the account currency input double scale_ = 1.00; // - Group scaling multiplier input group "::: Closing manager" input bool cmIsActive_ = true; // - Active? input double cmStartBaseBalance_ = 0; // - Basic balance input ENUM_CM_CALC_LOSS cmCalcLossLimit_ = CM_CALC_LOSS_MONEY_BB; // - Loss calculation method input double cmLossLimit_ = 100; // - Threshold loss value input ENUM_CM_CALC_PROFIT cmCalcProfitLimit_ = CM_CALC_PROFIT_MONEY_BB; // - Method for calculating total profit input double cmProfitLimit_ = 1000000; // - Profit target // ...
Diese Datei wird beim Kompilieren des finalen EAs eingebunden, sodass die hinzugefügten Eingabeparameter darin verfügbar werden. Standardmäßig legen wir fest, dass die Werte für Gewinnmitnahme und Verlust in Geld (in der Kontowährung ) angegeben werden. Wir müssen die Werte selbst noch auswählen, daher ist das, was in den Standardwerten angegeben ist, vorerst nicht wichtig.
Erste Tests
Schauen wir mal, was wir erhalten haben. Überprüfen wir zunächst die korrekte Funktionsweise des Mechanismus zum Schließen von Positionen ohne Berücksichtigung des erzielten Gewinns. Wenn alles korrekt funktioniert, können wir im nächsten Schritt mit der Optimierung des erzielten Gewinns beginnen.
Verwenden wir die Datenbank des finalen EAs aus Teil 25, um den finalen EA im Tester zu starten. Wir haben dann eine beschleunigte Optimierung über mehrere Intervalle von jeweils 1 Jahr durchgeführt und zwölf Strategiegruppen erhalten, die in der Tabelle strategy_groups gespeichert sind:

Die Datenbankdatei hieß SimpleCandles-27183.test.db.sqlite. Damit der finale EA diese Datenbank verwenden kann, muss sich diese Datei im gemeinsamen Datenordner der MetaTrader 5-Terminals im Unterordner Files befinden. Zudem sollte der finale EA SimpleCandles.ex5 heißen, und der Wert der Magic Number 27183 sollte in den Eingabeparametern beibehalten werden.
Starten wir zunächst den EA ohne Verwendung des Closing-Managers mit der allerersten Strategiegruppe mit id_group=20. Stellen wir dazu die folgenden Werte für die Eingabeparameter ein:

Wir verwenden denselben Zeitraum, in dem die automatische Optimierung durchgeführt wurde, als Testzeitraum, d. h. das gesamte Jahr 2022. Wir erhalten die folgenden Ergebnisse:


Abb. 2. Ergebnisse des finalen EAs mit id_group=20 ohne Closing-Manager für 2022
Wie Sie sehen, hat die Optimierung recht gute Parameterkombinationen für verschiedene Instanzen einfacher Handelsstrategien gefunden, um einen signifikanten Gewinn über einen bestimmten Zeitraum zu erzielen und dabei innerhalb des festgelegten Drawdowns von 10 % zu bleiben.
Schalten wir nun den Closing-Manager ein und setzen einen kleinen Wert, zum Beispiel 10 USD, als erwarteten Gewinn für die Gewinnmitnahme fest:

Lassen Sie uns den EA im visuellen Testmodus ausführen. Da wir die Anzeige der Betriebsdaten zum endgültigen EA hinzugefügt haben, können wir in diesem Modus sehen, welche Symbole und wie viele Strategien in der Gruppe mit der ID 20 aus der EA-Datenbank verwendet werden (drei Symbole GBPUSD, EURUSD, EURGBP und 48 Strategien), den Wert des Basisguthabens des Closing-Managers sowie das Zielniveau für Gewinn und Verlust für das Schließen.

Abb. 3. Starten des EA-Visualtests mit aktiviertem Closing-Manager
Abbildung 3 zeigt, dass der Basisguthaben des Closing-Managers bereits 10009.89 USD erreicht hat, was bedeutet, dass alle Positionen geschlossen wurden, sobald der Zielgewinn von 10 USD erreicht war.
Wir sehen die folgende Zeile im Protokoll:
2022.01.03 02:31:00 CVirtualCloseManager::CheckLimits | CLOSE PROFIT Profit = 12.94 | OverallProfit = 10.54 (10.00)
Der Closing-Manager wurde ausgelöst, als der Gesamtgewinn (OverallProfit = 10.54) relativ zum anfänglichen Basisguthaben von 10.000 USD 10 USD überstieg. Aufgrund des Testmodus nur zu Beginn jedes Minuten-Bars (1 Minute OHLC) erstreckte sich das Schließen aller offenen Positionen über zwei benachbarte Minuten, sodass das aufgezeichnete neue Basisniveau etwas niedriger als 10.010 USD ausfiel. Wir beobachten solche Diskrepanzen nicht mehr, wenn der Modus für jeden Tick aktiviert ist.
Testen wir nun den Closing-Manager zur Verlustbegrenzung . Lassen Sie uns einen kleinen Wert für die Verlusttoleranz festlegen, zum Beispiel 20 USD, während wir den Wert für die Gewinnmitnahme groß wählen, sodass wir höchstwahrscheinlich einen Verlust erleiden, anstatt einen Gewinn zu erzielen.

Bei anderen Parametern deaktivieren wir die Ausführung nur zur Bar-Eröffnung, sodass der EA bei aktiviertem Simulationsmodus für jeden Tick alle erforderlichen Aktionen bei jedem Tick ausführt und nicht nur zu Beginn des Minuten-Bars:

Wir starten den Test für ein kurzes Intervall von einem Tag (2022.01.03). Durch Filtern der Protokollmeldungen wählen wir nur die Zeilen aus, die angezeigt werden, wenn der festgelegte Verlust von 20 USD erreicht ist:
2022.01.03 17:11:33 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -33.13 | OverallProfit = -20.06 (-20.00)
2022.01.03 17:30:39 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -20.51 | OverallProfit = -20.51 (-20.00)
2022.01.03 19:13:31 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -21.20 | OverallProfit = -20.11 (-20.00)
Wir können sehen, dass dies während des Testtages dreimal aufgetreten ist. Im Modus für jeden Tick liegt der Gesamtgewinn (Overall Profit), der das Schließen von Positionen beim Erreichen eines festgelegten Verlusts auslöst, viel näher an dem in den Parametern angegebenen Wert.
Bitte beachten Sie, dass der erste Protokolleintrag oben den folgenden Teil enthält:
Profit = -33.13
Dies ist der Wert des aktuellen Gewinns bei offenen Positionen (ein negativer Gewinn ist ein Verlust). In diesem Fall unterscheidet er sich vom Wert von USD -20, da mehrere Positionen ursprünglich mit einem Gewinn von etwa USD 13 geschlossen wurden. Daher wurde ein Verlust von USD 20 relativ zum anfänglichen Basisguthaben mit genau diesem Gewinnwert bei offenen Positionen erzielt.
Die ersten Tests haben also gezeigt, dass der entwickelte Closing-Manager den grundlegenden Teil seiner Arbeit bereits ausführen kann.
Schlussfolgerung
Wir machen hier eine kurze Pause und werden die Entwicklung des Closing-Managers in einem der nächsten Teile fortsetzen. Die Pläne für die weitere Entwicklung seiner Funktionalität umfassen in erster Linie das Hinzufügen einer Profit-Trailing-Funktion für offene Positionen sowie die Möglichkeit, ein Breakeven-Niveau festzulegen.
Die Verbesserungen enden hier jedoch nicht. Zum Beispiel prüft der Closing-Manager derzeit, ob alle Positionen geschlossen wurden, indem er einfach darauf wartet, dass die Anzahl der offenen Positionen Null erreicht. Dies kann jedoch auch geschehen, wenn offene virtuelle Positionen vorhanden sind. Lassen Sie uns also prüfen, ob wir hier eine zuverlässigere Überprüfungsmethode benötigen. Möglicherweise müssen wir auch die Interaktion zwischen dem Risikomanager und dem Closing-Manager organisieren: Beim Schließen sollte der Status des Risikomanagers aktualisiert werden und umgekehrt.
Dennoch ist die erste Version damit fertiggestellt, und der nächste Schritt wird nicht bei Null beginnen.
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 |
|---|---|---|---|---|
| SimpleCandles | Arbeitsordner des Projekts (sollte sich innerhalb von MQL5/Experts befinden) | |||
| 1 | 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 |
| └ Optimization | Ordner für Projektoptimierungs-EAs | |||
| 2 | CreateProject.mq5 | 1.02 | EA-Skript zur Erstellung eines Projekts mit Phasen, Aufträgen und Optimierungsaufgaben. | Teil 25 |
| 3 | Optimization.mq5 | 1.00 | EA für die automatische Optimierung von Projekten | |
| 4 | Stage1.mq5 | 1.02 | Handelsstrategie Einzelinstanzoptimierung EA (Phase 1) | Teil 25 |
| 5 | Stage2.mq5 | 1.01 | EA zur Optimierung einer Gruppe von Handelsstrategie-Instanzen (Phase 2) | Teil 25 |
| 6 | Stage3.mq5 | 1.01 | Der EA, der eine generierte standardisierte Gruppe von Strategien in einer EA-Datenbank mit einem bestimmten Namen speichert. | Teil 25 |
| └ Strategies | Ordner Projektstrategien | Teil 25 | ||
| 7 | SimpleCandlesStrategy.mqh | 1.01 | Handelsstrategieklasse SimpleCandles | Teil 25 |
| └ Include/Adwizard | Bibliotheksordner Adwizard | |||
| └ Base | Basisklassen, von denen andere Projektklassen erben | |||
| 8 | Advisor.mqh | 1.04 | EA-Basisklasse | Teil 10 |
| 9 | Factorable.mqh | 1.06 | Basisklasse von Objekten, die aus einer Zeichenkette erstellt werden | Teil 28 |
| 10 | FactorableCreator.mqh | 1.00 | Klasse von Erzeugern, die Namen und statische Konstruktoren von CFactorable-Nachfolgeklassen binden | 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 |
| └ 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 | Struktur der Optimierungsdatenbank | 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 |
| └ Experts | Dateien mit gemeinsamen Teilen der verwendeten EAs verschiedener Typen | |||
| 19 | Expert.mqh | 1.24 | Die Bibliotheksdatei für den endgültigen EA. Gruppenparameter können aus der EA-Datenbank übernommen werden | Teil 28 |
| 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 |
| └ Optimization | Für die automatische Optimierung zuständige Klassen | |||
| 24 | OptimizationJob.mqh | 1.00 | Jobklasse für die Optimierungsphase des Projekts | Teil 25 |
| 25 | OptimizationProject.mqh | 1.00 | Klasse für ein Optimierungsprojekt | Teil 25 |
| 26 | OptimizationStage.mqh | 1.00 | Klasse für eine Optimierungsprojektphase | Teil 25 |
| 27 | OptimizationTask.mqh | 1.00 | Optimierungsaufgabenklasse (Erstellung) | Teil 25 |
| 28 | Optimizer.mqh | 1.03 | Klasse für den Projektautooptimierungsmanager | Teil 22 |
| 29 | OptimizerTask.mqh | 1.03 | Optimierungsaufgabenklasse (Pipeline) | Teil 22 |
| └ Strategies | Beispiele für Handelsstrategien, die die Funktionsweise des Projekts veranschaulichen | |||
| 24 | HistoryStrategy.mqh | 1.00 | Klasse der Handelsstrategie für die Wiederholung der Handelshistorie | Teil 16 |
| 25 | SimpleVolumesStrategy.mqh | 1.11 | Klasse der Handelsstrategie mit Tick-Volumen | Teil 22 |
| └ Utils | Hilfsprogramme, Makros zur Code-Reduzierung | |||
| 26 | ConsoleDialog.mqh | 1.01 | Klasse zur Anzeige von Textdaten in einem Chart | Teil 28 |
| 26 | ExpertHistory.mqh | 1.00 | Klasse für den Export der Handelshistorie in eine Datei | Teil 16 |
| 27 | Macros.mqh | 1.07 | Nützliche Makros für Array-Operationen | Teil 26 |
| 28 | MTTester.mqh | – | Datei für die Arbeit mit dem Strategietester aus der Bibliothek MultiTester | Teil 28 |
| 29 | NewBarEvent.mqh | 1.00 | Klasse zur Erkennung einer neuen Bar eines bestimmten Symbols | Teil 8 |
| 30 | SymbolsMonitor.mqh | 1.01 | Klasse zur Beschaffung von Informationen über Handelsinstrumente (Symbole) | Teil 28 |
| └ Virtual | Klassen zur Erstellung verschiedener Objekte, die durch ein System virtueller Handelsaufträge und -positionen verbunden sind | |||
| 31 | Money.mqh | 1.01 | Basisklasse für das Money-Management | Teil 12 |
| 32 | TesterHandler.mqh | 1.07 | Klasse zur Behandlung von Optimierungsereignissen | Teil 23 |
| 33 | VirtualAdvisor.mqh | 1.12 | Klasse des EA, der virtuelle Positionen (Aufträge) bearbeitet | Teil 28 |
| 34 | VirtualChartOrder.mqh | 1.02 | Grafische virtuelle Positionsklasse | Teil 28 |
| 35 | VirtualCloseManager.mqh | 1.00 | Klasse des Closing-Managers | Teil 28 |
| 36 | VirtualHistoryAdvisor.mqh | 1.00 | Die Klasse des EA zur Wiedergabe der Handelshistorie | Teil 16 |
| 37 | VirtualInterface.mqh | 1.00 | EA GUI-Klasse | Teil 4 |
| 38 | VirtualOrder.mqh | 1.09 | Klasse der virtuellen Aufträge und Positionen | Teil 22 |
| 39 | VirtualReceiver.mqh | 1.04 | Klasse für die Umwandlung von offenen Volumina in Marktpositionen (Empfänger) | Teil 23 |
| 40 | VirtualRiskManager.mqh | 1.06 | Risikomanagement-Klasse (Risikomanager) | Teil 28 |
| 41 | VirtualStrategy.mqh | 1.09 | Klasse einer Handelsstrategie mit virtuellen Positionen | Teil 23 |
| 42 | VirtualStrategyGroup.mqh | 1.04 | Klasse der Handelsstrategiegruppe(n) | Teil 28 |
| 43 | VirtualSymbolReceiver.mqh | 1.00 | Empfängerklasse für Symbole | Teil 3 |
| Common/Files | Gemeinsamer Ordner für MetaTrader 5-Terminaldaten | |||
| 44 | SimpleCandles-27183.test.db.sqlite | – | Finale EA-Datenbank | Teil 25 |
Der Quellcode ist auch unter SimpleCandles und Adwizard verfügbar.
Verwendung von Open-Source-Repositories
Während wir schrittweise zum neuen Algo Forge-Speicher übergehen, arbeiten wir noch an der besten und bequemsten Art und Weise, ihn zu nutzen. Die Einschränkung des MetaEditors auf die Verwendung eines einzigen Repositorys, das dem MQL5-Stammordner entspricht, ist nicht besonders komfortabel. Andere Repositories sind nur als Unterordner des Ordners „Shared Projects“ verfügbar, was die Sache etwas überschaubarer macht, aber nicht ausreicht, um diesen Ansatz sofort zu rechtfertigen.
Darüber hinaus wurde während des Übergangs das einzige Haupt-Repository zweimal im Namen des Superadmins erstellt, und beim zweiten Mal zerstörte dessen Erstellung aus irgendeinem Grund andere zusätzliche Repositories, die zuvor vom Benutzer erstellt worden waren. Glücklicherweise ermöglicht uns eine lokale Kopie des Repositorys, es erneut auf den Server hochzuladen, aber diese Art von erzwungener Aktion ist nicht besonders wünschenswert. Daher warten wir vorerst auf die weitere Entwicklung der MetaEditor-Funktionalität zur Handhabung von Repositories, ohne diese direkt zu verwenden.
Daran ist nichts kompliziert: Während wir weiterhin im MetaEditor arbeiten, verlagern wir vorerst einfach alle speicherbezogenen Vorgänge auf externe Anwendungen.
Wir können zum Beispiel eine Kopie aller Dateien, die den Code für diesen Artikel enthalten, auf unserem lokalen Computer abrufen, indem wir das folgende Skript in der Konsole ausführen, nachdem wir zuvor einen Ordner innerhalb des MQL5-Ordners des gewünschten MetaTrader-Terminals als aktuellen Ordner festgelegt haben:
# Erstellen eines Projektordners
mkdir SimpleCandles
# Zum Projektordner wechseln
cd SimpleCandles
# Das Projekt-Repository in den aktuellen Ordner klonen
git clone https://forge.mql5.io/antekov/SimpleCandles.git .
# Wechseln Sie das Repository auf den gewünschten Branch (für diesen Artikel - „article-17608-close-manager“)
git checkout article-17608-close-manager
# Stellen Sie sicher, dass wir auf den Branch gewechselt haben
git status
# Erstellen Sie einen Ordner für den Bibliotheksteil
mkdir Include
# Wechseln Sie dorthin
cd Include
# Klonen Sie das Adwizard-Repository in den Bibliotheksordner
git clone https://forge.mql5.io/antekov/Adwizard.git
# Wechseln Sie in den erstellten Ordner
cd Adwizard
# Wechseln Sie das Repository auf den gewünschten Branch (für diesen Artikel - „article-17608-close-manager“)
git checkout article-17608-close-manager
# Stellen Sie sicher, dass wir auf den Branch gewechselt haben
git status
# Gehen Sie zwei Ebenen nach oben zum ursprünglichen Projektordner
cd ./../..
Das Einzige, was in diesen Repositories fehlt, ist eine Datei mit der Datenbank des fertigen EAs, da der Code im Repository es uns ermöglicht, diese durch automatische Optimierung zu erhalten. Falls erforderlich, kann diese Datei aus dem Archiv in den Artikel übernommen und im gemeinsamen Terminal-Ordner im Ordner Files abgelegt werden.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/17608
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Neuronale Netze im Trading: Zeitreihenprognose mittels adaptiver Modenzerlegung (ACEFormer)
Von der Grundstufe bis zur Mittelstufe: Objektereignisse (I)
Von der Grundstufe bis zur Mittelstufe: Objektereignisse (III)
Neuronale Netze in der Praxis: Übung macht den Meister
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.