
Automatisieren von Handelsstrategien in MQL5 (Teil 3): Das Zone Recovery RSI System für ein dynamisches Handelsmanagement
Einführung
Im vorherigen Artikel (Teil 2 der Serie) haben wir gezeigt, wie man die Kumo Breakout-Strategie in einen voll funktionsfähigen Expert Advisor (EA) mit MetaQuotes Language 5 (MQL5) umwandelt. In diesem Artikel (Teil 3) konzentrieren wir uns auf das System Zone Recovery RSI, eine fortgeschrittene Strategie, die entwickelt wurde, um Handelsgeschäfte zu managen und sich dynamisch von Verlusten zu erholen. Dieses System kombiniert den Relative Strength Index (RSI) zur Auslösung von Einstiegssignalen mit einem Mechanismus, der Zone Recovery, der Gegengeschäfte platziert, wenn sich der Markt gegen die Ausgangsposition bewegt. Ziel ist es, durch Anpassung an die Marktbedingungen Rückschläge zu vermeiden und die Gesamtrentabilität zu verbessern.
Wir gehen durch den Prozess der Kodierung der Erholungslogik, der Verwaltung von Positionen mit dynamischer Losgröße und der Nutzung des RSI für Handelseinstiegs- und Erholungssignale. Am Ende dieses Artikels werden Sie genau wissen, wie Sie Zone Recovery RSI implementieren, seine Leistung mit dem MQL5 Strategietester testen und es für ein besseres Risikomanagement und bessere Renditen optimieren können. Zum besseren Verständnis ist der Artikel wie folgt aufgebaut.
- Strategieentwicklung und Schlüsselkonzepte
- Implementation in MQL5
- Backtest und Leistungsanalyse
- Schlussfolgerung
Strategieentwicklung und Schlüsselkonzepte
Das System der Zone Recovery RSI kombiniert den Relative Strength Index (RSI) Indikator für den Einstieg in den Handel mit einem Zone Recovery-Mechanismus zur Bewältigung ungünstiger Kursbewegungen. Handelseröffnungen werden ausgelöst, wenn der RSI wichtige Schwellenwerte überschreitet - in der Regel 30 für überverkaufte (Kauf) und 70 für überkaufte (Verkauf) Bedingungen. Die wahre Stärke des Systems liegt jedoch in seiner Fähigkeit, sich von dem Verlusten aus Handelsgeschäfte zu erholen, indem es ein gut strukturiertes Zone Recovery-Modell verwendet.
Das Zone Recovery System legt vier kritische Kursniveaus für jeden Handel fest: Zone High, Zone Low, Target High und Target Low. Wenn ein Handelsgeschäft eröffnet wird, werden diese Niveaus relativ zum Einstiegskurs berechnet. Bei einem Kauf wird Zone Low unter dem Einstiegskurs festgelegt, während Zone High beim Einstiegskurs liegt. Umgekehrt wird bei einem Verkauf Zone High über dem Einstiegskurs platziert, während Zone Low mit diesem übereinstimmt. Bewegt sich der Markt aus Zone Low (für Käufe) oder Zone High (für Verkäufe), wird ein Gegengeschäft in die entgegengesetzte Richtung mit einer erhöhten Losgröße auf der Grundlage eines vordefinierten Multiplikators ausgelöst. TargetHigh und TargetLow legen die Gewinnmitnahmepunkte für Kauf- und Verkaufspositionen fest und stellen sicher, dass die Handelsgeschäfte mit Gewinn geschlossen werden, sobald sich der Markt günstig entwickelt. Dieser Ansatz ermöglicht die Rückgewinnung oder Erholung von Verlusten bei gleichzeitiger Risikokontrolle durch systematische Positionsgrößen- und Levelanpassungen. Die folgende Illustration fasst das gesamte Modell zusammen.
Implementation in MQL5
Nachdem wir alle Theorien über die Handelsstrategie Zone Recovery gelernt haben, wollen wir die Theorie automatisieren und einen Expert Advisor (EA) in MetaQuotes Language 5 (MQL5) für MetaTrader 5 erstellen.
Um einen Expert Advisor (EA) zu erstellen, klicken Sie in Ihrem MetaTrader 5-Terminal auf die Registerkarte Extras und aktivieren Sie MetaQuotes Language Editor oder drücken Sie einfach F4 auf Ihrer Tastatur. Alternativ können Sie auch auf das IDE-Symbol (Integrated Development Environment) in der Symbolleiste klicken. Dadurch wird die Umgebung des MetaQuotes-Spracheditors geöffnet, die das Schreiben von Handelsrobotern, technischen Indikatoren, Skripten und Funktionsbibliotheken ermöglicht. Sobald der MetaEditor geöffnet ist, navigieren wir in der Symbolleiste zur Registerkarte „Datei“ und wählen „Neue Datei“, oder drücken einfach die Tastenkombination STRG + N, um ein neues Dokument zu erstellen. Alternativ könnten wir auch auf das Symbol New auf der Registerkarte Werkzeuge klicken. Daraufhin erscheint ein Popup-Fenster des MQL-Assistenten.
In dem sich öffnenden Assistenten markieren wir die Option Expert Advisor (Template bzw. Vorlage) und klicken auf Weiter (Next). Wir geben in den allgemeinen Eigenschaften des Expert Advisors unter dem Abschnitt Name den Dateinamen Ihres Experten an. Nicht vergessen, den Backslash vor dem Namen des EA verwenden, um einen Ordner anzugeben oder zu erstellen, wenn er nicht existiert. Hier haben wir zum Beispiel standardmäßig „Experts\“. Das bedeutet, dass unser EA im Ordner Experts erstellt wird und wir ihn dort finden können. Die anderen Abschnitte sind ziemlich einfach, aber Sie können dem Link am Ende des Assistenten folgen, um zu erfahren, wie der Prozess genau abläuft.
Nachdem Sie den gewünschten Expert Advisor-Dateinamen eingegeben haben, klicken Sie auf Weiter, dann auf Weiter und schließlich auf Fertig stellen. Nachdem wir all dies getan haben, können wir nun unsere Strategie programmieren.
Zunächst definieren wir einige Metadaten über den Expert Advisor (EA). Dazu gehören der Name des EA, die Copyright-Informationen und ein Link zur MetaQuotes-Website. Wir geben auch die Version des EA an, die auf „1.00“ eingestellt ist.
//+------------------------------------------------------------------+ //| 1. Zone Recovery RSI EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00"
Dadurch werden beim Laden des Programms die System-Metadaten angezeigt. Anschließend können wir einige globale Variablen hinzufügen, die wir im Programm verwenden werden. Zunächst binden wir eine Handelsinstanz ein, indem wir #include am Anfang des Quellcodes verwenden. Dadurch erhalten wir Zugriff auf die Klasse „CTrade“, die wir zur Erstellung eines Handelsobjekts verwenden werden. Dies ist von entscheidender Bedeutung, da wir sie zur Eröffnung von Handelsgeschäften benötigen.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
Der Präprozessor wird die Zeile #include <Trade/Trade.mqh> durch den Inhalt der Datei Trade.mqh ersetzen. Die spitzen Klammern zeigen an, dass die Datei Trade.mqh aus dem Standardverzeichnis verwendet werden soll (normalerweise ist es das Terminal-Installationsverzeichnis\MQL5\Include). Das aktuelle Verzeichnis wird bei der Suche nicht berücksichtigt. Die Zeile kann an beliebiger Stelle im Programm platziert werden, aber in der Regel werden alle Einbindungen am Anfang des Quellcodes platziert, um den Code besser zu strukturieren und die Referenz zu erleichtern. Die Deklaration des Objekts „obj_Trade“ der Klasse CTrade ermöglicht uns dank der MQL5-Entwickler einen einfachen Zugriff auf die in dieser Klasse enthaltenen Methoden.
Danach müssen wir mehrere wichtige globale Variablen deklarieren, die wir im Handelssystem verwenden werden.
// Global variables for RSI logic int rsiPeriod = 14; //--- The period used for calculating the RSI indicator. int rsiHandle; //--- Handle for the RSI indicator, used to retrieve RSI values. double rsiBuffer[]; //--- Array to store the RSI values retrieved from the indicator. datetime lastBarTime = 0; //--- Holds the time of the last processed bar to prevent duplicate signals.
Für die Signalerzeugung richten wir die globalen Variablen ein, die für die Verwaltung der Logik des Indikators Relative Strength Index (RSI) erforderlich sind. Zunächst definieren wir „rsiPeriod“ als 14, was die Anzahl der Kursbalken bestimmt, die zur Berechnung des RSI verwendet werden. Dies ist eine Standardeinstellung in der technischen Analyse, die es uns ermöglicht, überkaufte oder überverkaufte Marktbedingungen zu beurteilen. Als Nächstes erstellen wir „rsiHandle“, eine Referenz für den RSI-Indikator. Mit diesem Handle können wir RSI-Werte von der MetaTrader-Plattform anfordern und abrufen und so die Bewegungen des Indikators in Echtzeit verfolgen.
Um diese RSI-Werte zu speichern, verwenden wir „rsiBuffer“, ein Array, das die Ausgabe des Indikators enthält. Wir werden diesen Puffer analysieren, um wichtige Kreuzungspunkte zu erkennen, z. B. wenn der RSI unter 30 (potenzielles Kaufsignal) oder über 70 (potenzielles Verkaufssignal) steigt. Schließlich führen wir „lastBarTime“ ein, die die Zeit des zuletzt verarbeiteten Balkens speichert. Diese Variable stellt sicher, dass wir nur ein Signal pro Balken verarbeiten und so verhindern, dass mehrere Handelsgeschäfte innerhalb desselben Balkens ausgelöst werden. Danach können wir eine Klasse definieren, die den Erholungsmechanismus handhabt.
// Global ZoneRecovery object class ZoneRecovery { //--- };
Hier erstellen wir ein Zone Recovery-System mit einer Klasse namens „ZoneRecovery“, die als Container für alle Variablen, Funktionen und Logik dient, die zur Verwaltung des Erholungsprozesses erforderlich sind. Durch die Verwendung einer Klasse können wir den Code in einem in sich geschlossenen Objekt organisieren, das es uns ermöglicht, den Handel zu verwalten, den Fortschritt der Erholung zu verfolgen und die wesentlichen Ebenen für jeden Handelszyklus zu berechnen. Dieser Ansatz bietet eine bessere Struktur, Wiederverwendbarkeit und Skalierbarkeit für die gleichzeitige Bearbeitung mehrerer Handelspositionen. Eine Klasse kann drei gekapselte Mitglieder in Form von privaten, geschützten und öffentlichen Mitgliedern enthalten. Lassen Sie uns zunächst die privaten Mitglieder definieren.
private: CTrade trade; //--- Object to handle trading operations. double initialLotSize; //--- The initial lot size for the first trade. double currentLotSize; //--- The lot size for the current trade in the sequence. double zoneSize; //--- Distance in points defining the range of the recovery zone. double targetSize; //--- Distance in points defining the target profit range. double multiplier; //--- Multiplier to increase lot size in recovery trades. string symbol; //--- Symbol for trading (e.g., currency pair). ENUM_ORDER_TYPE lastOrderType; //--- Type of the last executed order (BUY or SELL). double lastOrderPrice; //--- Price at which the last order was executed. double zoneHigh; //--- Upper boundary of the recovery zone. double zoneLow; //--- Lower boundary of the recovery zone. double zoneTargetHigh; //--- Upper boundary for target profit range. double zoneTargetLow; //--- Lower boundary for target profit range. bool isRecovery; //--- Flag indicating whether the recovery process is active.
Hier werden die privaten Mitgliedsvariablen der Klasse „ZoneRecovery“ definiert, die wichtige Daten für die Verwaltung des Zone Recovery-Prozesses speichern. Diese Variablen ermöglichen es uns, den Status der Strategie zu verfolgen, die wichtigsten Niveaus der Erholungszone zu berechnen und die Logik der Handelsausführung zu verwalten.
Wir verwenden das Objekt „CTrade“, um alle Handelsoperationen, wie das Platzieren, Ändern und Schließen von Handelsgeschäften, abzuwickeln. „initialLotSize“ steht für die Losgröße des ersten Handelsgeschäfts, während „currentLotSize“ die Losgröße für nachfolgende Recovery-Handelsgeschäfte angibt, die sich auf der Grundlage des „Multiplikators“ erhöht. Mit „zoneSize“ und „targetSize“ werden die kritischen Grenzen des Erholungssystems festgelegt. Konkret wird die Erholungszone durch „zoneHigh“ und „zoneLow“ begrenzt, während das Gewinnziel durch „zoneTargetHigh“ und „zoneTargetLow“ definiert wird.
Um den Fluss der Handelsgeschäfte zu verfolgen, speichern wir den „lastOrderType“ (BUY oder SELL) und den „lastOrderPrice“, zu dem das letzte Handelsgeschäft ausgeführt wurde. Anhand dieser Informationen können wir bestimmen, wie wir künftige Handelsgeschäfte in Reaktion auf Marktbewegungen positionieren. Die Variable „symbol“ identifiziert das verwendete Handelsinstrument, während das Flag „isRecovery“ angibt, ob sich das System aktiv im Erholungsprozess befindet. Indem wir diese Variablen privat halten, stellen wir sicher, dass nur die interne Logik der Klasse sie ändern kann, wodurch die Integrität und Genauigkeit der Berechnungen des Systems erhalten bleibt. Danach können wir die Funktionen der Klasse direkt definieren, ohne sie später aufrufen und definieren zu müssen, nur der Einfachheit halber. Anstatt also die benötigten Funktionen zu deklarieren und später zu definieren, deklarieren und definieren wir sie ein für alle Mal. Definieren wir zunächst die Funktion, die für die Berechnung der Erholungszonen zuständig ist.
// Calculate dynamic zones and targets void CalculateZones() { if (lastOrderType == ORDER_TYPE_BUY) { zoneHigh = lastOrderPrice; //--- Upper boundary starts from the last BUY price. zoneLow = zoneHigh - zoneSize; //--- Lower boundary is calculated by subtracting zone size. zoneTargetHigh = zoneHigh + targetSize; //--- Profit target above the upper boundary. zoneTargetLow = zoneLow - targetSize; //--- Buffer below the lower boundary for recovery trades. } else if (lastOrderType == ORDER_TYPE_SELL) { zoneLow = lastOrderPrice; //--- Lower boundary starts from the last SELL price. zoneHigh = zoneLow + zoneSize; //--- Upper boundary is calculated by adding zone size. zoneTargetLow = zoneLow - targetSize; //--- Buffer below the lower boundary for profit range. zoneTargetHigh = zoneHigh + targetSize; //--- Profit target above the upper boundary. } Print("Zone recalculated: ZoneHigh=", zoneHigh, ", ZoneLow=", zoneLow, ", TargetHigh=", zoneTargetHigh, ", TargetLow=", zoneTargetLow); }
Hier entwerfen wir die Funktion „CalculateZones“, die eine wichtige Rolle bei der Festlegung der Schlüsselebenen für unsere Strategie der Zone Recovery spielt. Das Hauptziel dieser Funktion ist die Berechnung der vier wesentlichen Grenzen — „zoneHigh“, „zoneLow“, „zoneTargetHigh“ und „zoneTargetLow“ — die unsere Einstiegs-, Erholungs- und Gewinnausstiegspunkte bestimmen. Diese Grenzen sind dynamisch und werden auf der Grundlage der Art und des Preises des zuletzt ausgeführten Auftrags angepasst, sodass wir die Kontrolle über den Einziehungsprozess behalten.
Wenn unser letzter Auftrag ein KAUFEN war, setzen wir die „zoneHigh“ auf den Preis, zu dem der KAUFEN-Auftrag ausgeführt wurde. Von diesem Punkt aus berechnen wir den „zoneLow“, indem wir die „zoneSize“ vom „zoneHigh“ abziehen und so einen Erholungsbereich unterhalb des ursprünglichen BUY-Preises schaffen. Um unsere Gewinnziele festzulegen, berechnen wir „zoneTargetHigh“, indem wir die „targetSize“ zur „zoneHigh“ addieren, während „zoneTargetLow“ um die gleiche „targetSize“ unter „zoneLow“ positioniert wird. Diese Struktur ermöglicht es uns, Erholungsgeschäfte unterhalb des ursprünglichen KAUF-Einstiegs zu positionieren und die obere und untere Grenze unseres Gewinnbereichs zu definieren.
Wenn unser letzter Auftrag ein VERKAUF war, drehen wir die Logik um. Hier setzen wir „zoneLow“ auf den Preis des letzten VERKAUFS-Auftrag. Wir berechnen dann „zoneHigh“, indem wir „zoneSize“ zu „zoneLow“ addieren und so die obere Grenze des Erholungsbereichs bilden. Die Gewinnziele werden festgelegt, indem „zoneTargetLow“ als ein Wert unterhalb von „zoneLow“ berechnet wird, während „zoneTargetHigh“ oberhalb von „zoneHigh“ festgelegt wird, beides um die „targetSize“. Diese Konstellation ermöglicht es uns, Erholungsgeschäfte oberhalb des ursprünglichen Einstiegs des VERKAUFS einzuleiten und gleichzeitig die Gewinnmitnahmezone zu definieren.
Am Ende dieses Prozesses haben wir die Grenzen unserer Erholungszone und die Gewinnziele für KAUF- und VERKAUFS-Handelsgeschäfte festgelegt. Um die Fehlersuche und die Bewertung der Strategie zu erleichtern, verwenden wir die Funktion Print, um die Werte von „zoneHigh“, „zoneLow“, „zoneTargetHigh“ und „zoneTargetLow“ im Protokoll anzuzeigen. Auf diese Weise können wir eine weitere Funktion definieren, die sich um die Logik der Handelsausführung kümmert.
// Open a trade based on the given type bool OpenTrade(ENUM_ORDER_TYPE type) { if (type == ORDER_TYPE_BUY) { if (trade.Buy(currentLotSize, symbol)) { lastOrderType = ORDER_TYPE_BUY; //--- Mark the last trade as BUY. lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price. CalculateZones(); //--- Recalculate zones after placing the trade. Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize); isRecovery = true; //--- Set recovery state to true after the first trade. return true; } } else if (type == ORDER_TYPE_SELL) { if (trade.Sell(currentLotSize, symbol)) { lastOrderType = ORDER_TYPE_SELL; //--- Mark the last trade as SELL. lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price. CalculateZones(); //--- Recalculate zones after placing the trade. Print(isRecovery ? "RECOVERY SELL order placed" : "INITIAL SELL order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize); isRecovery = true; //--- Set recovery state to true after the first trade. return true; } } return false; //--- Return false if the trade fails. }
Hier definieren wir eine Funktion namens „OpenTrade“, die einen booleschen Wert zurückgibt. Der Zweck dieser Funktion besteht darin, ein Handelsgeschäft zu eröffnen, je nachdem, ob wir einen KAUF- oder VERKAUFS-Auftrag ausführen wollen. Zunächst wird geprüft, ob es sich bei der angeforderten Auftragsart um einen KAUF handelt. Ist dies der Fall, versuchen wir mit der Funktion „trade.Buy“, eine Kaufposition mit der aktuellen Losgröße und dem angegebenen Symbol zu eröffnen. Wenn das Handelsgeschäft erfolgreich eröffnet wurde, setzen wir den „lastOrderType“ auf BUY (KAUF), dann speichern wir den aktuellen Preis des Symbols mit der Funktion SymbolInfoDouble, um den Geldkurs zu erhalten. Dieser Preis entspricht dem Preis, zu dem wir die Position eröffnet haben. Anschließend werden die Erholungszonen neu berechnet, indem die Funktion „CalculateZones“ aufgerufen wird, die die Zonenstufen auf der Grundlage der neuen Position anpasst.
Als Nächstes geben wir eine Meldung in das Protokoll ein, die angibt, ob es sich um einen Erst-KAUF oder um einen Erholungs-KAUF handelt. Wir verwenden einen ternären Operator, um zu prüfen, ob das Flag „isRecovery“ wahr oder falsch ist - wenn es wahr ist, wird in der Nachricht angegeben, dass es sich um einen Erholungsauftrag handelt; andernfalls wird angegeben, dass es sich um den Erst-Auftrag handelt. Danach setzen wir das Flag „isRecovery“ auf true, um zu signalisieren, dass alle nachfolgenden Handelsgeschäfte als Teil des Recovery-Prozesses betrachtet werden. Schließlich gibt die Funktion „true“ zurück und bestätigt damit, dass das Handelsgeschäft erfolgreich abgeschlossen wurde.
Wenn die Auftragsart VERKAUFEN ist, folgen wir den gleichen Schritten. Wir versuchen, eine VERKAUFS-Position zu eröffnen, indem wir die Funktion „trade.Sell“ mit denselben Parametern aufrufen, und bei erfolgreicher Ausführung speichern wir den „lastOrderPrice“ und passen die Erholungszonen auf dieselbe Weise an. Wir drucken eine Meldung aus, die angibt, ob es sich um einen Erst-VERKAUF oder um einen Erholungs-VERKAUF handelt, wobei wir wiederum einen ternären Operator verwenden, um das Flag „isRecovery“ zu überprüfen. Das Flag „isRecovery“ wird dann auf „true“ gesetzt, und die Funktion gibt „true“ zurück, um anzuzeigen, dass das Handelsgeschäft erfolgreich platziert wurde. Wird das Handelsgeschäft aus irgendeinem Grund nicht erfolgreich eröffnet, gibt die Funktion den Wert false zurück und zeigt damit an, dass der Handelsversuch fehlgeschlagen ist. Das sind die entscheidenden Funktionen, die wir als Privatperson haben müssen. Andere können wir als öffentlich betrachten, ohne Probleme.
public: // Constructor ZoneRecovery(double initialLot, double zonePts, double targetPts, double lotMultiplier, string _symbol) { initialLotSize = initialLot; currentLotSize = initialLot; //--- Start with the initial lot size. zoneSize = zonePts * _Point; //--- Convert zone size to points. targetSize = targetPts * _Point; //--- Convert target size to points. multiplier = lotMultiplier; symbol = _symbol; //--- Initialize the trading symbol. lastOrderType = ORDER_TYPE_BUY; lastOrderPrice = 0.0; //--- No trades exist initially. isRecovery = false; //--- No recovery process active at initialization. }
Hier deklarieren wir den öffentlichen Abschnitt der Klasse „ZoneRecovery“, der den Konstruktor enthält. Der Konstruktor wird verwendet, um ein Objekt der Klasse „ZoneRecovery“ mit bestimmten Parametern zu initialisieren, wenn es erstellt wird. Der Konstruktor nimmt „initialLot“, „zonePts“, „targetPts“, „lotMultiplier“ und „_symbol“ als Eingaben auf.
Wir beginnen damit, den Wert „initialLot“ den Werten „initialLotSize“ und „currentLotSize“ zuzuweisen, wobei wir sicherstellen, dass beide mit demselben Wert beginnen, der die Losgröße für das ersten Handelsgeschäft darstellt. Wir berechnen dann die „zoneSize“, indem wir „zonePts“ (den Zonenabstand in Punkten) mit _Point multiplizieren, einer eingebauten Konstante, die die minimale Preisbewegung für das Symbol darstellt. In ähnlicher Weise wird „targetSize“ durch Umrechnung von „targetPts“ (Ziel-Gewinnabstand) in Punkte berechnet. „multiplier“ wird auf „lotMultiplier“ gesetzt, der später verwendet wird, um die Lotgröße für Recovery Handelsgeschäfte anzupassen.
Als Nächstes wird das „symbol“ der Variablen „symbol“ zugewiesen, um anzugeben, welches Handelsinstrument verwendet wird. „lastOrderType“ wird zunächst auf ORDER_TYPE_BUY gesetzt, in der Annahme, dass der erste Handel ein Kaufauftrag sein wird. „lastOrderPrice“ wird auf „0.0“ gesetzt, da noch kein Handelsgeschäft ausgeführt wurde. Schließlich wird „isRecovery“ auf „false“ gesetzt, was bedeutet, dass der Erholungsprozess noch nicht aktiv ist. Dieser Konstruktor stellt sicher, dass das Objekt „ZoneRecovery“ ordnungsgemäß initialisiert und für die Verwaltung von Gewerben und Erholungsprozessen vorbereitet ist. Als Nächstes definieren wir eine Funktion zur Auslösung von Handelsgeschäften auf der Grundlage externer Signale.
// Trigger trade based on external signals void HandleSignal(ENUM_ORDER_TYPE type) { if (lastOrderPrice == 0.0) //--- Open the first trade if no trades exist. OpenTrade(type); }
Hier definieren wir eine Funktion mit dem Namen „HandleSignal“, die als Parameter vom Typ ENUM_ORDER_TYPE annimmt, der die Art des auszuführenden Handels darstellt (entweder ein BUY oder SELL). Zunächst prüfen wir, ob der „lastOrderPrice“ „0.0“ ist, was bedeutet, dass kein vorheriges Handelsgeschäft durchgeführt wurde. Wenn diese Bedingung erfüllt ist, bedeutet dies, dass dies das erste zu eröffnende Handelsgeschäft ist, also rufen wir die Funktion „OpenTrade“ auf und übergeben ihr den Parameter „type“. Die Funktion „OpenTrade“ übernimmt dann die Logik zur Eröffnung eines KAUF- oder VERKAUFS-Auftrags auf der Grundlage des empfangenen Signals. Wir können nun die Zonen verwalten, indem wir über die nachstehende Logik Erholungsgeschäfte eröffnen.
// Manage zone recovery positions void ManageZones() { double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current BID price. // Open recovery trades based on zones if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) { currentLotSize *= multiplier; //--- Increase lot size for recovery. OpenTrade(ORDER_TYPE_SELL); //--- Open a SELL order for recovery. } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) { currentLotSize *= multiplier; //--- Increase lot size for recovery. OpenTrade(ORDER_TYPE_BUY); //--- Open a BUY order for recovery. } }
Zur Verwaltung der eröffneten Handelsgeschäfte definieren wir eine void Funktion namens „ManageZones“, die für die Verwaltung der Erholungsgeschäfte auf der Grundlage vordefinierter Preiszonen zuständig ist. In dieser Funktion wird zunächst der aktuelle BID-Preis für das angegebene Symbol mit Hilfe der Funktion SymbolInfoDouble mit dem Parameter SYMBOL_BID ermittelt. So erhalten wir den aktuellen Marktpreis, zu dem der Vermögenswert gehandelt wird.
Als Nächstes überprüfen wir den Handelstyp des zuletzt ausgeführten Auftrags anhand der Variablen „lastOrderType“. Wenn der letzte Abschluss ein KAUF war und der aktuelle Marktpreis auf oder unter den „zoneLow“ (die untere Grenze der Erholungszone) gefallen ist, erhöhen wir die „currentLotSize“, indem wir sie mit „multiplier“ multiplizieren, um mehr Kapital für den Erholungsabschluss zuzuweisen. Danach rufen wir die Funktion „OpenTrade“ mit dem Parameter ORDER_TYPE_SELL auf, um anzugeben, dass wir eine VERKAUFS-Position eröffnen müssen, um den Verlust aus der vorherigen KAUF-Position zu verwalten.
Wenn der letzte Abschluss ein VERKAUF war und der aktuelle Marktpreis auf oder über das „zoneHigh“ (die obere Grenze der Erholungszone) gestiegen ist, erhöhen wir die „currentLotSize“ erneut, indem wir sie mit „multiplier“ multiplizieren, um die Handelsgröße für die Erholung zu vergrößern. Dann rufen wir die Funktion „OpenTrade“ mit dem Parameter ORDER_TYPE_BUY auf und eröffnen eine KAUF-Position, um die frühere VERKAUFS-Position wieder auszugleichen. So einfach ist das. Nachdem wir nun die Erst- und Erholungsgeschäfte eröffnet haben, brauchen wir eine Logik, um sie zu einem bestimmten Zeitpunkt zu schließen. Definieren wir also im Folgenden die Abschluss- oder Ziellogik.
// Check and close trades at zone targets void CheckCloseAtTargets() { double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current BID price. // Close BUY trades at target high if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) { for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Loop through all open positions. if (PositionGetSymbol(i) == symbol) { //--- Check if the position belongs to the current symbol. ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Retrieve the ticket number. int retries = 10; while (retries > 0) { if (trade.PositionClose(ticket)) { //--- Attempt to close the position. Print("Closed BUY position with ticket: ", ticket); break; } else { Print("Failed to close BUY position with ticket: ", ticket, ". Retrying... Error: ", GetLastError()); retries--; Sleep(100); //--- Wait 100ms before retrying. } } if (retries == 0) Print("Gave up on closing BUY position with ticket: ", ticket); } } Reset(); //--- Reset the strategy after closing all positions. } // Close SELL trades at target low else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) { for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Loop through all open positions. if (PositionGetSymbol(i) == symbol) { //--- Check if the position belongs to the current symbol. ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Retrieve the ticket number. int retries = 10; while (retries > 0) { if (trade.PositionClose(ticket)) { //--- Attempt to close the position. Print("Closed SELL position with ticket: ", ticket); break; } else { Print("Failed to close SELL position with ticket: ", ticket, ". Retrying... Error: ", GetLastError()); retries--; Sleep(100); //--- Wait 100ms before retrying. } } if (retries == 0) Print("Gave up on closing SELL position with ticket: ", ticket); } } Reset(); //--- Reset the strategy after closing all positions. } }
Hier definieren wir eine Funktion namens „CheckCloseAtTargets“, die dafür verantwortlich ist, zu prüfen, ob offene Handelsgeschäfte ihren vordefinierten Zielpreis erreicht haben und sie entsprechend zu schließen.
Zunächst wird der aktuelle BID-Preis für das angegebene Symbol mit der Funktion SymbolInfoDouble und dem Parameter SYMBOL_BID ermittelt. So erhalten wir den aktuellen Marktpreis des Symbols, den wir mit den Zielpreisniveaus (entweder „zoneTargetHigh“ oder „zoneTargetLow“) vergleichen, um zu entscheiden, ob die Handelsgeschäfte geschlossen werden sollten.
Als Nächstes prüfen wir, ob der letzte Auftragstyp KAUF ist und ob der aktuelle Kurs die „zoneTargetHigh“ (das Zielkursniveau für eine KAUF-Position) erreicht oder überschritten hat. Wenn diese Bedingungen erfüllt sind, durchlaufen wir mit der Funktion PositionsTotal alle offenen Positionen, beginnend mit der letzten Position. Für jede offene Position wird mit der Funktion PositionGetSymbol geprüft, ob die Position zum selben Symbol gehört. Wenn das Symbol übereinstimmt, wird die Ticketnummer der Position mit der Funktion PositionGetInteger mit dem Parameter „POSITION_TICKET“ abgerufen.
Anschließend versuchen wir, die Position zu schließen, indem wir die Funktion „trade.PositionClose“ mit dem abgerufenen Ticket aufrufen. Wenn die Position erfolgreich geschlossen wurde, drucken wir eine Bestätigungsmeldung aus, die besagt, dass die KAUF-Position geschlossen wurde, einschließlich der Ticketnummer. Wenn das Schließen fehlschlägt, versuchen wir es bis zu 10 Mal erneut, wobei wir jedes Mal eine Fehlermeldung ausgeben und die Funktion Sleep verwenden, um 100 Millisekunden zu warten, bevor wir es erneut versuchen. Wenn wir die Position nach 10 Versuchen immer noch nicht schließen können, geben wir eine Fehlermeldung aus und fahren mit der nächsten offenen Position fort. Sobald alle Positionen geschlossen sind oder das Wiederholungslimit erreicht ist, rufen wir die Funktion „Reset“ auf, um die Strategie zurückzusetzen und sicherzustellen, dass der Status für alle zukünftigen Handelsgeschäfte gelöscht wird.
Ähnlich verhält es sich, wenn der letzte Auftragstyp ein VEKAUF ist und der aktuelle Preis die „zoneTargetLow“ (das Zielpreisniveau für eine VEKAUFS-Position) erreicht oder unterschritten hat, wird der Vorgang für alle SELL-Positionen wiederholt. Die Funktion versucht, die VEKAUFS-Positionen auf die gleiche Weise zu schließen, wobei sie es gegebenenfalls erneut versucht und bei jedem Schritt Statusmeldungen ausgibt. Wir haben eine fremde Funktion verwendet, um den Status zurückzusetzen, aber hier ist die angewandte Logik.
// Reset the strategy after hitting targets void Reset() { currentLotSize = initialLotSize; //--- Reset lot size to the initial value. lastOrderType = -1; //--- Clear the last order type. lastOrderPrice = 0.0; //--- Clear the last order price. isRecovery = false; //--- Set recovery state to false. Print("Strategy reset after closing trades."); }
Wir definieren eine Funktion namens „Reset“, die für das Zurücksetzen der internen Variablen der Strategie verantwortlich ist und das System auf das nächste Handelsgeschäft oder das nächste Reset-Szenario vorbereitet. Zunächst wird die „currentLotSize“ auf die „initialLotSize“ zurückgesetzt, d. h. nach einer Reihe von Erholungsgeschäften oder dem Erreichen von Zielniveaus wird die Losgröße auf ihren ursprünglichen Wert zurückgesetzt. Dadurch wird sichergestellt, dass die Strategie bei allen neuen Handelsgeschäften mit der Anfangs-Losgröße beginnt.
Als Nächstes löschen wir „lastOrderType“, indem wir ihn auf -1 setzen, was bedeutet, dass es keinen vorherigen Auftragstyp gibt (weder ein BUY noch ein SELL). Dadurch wird sichergestellt, dass es in der zukünftigen Handelslogik keine Verwechslungen oder Abhängigkeiten von der vorherigen Auftragsart gibt. In ähnlicher Weise setzen wir den „lastOrderPrice“ auf 0,0 zurück und löschen damit den letzten Preis, zu dem ein Handelsgeschäft ausgeführt wurde. Dann setzen wir das Flag „isRecovery“ auf false, um zu signalisieren, dass der Erholungsprozess nicht mehr aktiv ist. Dies ist besonders wichtig, da es sicherstellt, dass alle zukünftigen Handelsgeschäfte als Erst-Geschäfte und nicht als Teil einer Erholungsstrategie behandelt werden.
Abschließend wird mit der Funktion Print eine Meldung ausgedruckt, dass die Strategie nach dem Schließen aller Handelsgeschäfte erfolgreich zurückgesetzt wurde. Dadurch erhält der Händler eine Rückmeldung im Terminal und kann nachvollziehen, wann die Strategie zurückgesetzt wurde, um den richtigen Zustand für künftige Operationen sicherzustellen. Im Wesentlichen löscht die Funktion alle wesentlichen Variablen, die Handelsbedingungen, Erholungszustände und Handelsgrößen verfolgen, und setzt das System auf seine Standardeinstellungen für neue Operationen zurück. Und das ist alles, was wir brauchen, damit die Klasse alle eingehenden Signale verarbeiten kann. Wir können nun mit der Initialisierung des Klassenobjekts fortfahren, indem wir die Standardparameter übergeben.
ZoneRecovery zoneRecovery(0.1, 200, 400, 2.0, _Symbol); //--- Initialize the ZoneRecovery object with specified parameters.
Hier erstellen wir eine Instanz der Klasse „ZoneRecovery“, indem wir ihren Konstruktor aufrufen und die erforderlichen Parameter übergeben. Konkret initialisieren wir das Objekt „zoneRecovery“ mit den folgenden Werten:
- „0,1“ für die initiale Losgröße. Das bedeutet, dass für das ersten Handelsgeschäft eine Losgröße von 0,1 verwendet wird.
- „200“ für die Zonengröße, d. h. die Anzahl der Punkte, die den Bereich der Erholungszone definieren. Anschließend wird sie mit _Point multipliziert, um diesen Wert in einen tatsächliche Preisabstand für das angegebene Symbol umzurechnen.
- „400“ für die Zielgröße, die den Abstand in Punkten zum Zielgewinnniveau angibt. Ähnlich wie die Zonengröße wird auch diese mit _Point umgerechnet.
- „2,0“ für den „multiplier“, mit dem die Losgröße bei späteren Erholungsgeschäften gegebenenfalls erhöht wird.
- „_Symbol“ wird als Handelssymbol für diese spezielle Instanz von ZoneRecovery verwendet, das dem Symbol des Instruments entspricht, das der Händler verwendet.
Indem wir „zoneRecovery“ mit diesen Parametern initialisieren, richten wir das Objekt ein, um die Handelslogik für diese spezifische Handelsstrategie zu handhaben, einschließlich der Verwaltung der Erholungszonen, der Lotgrößenanpassungen und der Zielniveaus für alle Handelsgeschäfte, die eröffnet oder verwaltet werden sollen. Dieses Objekt ist bereit, Handelsoperationen auf der Grundlage der definierten Erholungsstrategie zu bearbeiten, sobald das System ausgeführt wird. Wir können nun zu der Ereignisbehandlung übergehen, wo wir uns auf die Signalerzeugung konzentrieren. Wir beginnen mit OnInit(). Hier müssen wir nur das Indikator-Handle initialisieren und das Speicher-Array als Zeitreihe festlegen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize RSI indicator rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE); //--- Create RSI indicator handle. if (rsiHandle == INVALID_HANDLE) { //--- Check if RSI handle creation failed. Print("Failed to create RSI handle. Error: ", GetLastError()); return(INIT_FAILED); //--- Return failure status if RSI initialization fails. } ArraySetAsSeries(rsiBuffer, true); //--- Set the RSI buffer as a time series to align values. Print("Zone Recovery Strategy initialized."); //--- Log successful initialization. return(INIT_SUCCEEDED); //--- Return success status. }
Hier initialisieren wir den RSI-Indikator und bereiten das System für den Handel vor, indem wir eine Reihe von Setup-Aufgaben in der OnInit-Funktion ausführen. Zunächst erstellen wir ein RSI-Indikator-Handle, indem wir die Funktion iRSI aufrufen und dabei das aktuelle Symbol (_Symbol), den Zeitrahmen PERIOD_CURRENT, die angegebene „rsiPeriod“ und den Preistyp PRICE_CLOSE übergeben. In diesem Schritt wird der RSI-Indikator für die Verwendung in der Strategie eingerichtet.
Anschließend wird geprüft, ob die Erstellung des Handles erfolgreich war, indem überprüft wird, ob das Handle ungleich INVALID_HANDLE ist. Wenn die Erstellung fehlschlägt, wird mit der Funktion GetLastError eine Fehlermeldung mit dem spezifischen Fehlercode ausgegeben und „INIT_FAILED“ zurückgegeben, um den Fehler zu signalisieren. Wenn die Erstellung des Handles erfolgreich war, wird der RSI-Puffer mit ArraySetAsSeries als Zeitserie eingestellt, um den Puffer an die Zeitserie des Diagramms anzupassen und sicherzustellen, dass die jüngsten Werte bei Index 0 liegen. Schließlich drucken wir eine Erfolgsmeldung, die die Initialisierung der „Zone Recovery Strategy“ bestätigt, und geben INIT_SUCCEEDED zurück, um zu signalisieren, dass die Einrichtung erfolgreich war und der Expert Advisor bereit ist, seinen Betrieb aufzunehmen. Hier ist eine Illustration.
Da wir jedoch einen Indikator erstellen und initialisieren, müssen wir ihn freigeben, sobald wir das Programm nicht mehr benötigen, um Ressourcen freizugeben. Das ist die Logik, die wir anwenden.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (rsiHandle != INVALID_HANDLE) //--- Check if RSI handle is valid. IndicatorRelease(rsiHandle); //--- Release RSI indicator handle to free resources. Print("Zone Recovery Strategy deinitialized."); //--- Log deinitialization message. }
Hier deinitialisieren wir die Strategie und geben alle vom RSI-Indikator verwendeten Ressourcen frei, wenn der Expert Advisor (EA) entfernt oder gestoppt wird. In der Funktion OnDeinit wird zunächst geprüft, ob das „rsiHandle“ gültig ist, indem bestätigt wird, dass es nicht gleich INVALID_HANDLE ist. Dadurch wird sichergestellt, dass der RSI-Indikator-Handle vorhanden ist, bevor versucht wird, ihn freizugeben.
Wenn der Handle gültig ist, verwenden wir die Funktion IndicatorRelease, um die mit dem RSI-Indikator verbundenen Ressourcen freizugeben und so sicherzustellen, dass der Speicher ordnungsgemäß verwaltet wird und nicht in Gebrauch bleibt, wenn der EA nicht mehr läuft. Abschließend wird die Meldung „Zone Recovery Strategy deinitialized“ gedruckt, um zu protokollieren, dass der Deinitialisierungsprozess abgeschlossen ist und das System ordnungsgemäß heruntergefahren wurde. Dadurch wird sichergestellt, dass der EA sicher entfernt werden kann, ohne dass unnötige Ressourcen zugewiesen werden. Hier ist ein Beispiel für ein Ergebnis.
Nachdem wir uns um die Instanz gekümmert haben, in der das Programm gestoppt wird, können wir zum letzten Event-Handler übergehen, dem wichtigsten, in dem Ticks verarbeitet werden, der Funktion OnTick.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Copy RSI values if (CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) <= 0) { //--- Attempt to copy RSI buffer values. Print("Failed to copy RSI buffer. Error: ", GetLastError()); //--- Log failure if copying fails. return; //--- Exit the function on failure to avoid processing invalid data. } //--- }
In der Funktion OnTick wird zunächst versucht, die RSI-Werte mit der Funktion CopyBuffer in das Array „rsiBuffer“ zu kopieren. Die Funktion CopyBuffer wird mit folgenden Parametern aufgerufen: dem RSI-Indikator-Handle „rsiHandle“, dem Pufferindex 0 (der den primären RSI-Puffer angibt), der Startposition 1 (wo das Kopieren der Daten beginnen soll), der Anzahl der zu kopierenden Werte 2 und dem Array „rsiBuffer“, in dem die kopierten Daten gespeichert werden. Diese Funktion ruft die letzten beiden RSI-Werte ab und speichert sie im Puffer.
Als Nächstes wird überprüft, ob der Kopiervorgang erfolgreich war, indem ausgewertet wird, ob der zurückgegebene Wert größer als 0 ist. Wenn der Vorgang fehlschlägt (d. h. ein Wert kleiner oder gleich 0 zurückgegeben wird), wird eine Fehlermeldung mit der Funktion Print ausgegeben, die angibt, dass das Kopieren des RSI-Puffers fehlgeschlagen ist, und der Code GetLastError wird angezeigt, um Einzelheiten über den Fehler zu liefern. Nach der Protokollierung des Fehlers wird die Funktion sofort mit „return“ beendet, um eine weitere Verarbeitung auf der Grundlage ungültiger oder fehlender RSI-Daten zu verhindern. Dadurch wird sichergestellt, dass der EA nicht versucht, Handelsentscheidungen mit unvollständigen oder fehlerhaften Daten zu treffen, wodurch mögliche Fehler oder Verluste vermieden werden. Wenn wir den Prozess nicht abbrechen, bedeutet dies, dass wir die erforderlichen Daten haben und weiterhin Handelsentscheidungen treffen können.
//--- Check RSI crossover signals datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar. if (currentBarTime != lastBarTime) { //--- Ensure processing happens only once per bar. lastBarTime = currentBarTime; //--- Update the last processed bar time. if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) { //--- Check for RSI crossing below 30 (oversold signal). Print("BUY SIGNAL"); //--- Log a BUY signal. zoneRecovery.HandleSignal(ORDER_TYPE_BUY); //--- Trigger the Zone Recovery BUY logic. } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) { //--- Check for RSI crossing above 70 (overbought signal). Print("SELL SIGNAL"); //--- Log a SELL signal. zoneRecovery.HandleSignal(ORDER_TYPE_SELL); //--- Trigger the Zone Recovery SELL logic. } }
Hier prüfen wir bei jedem neuen Balken des Marktes auf Kreuzungs-Signale des RSI, um potenzielle Handelsgeschäfte auszulösen. Zunächst wird mit der Funktion iTime der Zeitstempel des aktuellen Balkens ermittelt. Die Funktion benötigt das Symbol (_Symbol), den Zeitrahmen (PERIOD_CURRENT) und den Balkenindex (0 für den aktuellen Balken). Dies liefert die „currentBarTime“, die den Zeitstempel des letzten abgeschlossenen Balkens darstellt.
Als Nächstes stellen wir sicher, dass die Handelslogik nur einmal pro Balken ausgeführt wird, indem wir die „currentBarTime“ mit der „lastBarTime“ vergleichen. Wenn die Zeiten unterschiedlich sind, bedeutet dies, dass sich ein neuer Balken gebildet hat, sodass wir mit der Verarbeitung fortfahren. Wir aktualisieren dann „lastBarTime“, damit sie mit der „currentBarTime“ übereinstimmt, um den zuletzt verarbeiteten Balken zu verfolgen und wiederholte Ausführungen während desselben Balkens zu verhindern.
Der nächste Schritt besteht darin, Kreuzungs-Signale des RSI zu erkennen. Zunächst wird geprüft, ob der RSI-Wert unter 30 gefallen ist (eine überverkaufte Bedingung), indem „rsiBuffer[1]“ (der RSI-Wert des vorherigen Balkens) mit „rsiBuffer[0]“ (dem RSI-Wert des aktuellen Balkens) verglichen wird. Wenn der RSI des vorangegangenen Balkens über 30 lag und der RSI des aktuellen Balkens bei oder unter 30 liegt, deutet dies auf ein potenzielles KAUF-Signal hin. Wir drucken also die Meldung „BUY SIGNAL“ und rufen dann die Funktion „HandleSignal“ des Objekts „zoneRecovery“ auf, um den Erholungsprozess für einen KAUF-Auftrag auszulösen.
In ähnlicher Weise prüfen wir, ob der RSI die 70er-Marke überschritten hat (ein überkaufter Zustand). Wenn der RSI des vorangegangenen Balkens unter 70 lag und der RSI des aktuellen Balkens bei oder über 70 liegt, handelt es sich um ein potenzielles VERKAUFSSIGNAL, und wir drucken „SELL SIGNAL“. Dann rufen wir „HandleSignal“ erneut auf, aber dieses Mal für einen SELL-Auftrag, wodurch die entsprechende Zone Recovery VERKAUFS-Logik ausgelöst wird. Schließlich rufen wir nur noch die entsprechenden Funktionen auf, um die geöffneten Zonen zu verwalten und sie zu schließen, wenn die Ziele erreicht sind.
//--- Manage zone recovery logic zoneRecovery.ManageZones(); //--- Perform zone recovery logic for active positions. //--- Check and close at zone targets zoneRecovery.CheckCloseAtTargets(); //--- Evaluate and close trades when target levels are reached.
Hier verwenden wir den Punktoperator „ . “, um Funktionen aufzurufen, die Teil der Klasse „ZoneRecovery“ sind. Zunächst führen wir mit „zoneRecovery.ManageZones()“ die Methode „ManageZones“ aus, die die Logik für die Verwaltung von Handelsgeschäften mit Zone Recovery auf der Grundlage des aktuellen Kurses und der definierten Erholungszonen übernimmt. Bei dieser Methode wird die Losgröße für Recovery Handelsgeschäfte angepasst und bei Bedarf werden neue Positionen eröffnet.
Als Nächstes rufen wir „zoneRecovery.CheckCloseAtTargets()“ auf, um die Methode „CheckCloseAtTargets“ auszulösen, die prüft, ob der Kurs die Zielniveaus für das Schließen der Positionen erreicht hat. Wenn die Bedingungen erfüllt sind, wird versucht, die offenen Handelsgeschäfte zu schließen, um sicherzustellen, dass die Strategie im Einklang mit den angestrebten Gewinn- oder Verlustgrenzen bleibt. Mit dem Punktoperator greifen wir auf diese Methoden zu und führen sie auf dem Objekt „zoneRecovery“ aus, um den Erholungsprozess effektiv zu verwalten. Um sicherzustellen, dass die Methoden bei jedem Tick erfolgreich aufgerufen werden, führen wir das Programm aus, und hier ist das Ergebnis.
Aus dem Bild können wir ersehen, dass wir die Methoden der Klasse erfolgreich aufgerufen haben, um das Programm beim ersten Tick vorzubereiten, was bestätigt, dass unsere Programmklasse verbunden und arbeitsbereit ist. Um auch dies zu bestätigen, führen wir das Programm aus, und hier sind die Handelsbestätigungen.
Aus dem Bild ist ersichtlich, dass wir ein Kaufsignal bestätigen, eine Position eröffnen, sie dem Zone Recovery-System hinzufügen, die Zonenstufen neu berechnen, erkennen, dass es sich um eine Anfangsposition handelt, und, wenn das Ziel erreicht ist, schließen wir die Position und setzen das System für das nächste Handelsgeschäft zurück. Versuchen wir es mit einem Fall, in dem wir ein Zone Recovery-System eingeben.
Aus dem Bild können wir ersehen, dass wir, wenn der Markt um 200 Punkte gegen uns läuft, davon ausgehen, dass der Trend nach oben geht, und wir folgen ihm, indem wir eine Kaufposition mit einer höheren Lotgröße eröffnen, in diesem Fall 0,2.
Auch hier sehen wir, dass wir, wenn der Markt die Zielmarken erreicht, die Handelsgeschäfte schließen und ein neues aufsetzen. Während sich das System im Erholungsmodus befindet, ignorieren wir alle eingehenden Signale. Damit ist sichergestellt, dass wir unser Ziel erreicht haben, und es bleibt nur noch, das Programm einem Backtest zu unterziehen und seine Leistung zu analysieren. Dies wird im nächsten Abschnitt behandelt.
Backtest und Leistungsanalyse
In diesem Abschnitt konzentrieren wir uns auf den Prozess des Backtests und der Analyse der Leistung unseres Zone Recovery RSI Systems. Das Backtest ermöglicht es uns, die Effektivität unserer Strategie anhand historischer Daten zu bewerten, potenzielle Schwachstellen zu identifizieren und die Parameter fein abzustimmen, um im Live-Handel bessere Ergebnisse zu erzielen.
Wir beginnen mit der Einrichtung des Strategietesters auf der Plattform MetaTrader 5. Der Strategietester ermöglicht es uns, historische Marktbedingungen zu simulieren und Handelsgeschäfte so auszuführen, als ob sie in Echtzeit stattfinden würden. Um einen Backtest durchzuführen, wählen wir das entsprechende Symbol, den Zeitrahmen und den Testzeitraum aus. Wir vergewissern uns auch, dass der „visuelle Modus“ aktiviert ist, wenn wir die Abschlüsse auf dem Chart sehen wollen, während sie stattfinden.
Sobald die Backtest-Umgebung fertig ist, konfigurieren wir die Eingaben für unser Programm. Wir geben die Ersteinlage, die Losgröße und die spezifischen Parameter in Bezug auf die Logik der Zone Recovery an. Zu den Schlüsseleingaben gehören „initial lot size“, „zone size“, „target size“ und „multiplier“. Wenn wir diese Inputs variieren, können wir analysieren, wie sie sich auf die Gesamtrentabilität der Strategie auswirken. Wir haben folgende Änderungen vorgenommen.
ZoneRecovery zoneRecovery(0.1, 700, 1400, 2.0, _Symbol); //--- Initialize the ZoneRecovery object with specified parameters.
Wir haben das System so konfiguriert, dass es ab dem ersten Tag im Januar 2024 ein ganzes Jahr lang läuft, und hier sind die Ergebnisse.
Grafik des Strategietesters:
Bericht des Strategietesters:
Anhand des Diagramms und der Berichtsergebnisse können wir sicher sein, dass unsere Strategie wie erwartet funktioniert. Wir können jedoch die Leistung des Programms noch steigern, indem wir sicherstellen, dass wir uns auf die Gewinnmaximierung beschränken, indem wir eine Trailing-Logik hinzufügen, wodurch wir, anstatt auf ein vollständiges Gewinnziel zu warten, was uns die meiste Zeit Erholungsphasen aussetzt, die kleinen Gewinne, die wir haben, schützen und sie durch die Anwendung eines Trailing-Stops maximieren können. Da wir nur die ersten Positionen nachziehen können, können wir eine Logik haben, die sicherstellt, dass wir nur die ersten Positionen nachziehen, und wenn wir in einen Erholungsmodus eintreten, warten wir auf eine vollständige Erholung. Daher benötigen wir zunächst eine Trailing-Stop-Funktion.
//+------------------------------------------------------------------+ //| FUNCTION TO APPLY TRAILING STOP | //+------------------------------------------------------------------+ void applyTrailingStop(double slPoints, CTrade &trade_object, int magicNo=0, double minProfitPoints=0){ double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - slPoints*_Point, _Digits); //--- Calculate the stop loss price for BUY trades double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + slPoints*_Point, _Digits); //--- Calculate the stop loss price for SELL trades for (int i = PositionsTotal() - 1; i >= 0; i--){ //--- Loop through all open positions ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position if (ticket > 0){ //--- Check if the ticket is valid if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number if (PositionGetString(POSITION_SYMBOL) == _Symbol && //--- Check if the position belongs to the current symbol (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)){ //--- Check if the position matches the given magic number or if no magic number is specified double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get the opening price of the position double positionSl = PositionGetDouble(POSITION_SL); //--- Get the current stop loss of the position if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ //--- Check if the position is a BUY trade double minProfitPrice = NormalizeDouble(positionOpenPrice + minProfitPoints * _Point, _Digits); //--- Calculate the minimum price at which profit is locked if (buySl > minProfitPrice && //--- Check if the calculated stop loss is above the minimum profit price buySl > positionOpenPrice && //--- Check if the calculated stop loss is above the opening price (buySl > positionSl || positionSl == 0)){ //--- Check if the calculated stop loss is greater than the current stop loss or if no stop loss is set trade_object.PositionModify(ticket, buySl, PositionGetDouble(POSITION_TP)); //--- Modify the position to update the stop loss } } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ //--- Check if the position is a SELL trade double minProfitPrice = NormalizeDouble(positionOpenPrice - minProfitPoints * _Point, _Digits); //--- Calculate the minimum price at which profit is locked if (sellSl < minProfitPrice && //--- Check if the calculated stop loss is below the minimum profit price sellSl < positionOpenPrice && //--- Check if the calculated stop loss is below the opening price (sellSl < positionSl || positionSl == 0)){ //--- Check if the calculated stop loss is less than the current stop loss or if no stop loss is set trade_object.PositionModify(ticket, sellSl, PositionGetDouble(POSITION_TP)); //--- Modify the position to update the stop loss } } } } } } }
Hier erstellen wir eine Funktion namens „applyTrailingStop“, mit der wir einen Trailing-Stop auf alle offenen Kauf- und Verkaufspositionen anwenden können. Der Zweck dieses Trailing-Stopps ist es, Gewinne zu schützen und zu sichern, wenn sich der Markt zu Gunsten unserer Handelsgeschäfte bewegt. Wir verwenden das „CTrade“-Objekt, um die Stop-Loss-Levels für die Handelsgeschäfte automatisch zu ändern. Um sicherzustellen, dass der Trailing-Stop nicht zu früh aktiviert wird, haben wir eine Bedingung eingebaut, die das Erreichen eines Mindestgewinns voraussetzt, bevor der Stop-Loss ausgelöst wird. Dieser Ansatz verhindert verfrühte Stop-Loss-Anpassungen und stellt sicher, dass wir uns vor dem Trailing einen gewissen Gewinn sichern.
Wir definieren vier Schlüsselparameter in dieser Funktion. Der Parameter „slPoints“ gibt den Abstand in Punkten zwischen dem aktuellen Marktpreis und dem neuen Stop-Loss-Niveau an. Der Parameter „trade_object“ bezieht sich auf das „CTrade“-Objekt, das es uns ermöglicht, offene Positionen zu verwalten, den Stop-Loss zu ändern und den Take-Profit anzupassen. Der Parameter „magicNo“ dient als eindeutiger Bezeichner zum Filtern von Handelsgeschäften. Wenn „magicNo“ auf 0 gesetzt ist, wird der Trailing-Stop auf alle Handelsgeschäfte angewendet, unabhängig von ihrer magischen Zahl. Schließlich definiert der Parameter „minProfitPoints“ den Mindestgewinn (in Punkten), der erreicht werden muss, bevor der Trailing-Stop aktiviert wird. Dadurch wird sichergestellt, dass wir den Stop-Loss erst dann anpassen, wenn die Position einen ausreichenden Gewinn erzielt hat.
Hier beginnen wir mit der Berechnung der nachlaufenden Stop-Loss-Kurse für KAUF- und VERKAUFS-Handelsgeschäfte. Für Käufe berechnen wir den neuen Stop-Loss-Kurs, indem wir „slPoints“ vom aktuellen BID-Kurs abziehen. Für Verkäufe berechnen wir ihn, indem wir „slPoints“ zum aktuellen ASK-Kurs addieren. Diese Stop-Loss-Kurse werden mit _Digits normalisiert, um die Genauigkeit auf der Grundlage der Kursgenauigkeit des Symbols zu gewährleisten. Dieser Normalisierungsschritt stellt sicher, dass die Preise die richtige Anzahl von Dezimalstellen für das jeweilige Finanzinstrument aufweisen.
Als Nächstes werden alle offenen Positionen in einer Schleife durchlaufen, beginnend mit der letzten Position und dann mit der ersten. Dieser Ansatz der umgekehrten Schleife ist unerlässlich, da die Änderung von Positionen während einer Vorwärtsschleife zu Fehlern bei der Positionsindizierung führen kann. Für jede Position erhalten wir ein „Ticket“, das eine eindeutige Kennung für diese Position darstellt. Wenn das Ticket gültig ist, verwenden wir die Funktion PositionSelectByTicket, um die Details der Position auszuwählen und abzurufen.
Sobald wir die Position ausgewählt haben, prüfen wir, ob sie mit dem aktuellen Symbol übereinstimmt und ob ihre magische Zahl mit der angegebenen „magicNo“ übereinstimmt. Wenn „magicNo“ auf 0 gesetzt ist, wird der Trailing-Stop auf alle Handelsgeschäfte angewendet, unabhängig von ihrer magischen Zahl. Nachdem wir eine passende Position identifiziert haben, bestimmen wir, ob es sich um einen KAUF oder VERKAUF handelt.
Wenn es sich bei der Position um einen KAUF handelt, berechnen wir den Mindestpreis, den der Markt erreichen muss, bevor der Stop-Loss nachläuft. Dieser Wert ergibt sich aus der Addition von „minProfitPoints“ zum Eröffnungskurs der Position. Anschließend wird geprüft, ob der berechnete Trailing-Stop-Kurs sowohl über dem Eröffnungskurs der Position als auch über dem aktuellen Stop-Loss liegt. Wenn diese Bedingungen erfüllt sind, ändern wir die Position mit „trade_object.PositionModify“ und aktualisieren den Stop-Loss-Kurs für den BUY-Handel.
Wenn es sich bei der Position um einen VERKAUF handelt, gehen wir ähnlich vor. Wir berechnen den Mindestgewinnpreis, indem wir „minProfitPoints“ vom Eröffnungskurs der Position abziehen. Wir prüfen, ob der berechnete Trailing-Stop-Kurs sowohl unter dem Eröffnungskurs der Position als auch unter dem aktuellen Stop-Loss liegt. Wenn diese Bedingungen erfüllt sind, ändern wir die Position mit „trade_object.PositionModify“ und aktualisieren den Stop-Loss für die Verkaufsposition.
Mit dieser Funktion ausgestattet, benötigen wir nun eine Logik, um zunächst die Anfangspositionen zu finden, und zu diesen Funktionen können wir die Trailing-Stop-Logik hinzufügen. Zu diesem Zweck müssen wir eine boolesche Variable in der Klasse der Zone Recovery definieren. Wichtig ist jedoch, dass sie überall im Programm zugänglich ist, indem sie öffentlich gemacht wird.
public: bool isFirstPosition;
Hier haben wir eine öffentliche Variable namens „isFirstPosition“ innerhalb der Klasse „ZoneRecovery“. Diese Variable ist vom Typ Boolean (bool), das heißt, sie kann nur zwei mögliche Werte enthalten: true oder false. Der Zweck dieser Funktion ist es, festzustellen, ob sich das aktuelle Handelsgeschäft in der ersten Position im Prozess der Zone Recovery befindet. Wenn „isFirstPosition“ wahr ist, bedeutet dies, dass keine vorherigen Handelsgeschäfte eröffnet wurden und dies die erste Position ist. Diese Unterscheidung ist wichtig, da sich die Logik für das erste Handelsgeschäft ändern wird, da wir die Trailing-Stop-Logik auf ihn anwenden wollen.
Da wir „isFirstPosition“ als öffentlich deklarieren, kann von außerhalb der Klasse „ZoneRecovery“ darauf zugegriffen und sie verändert werden. Dadurch können andere Teile des Programms prüfen, ob eine Position die erste in einer Reihe ist, und ihren Status entsprechend aktualisieren. In der Funktion, die für die Eröffnung von Handelsgeschäften zuständig ist, müssen wir nun die booleschen Flags zuweisen, die angeben, ob es sich um eine erste Position handelt oder nicht, sobald eine Position eröffnet wird.
if (trade.Buy(currentLotSize, symbol)) { lastOrderType = ORDER_TYPE_BUY; //--- Mark the last trade as BUY. lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price. CalculateZones(); //--- Recalculate zones after placing the trade. Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize); isFirstPosition = isRecovery ? false : true; isRecovery = true; //--- Set recovery state to true after the first trade. return true; }
Hier setzen wir die Variable „isFirstPosition“ auf false, wenn die Position als Erholungsposition registriert ist, oder auf true, wenn die Variable „isRecovery“ false ist. Auch hier setzen wir in den Konstruktor- und Rücksetzfunktionen die Zielvariable auf false. Von dort aus können wir zum „OnTick“-Ereignishandler gehen und den Trailing-Stop anwenden, wenn wir eine Anfangsposition haben.
if (zoneRecovery.isFirstPosition == true){ //--- Check if this is the first position in the Zone Recovery process applyTrailingStop(100, obj_Trade, 0, 100); //--- Apply a trailing stop with 100 points, passing the "obj_Trade" object, a magic number of 0, and a minimum profit of 100 points }
Hier wird geprüft, ob die Variable „zoneRecovery.isFirstPosition“ wahr ist, was bedeutet, dass dies die erste Position im Prozess der Zone Recovery ist. Wenn ja, rufen wir die Funktion „applyTrailingStop“ auf. Die übergebenen Parameter sind „100“ Punkte für den Trailing-Stop-Abstand, „obj_Trade“ als Handelsobjekt, eine magische Zahl von „0“ zur Identifizierung des Handels und ein Mindestgewinn von „100“ Punkten. Dadurch wird sichergestellt, dass, sobald das Handelsgeschäft einen Gewinn von 100 Punkten erreicht, der Trailing-Stop angewendet wird, um die Gewinne zu schützen, indem der Stop-Loss nachgezogen wird, wenn sich der Kurs zu Gunsten des Handels bewegt. Wenn wir jedoch die Handelsgeschäfte per Trailing-Stop schließen, haben wir immer noch Reste der Logik der Zone Recovery, da wir sie nicht zurücksetzen. Dies veranlasst das System, Handelsgeschäfte für eine Erholung von dem Verlust zu eröffnen, auch wenn keine Handelsgeschäfte vorhanden sind. Das meinen wir damit.
Anhand der Visualisierung können Sie sehen, dass wir das System zurücksetzen müssen, sobald die Ausgangsposition abgefahren ist. Hier ist die Logik, die wir dafür anwenden müssen.
if (zoneRecovery.isFirstPosition == true && PositionsTotal() == 0){ //--- Check if this is the first position and if there are no open positions zoneRecovery.Reset(); //--- Reset the Zone Recovery system, restoring initial settings and clearing previous trade data }
Hier wird geprüft, ob die Variable „isFirstPosition“ wahr ist und ob es keine Positionen gibt. Wenn beide Bedingungen erfüllt sind, bedeutet dies, dass wir eine Anfangsposition hatten, die aus welchem Grund auch immer geschlossen wurde, und jetzt, da sie nicht mehr existiert, rufen wir die Funktion „zoneRecovery.Reset()“ auf. Dadurch wird das Zonensystem für die Erholung zurückgesetzt, indem seine ursprünglichen Einstellungen wiederhergestellt und alle früheren Handelsdaten gelöscht werden, sodass der Erholungsprozess neu beginnt. Diese Änderungen machen das System perfekt. Die abschließenden Tests haben zu folgenden Ergebnissen geführt.
Grafik des Strategietesters:
Bericht des Strategietesters:
Aus dem Bild ist ersichtlich, dass wir die Anzahl der Erholungspositionen reduziert haben, was unsere Trefferquote deutlich erhöht. Dies bestätigt, dass wir unser Ziel erreicht haben, ein System zur Erholung von Zonen mit einer dynamischen Handelsverwaltungslogik zu schaffen.
Schlussfolgerung
Abschließend haben wir gezeigt, wie man einen Expert Advisor in MetaQuotes Language 5 (MQL5) mit der Zone Recovery Strategie erstellt. Durch die Kombination des IndikatorsRelative Strength Index (RSI) mit der Logik der „Zone Recovery“ haben wir ein System geschaffen, das in der Lage ist, Handelssignale zu erkennen, Erholungspositionen zu verwalten und Gewinne mit Trailing-Stops zu sichern. Zu den Schlüsselelementen gehörten die Identifizierung von Signalen, die automatische Handelsausführung und die dynamische Erholung von Positionen.
Haftungsausschluss: Dieser Artikel dient als Leitfaden für die Entwicklung von MQL5-Programmen. Obwohl die Strategie der „Zone Recovery RSI“ einen strukturierten Ansatz für das Handelsmanagement bietet, bleiben die Marktbedingungen unvorhersehbar. Der Handel birgt ein finanzielles Risiko, und frühere Leistungen sind keine Garantie für zukünftige Ergebnisse. Ordnungsgemäße Tests und Risikomanagement sind vor dem Live-Handel unerlässlich.
Wenn Sie die in diesem Leitfaden beschriebenen Konzepte beherrschen, können Sie anpassungsfähigere Handelssysteme aufbauen und neue Strategien für den algorithmischen Handel erforschen. Viel Spaß beim Kodieren und ein erfolgreicher Handel!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16705





- 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.