Einführung in MQL5 (Teil 24): Erstellen eines EAs, der mit Chart-Objekten handelt
Einführung
Willkommen zurück zu Teil 24 der Serie über die Einführung in MQL5! In diesem Artikel gehen wir einen großen Schritt weiter, indem wir die manuelle Chartanalyse mit der automatisierten Handelsausführung kombinieren. Insbesondere werden wir einen Expert Advisor erstellen, der auf der Grundlage von Chart-Objekten wie Rechtecken, Trendlinien sowie Unterstützungs- und Widerstandslinien Handelsgeschäfte durchführt.
Diese Methode füllt die Lücke zwischen dem automatisierten und dem diskretionären Handel. Indem Sie nur Elemente auf Ihrem Chart skizzieren, können Sie Ihren EA nun visuell leiten, anstatt sich nur auf vorgegebene technische Parameter zu verlassen. Nachdem diese Elemente identifiziert wurden, verfolgt der EA die Kursveränderungen und initiiert automatisch Handelsgeschäfte, sobald der Preis mit ihnen in Berührung kommt.
In diesem Artikel erfahren Sie auch, wie Sie Chart-Objekte programmatisch erkennen und diese Koordinaten zur Berechnung von Handelsein- und -ausstiegen verwenden können. Außerdem werden wir den EA so konfigurieren, dass er dynamisch reagiert, d. h. er passt sich sofort an alle Änderungen an, die Sie an den Elementen in Ihrem Chart vornehmen.
Wie der EA funktioniert
Wir erstellen einen Expert Advisor, der mit Hilfe von Chart-Objekten Trendumkehrungen an strategischen Zonen erkennt. Wir werden einen Support and Resistance EA erstellen, bei dem der Nutzer mögliche Umkehrpunkte auf dem Chart mit einem Rechteckobjekt manuell markiert. Der EA wird diese Region im Auge behalten und darauf warten, dass der Preis sie erreicht, wenn Sie ein Rechteck über dem Preis platzieren, um eine Widerstandszone anzuzeigen.
Der EA wartet auf eine Abwärts-Veränderung des Charakters, um zu bestätigen, dass sich ein möglicher Umschwung bildet, bevor er einen Handel ausführt, sobald der Preis die Zone erreicht. Der EA wird automatisch einen Verkaufshandel einleiten, wenn der Kurs innerhalb des Rechtecks reagiert, ohne darüber auszubrechen, und wenn die Abwärts-Veränderung des Charakters erkannt wird.
Wenn der Markt anfängt, niedrigere Hochs und niedrigere Tiefs (Abwärts-Struktur) anstelle von höheren Hochs und höheren Tiefs (Aufwärts-Struktur) zu produzieren, sagt man, dass er einen Abwärts-Charakterwechsel durchgemacht hat. Um dies zu erkennen, sucht der EA nach vier signifikanten Kursbewegungen:
- Ein Tief,
- gefolgt von einem Hoch,
- dann ein höheres Tief,
- dann ein höheres Hoch,
- und schließlich ein Durchbruch unter das vorherige höhere Tief.
Die Aufwärts-Struktur ist durchbrochen worden, und es hat eine negative Veränderung stattgefunden, die durch den letzten Durchbruch unter das höhere Tief bestätigt wurde. Der EA platziert eine Verkaufsorder, wenn diese Strukturverschiebung innerhalb oder in der Nähe des Widerstandsrechtecks auftritt, was auf eine hohe Wahrscheinlichkeit einer Umkehrung hinweist.

Eine Unterstützungszone folgt der gleichen Logik. Um die Unterstützung zu symbolisieren, zeichnet der Nutzer ein Rechteck unter dem Kurs, das einen potenziellen Ort für eine Aufwärts-Umkehr darstellt. Der EA wartet geduldig auf eine Aufwärts-Veränderung des Charakters, bevor er Handlungen vornimmt, sobald der Preis diese Zone erreicht. Vier Umkehrpunkte können auch dazu dienen, den Aufwärtstrend zu erkennen:
- Ein Hoch,
- gefolgt von einem Tief,
- dann ein niedrigeres Hoch,
- dann ein tieferes Tief,
- und schließlich ein Durchbruch über das vorherige niedrigere Hoch.
Die Marktstruktur hat sich von abwärts zu aufwärts verändert, wie der letzte Durchbruch über das untere Hoch zeigt. Dies deutet auf eine wahrscheinliche Aufwärts-Umkehrung hin, und der EA führt automatisch einen Kauf durch, wenn dieser innerhalb oder nahe dem Unterstützungsrechteck stattfindet.

Identifizieren von Chart-Objekten
Da das bestimmt, wie der EA das Rechteck auswählt, das er als Unterstützung oder Widerstand nutzt, ist die Identifizierung von Chart-Objekten ein wichtiger Bestandteil dieses Projekts. Ein EA kann Chart-Objekte identifizieren, die er selbst entwickelt hat, aber er kann keine Objekte identifizieren, die ein Nutzer manuell gezeichnet hat. Der EA muss in der Lage sein, genau zu bestimmen, welche der vielen Elemente, Rechtecke, Trendlinien, Textbeschriftungen oder Formulare, die häufig im Chart vorhanden sind, für seine Argumentation verwendet werden sollen.
Wir verwenden die Funktion ObjectCreate(), wenn wir Code zum Erstellen von Chart-Objekten schreiben. Mit dieser Funktion kann der EA verschiedene Objekte auf dem Chart zeichnen. Es ist jedoch wichtig zu wissen, dass Sie jederzeit auf die Eigenschaften eines Objekts zugreifen oder sie ändern können, nachdem es erstellt worden ist. Bei diesen Attributen kann es sich um Farbe, Stil, Sichtbarkeit, Zeit- und Preisniveau usw. handeln.
Eine ausführlichere Erklärung der Funktionsweise von ObjectCreate() finden Sie in Teil 9 dieser Serie, in dem ich beschrieben habe, wie Sie Chart-Objekte in Ihrem Code erstellen.
Der wichtigste Aspekt ist, dass in MQL5 der Name eines Objekts alles über das Objekt aussagt. Sie können die Details eines Objekts abrufen oder ändern, indem Sie einfach seinen Namen kennen, unabhängig davon, ob es manuell vom Nutzer oder automatisch vom EA erstellt wurde. Die Frage nach den oberen und unteren Kursniveaus oder die Überprüfung der zeitlichen Grenzen eines Rechteckobjekts, das für den Widerstand verwendet wird, ist einfach, wenn man seinen Namen kennt.
Aus diesem Grund fungiert der Objektname als eine Art Bindeglied zwischen der Logik des EA und dem Chart des Nutzers. Es ebnet den Weg für präzise automatisierte Entscheidungen, indem es dem EA ermöglicht, die genauen Koordinaten der gezeichneten Zone zu lesen und diese Informationen zu nutzen, um zu bestimmen, wann der Preis in diesen Bereich eintritt.
Das Problem ist, wie wir den Namen eines Objekts in unseren Code einfügen können, den wir noch nicht kennen. Bedenken Sie: Noch bevor der Nutzer das Objekt auf den Chart zeichnet, ist die Software bereits kompiliert und in Betrieb. Wie ist es also möglich, dass der EA im Voraus weiß, welchen Objektnamen er verwenden soll?
Der Name des Objekts kann direkt aus dem Chart abgerufen werden, auch nachdem es gezeichnet wurde, und zwar auf einfache und effiziente Weise. Diese Technik ermöglicht es dem EA, vom Nutzer erstellte Elemente dynamisch zu erkennen, ohne dass der Name im Voraus fest codiert werden muss. Lassen Sie uns darüber sprechen, wie das funktioniert.
Beispiel:input string h_line = ""; // Horizontal Line ulong chart_id = ChartID(); double line_price; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- line_price = ObjectGetDouble(chart_id,h_line,OBJPROP_PRICE,0); Comment(line_price); }
Erläuterung:
Die Erwartung, dass der Nutzer eine horizontale Linie in das Chart einfügt, ist der erste Schritt bei diesem Ansatz. Daraufhin haben wir den Eingabeparameter „input string h_line“ entwickelt. Der Nutzer kann den genauen Namen der horizontalen Linie, die er auf dem Chart erstellt hat, über diesen Eingang manuell eingeben oder kopieren. Der EA weiß dann, mit welchem Objekt er sich befassen muss.
Danach haben wir die Funktion ChartID() verwendet, um die Chart-ID zu erhalten. Wenn mehrere Charts auf dem MetaTrader 5 geöffnet sind, garantiert dies, dass der EA mit dem entsprechenden Chart kommuniziert. Um das Preisniveau des Objekts, dessen Name in der Eingabe angegeben wurde, zu erhalten, verwenden wir die Funktion ObjectGetDouble() innerhalb der Funktion OnTick(). Zu den Parametern, die dieser Funktion übergeben werden, gehören die Chart-ID, der Objektname, die Eigenschaft, auf die wir zugreifen möchten (OBJPROP_PRICE), und ein Indexwert. Der Index für eine horizontale Linie wird auf 0 gesetzt.
Um zu überprüfen, ob der EA die Daten des manuell erstellten Objekts richtig gefunden und extrahiert hat, zeigt die Funktion Comment() schließlich den Wert des Preisniveaus des Artikels sofort im Chart an. Diese Methode macht den EA vielseitig und einfach zu verwenden, da er dynamisch mit jedem Objektnamen arbeiten kann, den der Nutzer angibt.
Ausgabe:

Klicken Sie einfach mit der rechten Maustaste auf das Objekt, das Sie im Chart gezeichnet haben, und wählen Sie im Menü Eigenschaften, um den Namen festzulegen. Im Eigenschaftsfenster wird ein Feld „Name“ angezeigt. Der Inhalt von „Name“ sollte genau so kopiert werden, wie er ist, und in die Eingabeeinstellungen des EA eingefügt werden. Wahlweise können Sie auch einen einprägsameren Namen wählen, z. B. ResistanceZone oder SupportArea.

Gehen Sie dann zu den Eingabeeinstellungen des EA und fügen Sie den Namen des Objekts in das Eingabefeld ein. Dies ist derselbe Eingabeparameter, den wir zuvor erstellt haben, um den Namen des Objekts zu erhalten.

Auf diese Weise verbinden Sie das manuell erstellte Chart-Objekt mit dem EA. Nach der Eingabe des Namens kann der EA diesen bestimmten Artikel identifizieren, seine Informationen, einschließlich Koordinaten oder Preisniveaus, abrufen und diese Daten nutzen, um zu entscheiden, wann ein Handel erfolgen soll.
Das Abrufen von Daten aus den Objekten des Charts ist der nächste Schritt, nachdem Sie gelernt haben, wie man sie identifiziert. In MQL5 enthält jedes Chart-Objekt eindeutige Daten; die Art der Daten, auf die Sie zugreifen können, variiert jedoch je nach Objekttyp. Da sich beispielsweise eine horizontale Linie auf einem festen Niveau über das gesamte Chart erstreckt, enthält sie nur Preisinformationen.

Während eine vertikale Linie nur Zeitinformationen enthält, da sie einen bestimmten Zeitpunkt darstellt.

Abrufen von Widerstandsobjektdaten
Die Rechtecke sind wichtig, da unser Projekt darauf angewiesen ist, sie zur Darstellung von Unterstützungs- und Widerstandszonen zu verwenden. Sie können sowohl Zeit- als auch Preisinformationen speichern. Jedes Rechteck enthält zwei Punkte und hat seine eigenen Zeit- und Preiskoordinaten.

Der EA kann die genaue Position des Rechtecks auf der Chart bestimmen, indem er diese Koordinaten erhält. Dadurch können der Zeitrahmen und die Preisspanne des vom Nutzer gezeichneten Unterstützungs- oder Widerstandsbereichs ermittelt werden, was für die Bestimmung des Zeitpunkts, zu dem sich der Markt in diese Zone bewegt, und für die Feststellung, ob sich ein mögliches Handels-Setup entwickelt, unerlässlich ist.
Ein rechteckiges Objekt wird durch zwei Punkte definiert, die gegenüberliegende Ecken darstellen und jeweils Zeit- und Preiskoordinaten haben. Je nachdem, wie das Rechteck gezeichnet wird, können diese Punkte ihre Rolle wechseln. Aufgrund dieser Flexibilität sollte der EA nicht davon ausgehen, dass ein Anker den oberen oder unteren Rand der Zone darstellt, wenn er Daten aus einem Rechteck erhält. Um den Unterstützungs- oder Widerstandsbereich richtig zu identifizieren, muss er stattdessen beide Koordinaten überprüfen und den niedrigeren Preis als untere Grenze und den höheren Preis als obere Begrenzung verwenden.
Beispiel:
input string reistance = ""; // Resistance Object Name ulong chart_id = ChartID(); double res_anchor1_price; double res_anchor2_price; double res_max_price; double res_min_price; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits); res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits); res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits); res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits); Comment("Anchor 1 Price: ",res_anchor1_price,"\nAnchor 2 Price: ",res_anchor2_price, "\n\nMax Resistance Price: ",res_max_price,"\nMin Resistance Price: ",res_min_price); }
Ausgabe:

Erläuterung:
Der Objektname wird als Eingabeparameter erzeugt. Er ermöglicht es Ihnen, den Namen des Rechteckobjekts aus dem Chart manuell zu kopieren und in die Eingabeeinstellungen einzufügen. Die Preisniveaus der beiden Anker im Rechteck werden dann in zwei Variablen gespeichert. Die Anker 1 und 2 sind die beiden Ecken eines jeden Rechtecks, und jede Ecke hat eine Koordinate für Zeit und Preis. Der EA ruft den Preis für beide Anker ab, sodass die Widerstandszone nur anhand der Preiswerte definiert werden kann.
Die Preiswerte der beiden Anker des Rechtecks werden vom EA mit der Funktion ObjectGetDouble gelesen. Der Punkt 1 wird in dieser Funktion durch den Indexwert 0 und der Punkt 2 durch den Indexwert 1 bezeichnet. Beide Anker haben ihre eigenen Zeit- und Preiskoordinaten, und jeder Anker steht für eine Ecke des Rechtecks. Der EA erhält nur die Preisdaten für jeden Anker, da wir nur an den Preisniveaus für dieses Projekt interessiert sind.
Der EA bestimmt, welcher dieser Werte höher und welcher niedriger ist, nachdem er sie erfasst hat. Die obere und untere Grenze der Widerstandszone wird durch den Höchst- bzw. Tiefstkurs dargestellt.
Dank dieser Logik liest der EA die Zonengrenzen genau, unabhängig davon, ob der Nutzer das Rechteck von oben nach unten oder von unten nach oben zeichnet. Diese beiden Grenzen werden genutzt, um die Preisentwicklung während des Projekts zu verfolgen. Bevor der EA eine Verkaufstransaktion durchführt, wartet er auf eine Abwärts-Veränderung des Marktverhaltens, wenn er sich dieser Widerstandszone nähert.
Daher kann der EA durch den Vergleich der Zeitwerte für die beiden Anker feststellen, welcher Anker den Anfang und welcher das Ende des Rechtecks darstellt. Durch die korrekte Interpretation der horizontalen Position des Rechtecks auf dem Chart stellt dieser Schritt sicher, dass der EA nur dann reagiert, wenn die aktuelle Marktzeit innerhalb des zulässigen Bereichs der gezeichneten Zone liegt.
Beispiel:input string reistance = ""; // Resistance Object Name ulong chart_id = ChartID(); double res_anchor1_price; double res_anchor2_price; double res_max_price; double res_min_price; long res_anchor1_time; long res_anchor2_time; datetime res_start_time; datetime res_end_time; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits); res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits); res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits); res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits); res_anchor1_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,0); res_anchor2_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,1); res_start_time = (datetime)MathMin(res_anchor1_time,res_anchor2_time); res_end_time = (datetime)MathMax(res_anchor1_time,res_anchor2_time); Comment("RESISTANCE START TIME: ",res_start_time,"\nRESISTANCE END TIME: ",res_end_time); }
Ausgabe:

Durch die Lokalisierung des ersten und zweiten Punktes (0 und 1) erhält dieser Teil die Zeitkoordinaten des Widerstandsrechtecks. Danach wird der frühere Zeitpunkt als Startzeitpunkt und der spätere als Endzeitpunkt festgelegt und verglichen, um festzustellen, welcher Zeitpunkt zuerst erreicht wurde. Dies garantiert, dass der EA unabhängig davon, wie das Rechteck gezeichnet wurde, den Zeitbereich der Widerstandszone genau bestimmt.
Abwärts-Charakterwechsel
Der nächste Schritt besteht darin, einen Abwärts-Charakterwechsel zu finden, der auf eine mögliche Umkehr von der Widerstandszone hinweist. Die EA wird Preisänderungen verfolgen, um festzustellen, ob eine spürbare strukturelle Anpassung stattfindet, um dies zu überprüfen. Insbesondere wird überprüft, ob die vom Nutzer gezeichnete Widerstandszone den Höchststand des Marktes enthält. Dies garantiert, dass die Preisgestaltung wirklich mit der Zone interagiert hat und dass sie legitim ist. Ein Abwärts-Charakterwechsel wird bestätigt, wenn der Markt dieses Hoch innerhalb der Widerstandszone etabliert und dann unter das vorherige höhere Tief bricht, was auf eine mögliche Abwärtsumkehr aus dieser Zone hindeutet.
Beispiel:
input string reistance = ""; // Resistance Object Name input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // TIME-FRAME ulong chart_id = ChartID(); double res_anchor1_price; double res_anchor2_price; double res_max_price; double res_min_price; long res_anchor1_time; long res_anchor2_time; datetime res_start_time; datetime res_end_time; int res_total_bars; double res_close[]; double res_open[]; double res_high[]; double res_low[]; datetime res_time[]; double high; datetime high_time; double low; datetime low_time; double higher_low; datetime higher_low_time; double higher_high; datetime higher_high_time; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(res_close,true); ArraySetAsSeries(res_open,true); ArraySetAsSeries(res_high,true); ArraySetAsSeries(res_low,true); ArraySetAsSeries(res_time,true); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits); res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits); res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits); res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits); res_anchor1_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,0); res_anchor2_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,1); res_start_time = (datetime)MathMin(res_anchor1_time,res_anchor2_time); res_end_time = (datetime)MathMax(res_anchor1_time,res_anchor2_time); res_total_bars = Bars(_Symbol,timeframe,TimeCurrent(),res_start_time); CopyOpen(_Symbol, timeframe, TimeCurrent(), res_start_time, res_open); CopyClose(_Symbol, timeframe, TimeCurrent(), res_start_time, res_close); CopyLow(_Symbol, timeframe, TimeCurrent(), res_start_time, res_low); CopyHigh(_Symbol, timeframe, TimeCurrent(), res_start_time, res_high); CopyTime(_Symbol, timeframe, TimeCurrent(), res_start_time, res_time); for(int i = 4; i < res_total_bars-3; i++) { if(IsSwingHigh(res_high, i, 3)) { higher_high = res_high[i]; higher_high_time = res_time[i]; for(int j = i; j < res_total_bars-3; j++) { if(IsSwingLow(res_low,j,3)) { higher_low = res_low[j]; higher_low_time = res_time[j]; for(int k = j; k < res_total_bars-3; k++) { if(IsSwingHigh(res_high, k, 3)) { high = res_high[k]; high_time = res_time[k]; for(int l = k; l < res_total_bars-3; l++) { if(IsSwingLow(res_low,l,3)) { // ObjectCreate(chart_id,"kk",OBJ_VLINE,0,res_time[l],0); ObjectDelete(chart_id,"kk"); low = res_low[l]; low_time = res_time[l]; for(int m = i; m > 0; m--) { if(res_close[m] < higher_low && res_open[m] > higher_low) { if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low) { ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low); ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high); ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low); ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2); } break; } } break; } } break; } } break; } } break; } } } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING LOW | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i]) return false; } return true; } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING HIGH | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i]) return false; // If the current high is not the highest, return false. } return true; }
Ausgabe:

Erläuterung:
Der Nutzer kann zunächst den Chart-Zeitrahmen auswählen, den der EA untersuchen soll, indem er den Zeitrahmen eingibt. Sie können ihn so ändern, dass er jeden unterstützten Zeitraum verwendet, aber standardmäßig wird das aktuelle Chart-Fenster verwendet. Dies ist von Bedeutung, da der Zeitrahmen für alle nachfolgenden Methoden zur Identifizierung der Marktstruktur verwendet wird, einschließlich Zeitstempel, Erkennen von Umkehrpunkten und dem Zählen der Balken. Die richtige Zeitspanne ist eine Design-Entscheidung, die ein Gleichgewicht zwischen Rauschen und Reaktionsfähigkeit herstellt.
Die Arrays, die historische Daten enthalten werden, werden während des Initialisierungsprozesses als Zeitreihen konfiguriert. Wenn sie als Serie eingestellt sind, befinden sich die ältesten Balken auf aufsteigenden Indizes, und der jüngste Balken wird auf dem Index Null gehalten. Bei der Suche nach den jüngsten Schwankungen ist es dank dieser Anordnung einfach, das Feld vom neuesten zum ältesten zu durchlaufen. Außerdem kann Ihre Schleifenlogik davon ausgehen, dass der Index Null der jüngste beendete Balken ist, da dies mit der Art und Weise übereinstimmt, in der MQL5 normalerweise Echtzeit-Seriendaten anzeigt.
Das Programm ermittelt dann die Anzahl der Balken, die den Beginn des Rechtecks von der aktuellen Zeit trennen. Die Größe des zu kopierenden Fensters wird durch diese Anzahl der Balken bestimmt. Der EA dupliziert die Open-, High-, Low-, Close- und Time-Arrays für jeden Balken in diesem Bereich, nachdem er diesen Zählwert kennt. Der Datensatz, den der EA untersucht, besteht aus diesen duplizierten Arrays. Der EA berücksichtigt nur Preisaktionen, die wirklich mit der vom Nutzer gezeichneten Zone interagiert haben, indem er die Daten auf die Zeitdauer des Rechtecks begrenzt.
Zwei kleine Hilfsfunktionen erkennen tiefe und hohe Umkehrpunkte, die der EA nutzt, um echte Wendepunkte zu identifizieren. Jede Funktion vergleicht eine Reihe von Kerzen links und rechts von einer Kandidatenkerze. Auf beiden Seiten gibt die Funktion für einen tiefen Umkehrpunkt „IsSwingLow“ nur dann den Wert „true" zurück, wenn das Tief des Kandidaten niedriger ist als die der Nachbarn. Wenn das Hoch des Kandidaten höher ist als die der Nachbarn auf beiden Seiten, gibt die Funktion für den hohen Umkehrpunkt „IsSwingHigh“ „true“ zurück. Durch die Verwendung eines Rückblickfensters auf beiden Seiten wird nicht nur sichergestellt, dass es sich bei den beobachteten Ausschlägen um lokal relevante Drehpunkte und nicht um zufälliges Rauschen handelt, sondern es werden auch falsch positive Ergebnisse durch isolierte Ticks vermieden.
Alles zusammengenommen funktioniert das Verfahren wie folgt. Um einen Anwärter für einen hohen Umkehrpunkt auszuwählen, untersucht der EA zunächst die duplizierten Preisfelder. Nach der Identifizierung wird nach dem nächsten tiefen Umkehrpunkt Ausschau gehalten, das zum Anwärter für ein höheres Tief wird. Nach der Suche nach einem weiteren hohen Umkehrpunkt wird dann nach einem nachfolgenden tiefen Umkehrpunkt gesucht. Nachdem diese vier Umkehrpunkte zu dem erwarteten Muster zusammengesetzt wurden, sucht der EA nach einem klaren Durchbruch, d. h. einer Kerze, die unterhalb des zuvor ermittelten höheren Tiefs schließt. Diese Nähe wird als Beweis für die Veränderung des Charakters angesehen.
Der EA bestätigt dann die Korrelationen zwischen den Ausschlägen, z. B. dass das höhere Tief tatsächlich unter dem höheren Hoch liegt und dass das nachfolgende Hoch und Tief in einer Weise positioniert sind, die der erwarteten Struktur entspricht.
Um die Richtigkeit der Abwärts-Veränderung des Charakters (ChoCH) zu überprüfen, müssen als Nächstes zwei wichtige Prüfungen durchgeführt werden. Der nächste Schritt besteht darin, festzustellen, ob seit dem Startzeitpunkt des Rechtecks kein Balken den maximalen Widerstandspreis überschritten hat. Wenn dies doch der Fall ist, ist der Widerstand nicht mehr gültig. Wir müssen zunächst eine Schleife um jeden Balken ziehen, alle Hochs vom Beginn des Rechtecks bis zum aktuellen Balken sammeln und dann bestimmen, welches das höchste Hoch ist.
Beispiel:int max_high_index; double max_high;
if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low) { ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low); ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high); ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low); ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2); max_high_index = ArrayMaximum(res_high,0,res_total_bars); max_high = res_high[max_high_index]; Comment(max_high); }
Ausgabe:

Erläuterung:
Der höchste Preis innerhalb des gewählten Bereichs und seine Position innerhalb des Feldes werden durch zwei Variablen bestimmt. Der reale Preis, der diesem Index entspricht, wird in der einen Variablen gespeichert, und der Index (Position) mit dem höchsten Wert wird in der anderen. Die Anwendung ruft dann den passenden Preiswert ab, nachdem sie den höchsten Preisindex des Arrays gefunden hat.
Die Echtheit der Widerstandszone kann nun anhand von zwei Bedingungen überprüft werden. Die erste Voraussetzung ist, dass keine Kerze die Widerstandszone durchbrochen hat, wenn das höchste Hoch unter dem maximalen Widerstandspreis liegt. Die zweite Bedingung ist, dass der höchste Wert der Zeichenänderung höher als der Mindestpreis des Widerstands, aber niedriger als der Höchstpreis sein muss. Dies deutet auf einen legitimen Reaktionspunkt hin, an dem sich ein möglicher negativer Charakterwandel vollziehen könnte, da der Markt tatsächlich die Widerstandszone erreicht hat.
Beispiel:if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low) { ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low); ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high); ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low); ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2); max_high_index = ArrayMaximum(res_high,0,res_total_bars); max_high = res_high[max_high_index]; if(max_high < res_max_price && higher_high > res_min_price && higher_high < res_max_price) { } }
Ausführung des Handels
Die Ausführung der Handelsgeschäfte erfolgt, nachdem sichergestellt wurde, dass der Höchststand innerhalb der Widerstandszone liegt und alle Voraussetzungen erfüllt sind. Dies deutet darauf hin, dass eine legitime negative Veränderung des Charakters stattgefunden hat und dass der Markt Anzeichen dafür gezeigt hat, den Widerstandsbereich zu respektieren. Basierend auf den Richtlinien der Strategie kann der Expert Advisor nun eine Verkaufsposition einleiten oder eine andere vorher festgelegte Handelsaktivität durchführen. Der Take-Profit (TP) wird durch ein vom Nutzer festgelegtes Risiko-Ertrags-Verhältnis bestimmt, und der Stop-Loss (SL) wird auf den Höchststand des Zeichenwechsels und nicht auf den höheren Höchststand gesetzt.
Beispiel:
#include <Trade/Trade.mqh> CTrade trade; int MagicNumber = 533915; // Unique Number double lot_size = 0.2; // Lot Size
double ask_price; double take_profit; datetime lastTradeBarTime = 0;
if(max_high < res_max_price && higher_high > res_min_price && higher_high < res_max_price) { if(res_time[1] == res_time[m] && currentBarTime != lastTradeBarTime) { take_profit = MathAbs(ask_price - ((high - ask_price) * RRR)); trade.Sell(lot_size,_Symbol,ask_price,high,take_profit); lastTradeBarTime = currentBarTime; } }
Ausgabe:

Erläuterung:
Sobald alle Handelsanforderungen erfüllt sind, ist dieser Teil der Software für die Planung und Durchführung eines Handelsgeschäfts zuständig. Die Handelsbibliothek, die die für die Durchführung von Handelsgeschäften erforderlichen Ressourcen bereitstellt, ist als erstes enthalten. Um den Expert Advisor in die Lage zu versetzen, Transaktionen zu initiieren, zu ändern und zu beenden, wird eine Instanz der Handelsklasse erstellt. Um zu verhindern, dass sie mit Transaktionen anderer EAs oder manuellen Positionen kollidieren, wird jeder von diesem EA getätigte Handel durch eine spezielle magische Zahl gekennzeichnet. Die Menge des pro Auftrag ausgetauschten Volumens wird durch die Losgröße bestimmt.
Um zu verhindern, dass mehrere Handelsgeschäfte innerhalb derselben Kerze eröffnet werden, gibt das Programm auch Variablen für den Briefkurs (den aktuellen Kurs, zu dem ein Verkaufsauftrag ausgeführt werden kann), das Take-Profit-Niveau und eine Variable an, die den Zeitpunkt der letzten durchgeführten Transaktion speichert. Danach wird die Zeit des aktuellen Balkens aufgezeichnet und der aktuelle Briefkurs des Symbols ermittelt.
Der EA überprüft zwei Dinge, bevor er einen Handel tätigt: Erstens, dass die Zeit mit dem genauen Zeitpunkt der Validierung der Widerstandszone übereinstimmt, und zweitens, dass sich die Barzeit jetzt von der Barzeit des vorherigen Handels unterscheidet (um wiederholte Einträge zu vermeiden). Die Software bestimmt das Take-Profit-Niveau anhand eines nutzerdefinierten Risiko-Ertrags-Verhältnisses und leitet einen Verkaufshandel ein, wenn diese Anforderungen erfüllt sind. Der Take-Profit wird anhand des berechneten Verhältnisses bestimmt, und der Stop-Loss wird auf den Höchststand der Zeichenänderung gesetzt.
Aufwärts-Charakterwechsel an der Unterstützungszone
Da die meisten der Argumente, die wir zur Identifizierung der Abwärts-Zeichenverschiebung an der Widerstandszone verwendet haben, auch hier gelten, werden wir nur kurz darauf eingehen. Um Missverständnissen oder Problemen vorzubeugen, müssen einige wichtige Details beachtet werden. Daher werden wir gleich zur Sache kommen und uns in diesem Teil hauptsächlich auf die grundlegenden Unterscheidungen konzentrieren.
Beispiel:
input string support = ""; // Support Object Name
double sup_anchor1_price; double sup_anchor2_price; double sup_max_price; double sup_min_price; long sup_anchor1_time; long sup_anchor2_time; datetime sup_start_time; datetime sup_end_time; int sup_total_bars; double sup_close[]; double sup_open[]; double sup_high[]; double sup_low[]; datetime sup_time[]; double lower_high; datetime lower_high_time; double lower_low; datetime lower_low_time; int min_low_index; double min_low; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(res_close,true); ArraySetAsSeries(res_open,true); ArraySetAsSeries(res_high,true); ArraySetAsSeries(res_low,true); ArraySetAsSeries(res_time,true); ArraySetAsSeries(sup_close,true); ArraySetAsSeries(sup_open,true); ArraySetAsSeries(sup_high,true); ArraySetAsSeries(sup_low,true); ArraySetAsSeries(sup_time,true); //--- return(INIT_SUCCEEDED); }
sup_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,support,OBJPROP_PRICE,0),_Digits); sup_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,support,OBJPROP_PRICE,1),_Digits); sup_max_price = NormalizeDouble(MathMax(sup_anchor1_price,sup_anchor2_price),_Digits); sup_min_price = NormalizeDouble(MathMin(sup_anchor1_price,sup_anchor2_price),_Digits); sup_anchor1_time = ObjectGetInteger(chart_id,support,OBJPROP_TIME,0); sup_anchor2_time = ObjectGetInteger(chart_id,support,OBJPROP_TIME,1); sup_start_time = (datetime)MathMin(sup_anchor1_time,sup_anchor2_time); sup_end_time = (datetime)MathMax(sup_anchor1_time,sup_anchor2_time); sup_total_bars = Bars(_Symbol,timeframe,TimeCurrent(),sup_start_time); CopyOpen(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_open); CopyClose(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_close); CopyLow(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_low); CopyHigh(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_high); CopyTime(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_time); for(int i = 4; i < sup_total_bars-3; i++) { if(IsSwingLow(sup_low, i, 3)) { lower_low = sup_low[i]; lower_low_time = sup_time[i]; for(int j = i; j < sup_total_bars-3; j++) { if(IsSwingHigh(sup_high,j,3)) { lower_high = sup_high[j]; lower_high_time = sup_time[j]; for(int k = j; k < sup_total_bars-3; k++) { if(IsSwingLow(sup_low, k, 3)) { low = sup_low[k]; low_time = sup_time[k]; for(int l = k; l < sup_total_bars-3; l++) { if(IsSwingHigh(sup_high,l,3)) { high = sup_high[l]; high_time = sup_time[l]; for(int m = i; m > 0; m--) { if(sup_close[m] > lower_high && sup_open[m] < lower_high) { if(lower_high > lower_low && low < lower_high && low > lower_low && high > low && high > lower_high) { ObjectCreate(chart_id,"LLLH",OBJ_TREND,0,lower_low_time,lower_low,lower_high_time,lower_high); ObjectSetInteger(chart_id,"LLLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LLLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LHL",OBJ_TREND,0,lower_high_time,lower_high,low_time,low); ObjectSetInteger(chart_id,"LHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LH",OBJ_TREND,0,low_time,low,high_time,high); ObjectSetInteger(chart_id,"LH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"S Cross Line",OBJ_TREND,0,lower_high_time,lower_high,sup_time[m],lower_high); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_WIDTH,2); min_low_index = ArrayMinimum(sup_low,0,sup_total_bars); min_low = sup_low[min_low_index]; if(min_low > sup_min_price && lower_low < sup_max_price && lower_low > sup_min_price) { if(sup_time[1] == sup_time[m] && currentBarTime != lastTradeBarTime) { take_profit = MathAbs(ask_price + ((ask_price - low) * RRR)); trade.Buy(lot_size,_Symbol,ask_price,low,take_profit); lastTradeBarTime = currentBarTime; } } } break; } } break; } } break; } } break; } } break; } }
Erläuterung:
Die Logik in diesem Programmteil ist fast identisch mit der der Widerstandszone. Der Hauptunterschied liegt in der Richtung des Preisverhaltens und der Marktstruktur. Wir hoffen auf eine positive Veränderung des Charakters der Unterstützungszone, was bedeutet, dass der Markt beginnen sollte, nach oben zu steigen und den unteren Bereich zu verwerfen.
Für die Aufbewahrung der Kerzendaten, insbesondere für die Unterstützungszone, wurde ein eigener Satz von Arrays deklariert, was ein wichtiger Aspekt des Codes ist. Dies umfasst Arrays für Kerzendaten. Da die Startzeit des Objekts des Unterstüzungsrechteck häufig von der Startzeit des Widerstandsrechtecks abweicht, werden neue Arrays verwendet. Getrennte Arrays garantieren, dass die zu analysierenden Daten genau mit der gewählten Unterstützungszone im Chart übereinstimmen. Die EA kann jede Ebene unabhängig voneinander analysieren und dadurch Datenüberschneidungen oder Verwechslungen zwischen den beiden Zonen vermeiden.
Anmerkung:
Die Strategie dieses Artikels ist vollständig projektbasiert und soll den Lesern MQL5 durch reale, praktische Anwendung vermitteln. Es handelt sich nicht um eine garantierte Methode, um im Live-Handel Gewinne zu erzielen.
Schlussfolgerung
In diesem Artikel haben wir die automatische Handelsausführung mit der manuellen Chartanalyse kombiniert, um einen Expert Advisor zu erstellen, der auf Chartobjekte wie Unterstützungs- und Widerstandszonen reagiert. Um die Lücke zwischen automatisiertem und diskretionärem Handel zu schließen, haben Sie gelernt, wie man Chartdaten extrahiert und interpretiert, um Handelsgeschäfte automatisch auszuführen.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19912
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.
Vom Neuling zum Experten: Hilfsprogramm zur Parametersteuerung
Die Grenzen des maschinellen Lernens überwinden (Teil 6): Effektive Speichervalidierung
Selbstoptimierende Expert Advisors in MQL5 (Teil 16): Überwachte lineare Systemidentifikation
Einführung in MQL5 (Teil 23): Automatisieren der Opening Range Breakout Strategie
- 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.