
Automatisieren von Handelsstrategien in MQL5 (Teil 8): Aufbau eines Expert Advisors mit harmonischen Schmetterlingsmustern
Einführung
Im vorherigen Artikel (Teil 7) haben wir einen Expert Advisor für einen Rasterhandel in MetaQuotes Language 5 (MQL5) mit dynamischer Losgrößen-Skalierung zur Optimierung von Risiko und Ertrag entwickelt. In Teil 8 konzentrieren wir uns nun auf das harmonische Schmetterlingsmuster - ein Umkehrmuster, das sich präzise Fibonacci Verhältnisse zunutze macht, um potenzielle Wendepunkte im Markt zu erkennen. Dieser Ansatz hilft nicht nur, klare Ein- und Ausstiegssignale zu erkennen, sondern verbessert auch Ihre Handelsstrategie durch automatisierte Visualisierung und Ausführung. In diesem Artikel werden wir uns mit folgenden Themen beschäftigen:
Am Ende werden Sie einen voll funktionsfähigen Expert Advisor haben, der in der Lage ist, harmonische Schmetterlingsmuster zu erkennen und zu handeln. Fangen wir an!
Blaupause der Strategie
Das Schmetterlingsmuster ist eine präzise geometrische Formation, die durch fünf wichtige Swing- oder Umkehrpunkt - X, A, B, C und D - definiert wird und in zwei Haupttypen auftritt: ein Auf- und ein Abwärtsmuster. Bei einem Abwärts-Schmetterling bildet die Struktur eine Hoch-Tief-Hoch-Tief-Hoch-Sequenz, bei der Pivot X ein Swing-Hoch, Pivot A ein Swing-Tief, Pivot B ein Swing-Hoch, Pivot C ein Swing-Tief und Pivot D ein Swing-Hoch ist (wobei D über X liegt). Umgekehrt bildet sich ein Aufwärts-Schmetterling in einer Tief-Hoch-Tief-Hoch-Tief-Sequenz, wobei der Pivot X ein Swing-Tief darstellt und der Pivot D unter X fällt.
Das harmonischen Muster des Abwärts-Schmetterlings:
Das harmonischen Muster des Aufwärts-Schmetterlings:
Um die Muster zu identifizieren, werden wir im Folgenden strukturiert vorgehen:
- Definition des „XA“-Schenkels: Die anfängliche Bewegung vom Umkehrpunkt X nach A legt unsere Referenzdistanz für das Muster fest.
- Einrichtung des „AB“-Schenkels: Bei beiden Mustern sollte der Umkehrpunkt B idealerweise bei einem 78,6 %igen Retracement der XA-Bewegung liegen, um zu bestätigen, dass der Kurs einen erheblichen Teil der ursprünglichen Bewegung rückgängig gemacht hat.
- Analyse des „BC“-Schenkels: Dieser Schenkel dürfte zwischen 38,2 % und 88,6 % der XA-Distanz zurückgehen, um eine stabile Konsolidierung vor der endgültigen Bewegung zu gewährleisten.
- Einstellung des „CD“-Schenkels: Der letzte Schenkel sollte sich zwischen 127 % und 161,8 % der XA-Bewegung erstrecken, was das Muster vervollständigen und einen Umkehrpunkt anzeigen würde.
Durch die Anwendung dieser geometrischen und Fibonacci-basierten Kriterien wird unser Expert Advisor systematisch gültige Schmetterlingsmuster in historischen Kursdaten erkennen. Sobald ein Muster bestätigt wird, visualisiert das Programm die Formation auf dem Chart mit kommentierten Dreiecken und Trendlinien und führt dann Handelsgeschäfte basierend auf den berechneten Einstiegs-, Stop-Loss- und Take-Profit-Levels aus.
Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, gehen Sie zum Navigator, suchen Sie den Ordner Indikatoren, klicken Sie auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| Copyright 2025, Forex Algo-Trader, Allan. | //| "https://t.me/Forex_Algo_Trader" | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property description "This EA trades based on Butterfly Strategy" #property strict //--- Include the trading library for order functions #include <Trade\Trade.mqh> //--- Include Trade library CTrade obj_Trade; //--- Instantiate a obj_Trade object //--- Input parameters for user configuration input int PivotLeft = 5; //--- Number of bars to the left for pivot check input int PivotRight = 5; //--- Number of bars to the right for pivot check input double Tolerance = 0.10; //--- Allowed deviation (10% of XA move) input double LotSize = 0.01; //--- Lot size for new orders input bool AllowTrading = true; //--- Enable or disable trading //--------------------------------------------------------------------------- //--- Butterfly pattern definition: // //--- Bullish Butterfly: //--- Pivots (X-A-B-C-D): X swing high, A swing low, B swing high, C swing low, D swing high. //--- Normally XA > 0; Ideal B = A + 0.786*(X-A); Legs within specified ranges. // //--- Bearish Butterfly: //--- Pivots (X-A-B-C-D): X swing low, A swing high, B swing low, C swing high, D swing low. //--- Normally XA > 0; Ideal B = A - 0.786*(A-X); Legs within specified ranges. //--------------------------------------------------------------------------- //--- Structure for a pivot point struct Pivot { datetime time; //--- Bar time of the pivot double price; //--- Pivot price (High for swing high, low for swing low) bool isHigh; //--- True if swing high; false if swing low }; //--- Global dynamic array for storing pivots in chronological order Pivot pivots[]; //--- Declare a dynamic array to hold identified pivot points //--- Global variables to lock in a pattern (avoid trading on repaint) int g_patternFormationBar = -1; //--- Bar index where the pattern was formed (-1 means none) datetime g_lockedPatternX = 0; //--- The key X pivot time for the locked pattern
Hier binden wir die Bibliothek „Trade\Trade.mqh“ ein, um auf Handelsfunktionen zuzugreifen und das Objekt „obj_Trade“ für die Auftragsausführung zu instanziieren. Wir definieren die Eingabeparameter wie „PivotLeft“ und „PivotRight“ für die Identifizierung der Umkehrpunkte, „Tolerance“ für die Validierung des harmonischen Verhältnisses, „LotSize“ für das Handelsvolumen und „AllowTrading“ zum Aktivieren oder Deaktivieren von Handelsgeschäften.
Um die Marktstruktur zu verfolgen, verwenden wir eine Struktur „Pivot“, die durch eine Struktur definiert ist, die „time“, „price“ und „isHigh“ speichert (true für hohe und false für tiefe Umkehrpunkte). Diese Umkehrpunkte werden in einem globalen, dynamischen Array, „pivots[]“, als historische Referenz gespeichert. Schließlich definieren wir die globalen Variablen „g_patternFormationBar“ und „g_lockedPatternX“, um doppelte Abschlüsse zu verhindern, indem wir ein erkanntes Muster einschließen. Als Nächstes können wir Funktionen definieren, die uns helfen, die Muster im Chart zu visualisieren.
//+------------------------------------------------------------------+ //| Helper: Draw a filled triangle | //+------------------------------------------------------------------+ void DrawTriangle(string name, datetime t1, double p1, datetime t2, double p2, datetime t3, double p3, color cl, int width, bool fill, bool back) { //--- Attempt to create a triangle object with three coordinate points if(ObjectCreate(0, name, OBJ_TRIANGLE, 0, t1, p1, t2, p2, t3, p3)) { //--- Set the triangle's color ObjectSetInteger(0, name, OBJPROP_COLOR, cl); //--- Set the triangle's line style to solid ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set the line width of the triangle ObjectSetInteger(0, name, OBJPROP_WIDTH, width); //--- Determine if the triangle should be filled ObjectSetInteger(0, name, OBJPROP_FILL, fill); //--- Set whether the object is drawn in the background ObjectSetInteger(0, name, OBJPROP_BACK, back); } } //+------------------------------------------------------------------+ //| Helper: Draw a trend line | //+------------------------------------------------------------------+ void DrawTrendLine(string name, datetime t1, double p1, datetime t2, double p2, color cl, int width, int style) { //--- Create a trend line object connecting two points if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) { //--- Set the trend line's color ObjectSetInteger(0, name, OBJPROP_COLOR, cl); //--- Set the trend line's style (solid, dotted, etc.) ObjectSetInteger(0, name, OBJPROP_STYLE, style); //--- Set the width of the trend line ObjectSetInteger(0, name, OBJPROP_WIDTH, width); } } //+------------------------------------------------------------------+ //| Helper: Draw a dotted trend line | //+------------------------------------------------------------------+ void DrawDottedLine(string name, datetime t1, double p, datetime t2, color lineColor) { //--- Create a horizontal trend line at a fixed price level with dotted style if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p, t2, p)) { //--- Set the dotted line's color ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); //--- Set the line style to dotted ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT); //--- Set the line width to 1 ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); } } //+------------------------------------------------------------------+ //| Helper: Draw anchored text label (for pivots) | //| If isHigh is true, anchor at the bottom (label appears above); | //| if false, anchor at the top (label appears below). | //+------------------------------------------------------------------+ void DrawTextEx(string name, string text, datetime t, double p, color cl, int fontsize, bool isHigh) { //--- Create a text label object at the specified time and price if(ObjectCreate(0, name, OBJ_TEXT, 0, t, p)) { //--- Set the text of the label ObjectSetString(0, name, OBJPROP_TEXT, text); //--- Set the color of the text ObjectSetInteger(0, name, OBJPROP_COLOR, cl); //--- Set the font size for the text ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontsize); //--- Set the font type and style ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold"); //--- Anchor the text depending on whether it's a swing high or low if(isHigh) ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_BOTTOM); else ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP); //--- Center-align the text ObjectSetInteger(0, name, OBJPROP_ALIGN, ALIGN_CENTER); } }
Wir definieren eine Reihe von Hilfsfunktionen zur Visualisierung von Preisaktionsstrukturen durch das Zeichnen von Dreiecken, Trendlinien, gepunkteten Linien und Textbeschriftungen im Chart. Diese Funktionen helfen bei der Markierung von Schlüsselpunkten, Trendrichtungen und potenziellen Umkehr-Levels. Die Funktion „DrawTriangle“ erstellt ein Dreiecksobjekt, das drei Preispunkte verbindet. Zunächst wird das Objekt vom Typ OBJ_TRIANGLE mit der Funktion ObjectCreate definiert, dann werden die Eigenschaften Farbe, Breite und Füllung mit der Funktion ObjectSetInteger zugewiesen. Diese Funktion ist nützlich, um harmonische Formationen und Preisaktionsmuster zu markieren.
Die Funktion „DrawTrendLine“ zeichnet Trendlinien zwischen zwei Kurspunkten und hilft, die Struktur des Musters zu definieren. Sie erstellt eine Trendlinie mit der Funktion ObjectCreate vom Typ OBJ_TREND und passt dann deren Farbe, Breite und Stil an. Die Funktion „DrawDottedLine“ hilft, eine horizontale gepunktete Linie auf einem bestimmten Kursniveau zwischen zwei Zeitpunkten zu zeichnen. Dies ist nützlich, um Einstiegs- und Ausstiegslevel zu markieren und sicherzustellen, dass wichtige Preiszonen visuell hervorgehoben werden. Die Funktion setzt den Linienstil zur Differenzierung auf STYLE_DOT. Die Funktion „DrawTextEx“ platziert Textbeschriftungen an bestimmten Umkehrpunkten. Es weist der Kennzeichnung einen Namen zu, legt seine Farbe, Schriftgröße und Ausrichtung fest und positioniert es entweder über oder unter dem Preisniveau, je nachdem, ob es sich um ein Swing-High oder Swing-Low handelt. Dies trägt dazu bei, wichtige Umkehr-Level für eine bessere Mustererkennung zu vermerken.
Mit diesen Variablen und Funktionen können wir zu OnTick übergehen und mit der Mustererkennung beginnen. Da wir jedoch nicht bei jedem Tick etwas verarbeiten müssen, müssen wir eine Logik definieren, mit der wir die Identifizierung einmal pro Balken verarbeiten können.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Declare a static variable to store the time of the last processed bar static datetime lastBarTime = 0; //--- Get the time of the current confirmed bar datetime currentBarTime = iTime(_Symbol, _Period, 1); //--- If the current bar time is the same as the last processed, exit if(currentBarTime == lastBarTime) return; //--- Update the last processed bar time lastBarTime = currentBarTime; }
Um sicherzustellen, dass das Programm die Logik nur bei neuen Balken ausführt, um redundante Berechnungen zu vermeiden, verwenden wir die statische Variable „lastBarTime“, um den Zeitstempel des zuletzt verarbeiteten Balkens zu speichern. Für jeden Tick wird die Zeit des letzten bestätigten Balkens mit der Funktion iTime abgerufen. Wenn die abgefragte Zeit mit „lastBarTime“ übereinstimmt, beenden wir das Programm vorzeitig mit return, um eine erneute Verarbeitung zu vermeiden. Andernfalls aktualisieren wir „lastBarTime“, um den neuen Balken als verarbeitet zu kennzeichnen, und wir können fortfahren, das Speicherarray für die Aufnahme der Daten zur Verarbeitung vorzubereiten.
//--- Clear the pivot array for fresh analysis ArrayResize(pivots, 0); //--- Get the total number of bars available on the chart int barsCount = Bars(_Symbol, _Period); //--- Define the starting index for pivot detection (ensuring enough left bars) int start = PivotLeft; //--- Define the ending index for pivot detection (ensuring enough right bars) int end = barsCount - PivotRight; //--- Loop through bars from 'end-1' down to 'start' to find pivot points for(int i = end - 1; i >= start; i--) { //--- Assume current bar is both a potential swing high and swing low bool isPivotHigh = true; bool isPivotLow = true; //--- Get the high and low of the current bar double currentHigh = iHigh(_Symbol, _Period, i); double currentLow = iLow(_Symbol, _Period, i); //--- Loop through the window of bars around the current bar for(int j = i - PivotLeft; j <= i + PivotRight; j++) { //--- Skip if the index is out of bounds if(j < 0 || j >= barsCount) continue; //--- Skip comparing the bar with itself if(j == i) continue; //--- If any bar in the window has a higher high, it's not a swing high if(iHigh(_Symbol, _Period, j) > currentHigh) isPivotHigh = false; //--- If any bar in the window has a lower low, it's not a swing low if(iLow(_Symbol, _Period, j) < currentLow) isPivotLow = false; } //--- If the current bar qualifies as either a swing high or swing low if(isPivotHigh || isPivotLow) { //--- Create a new pivot structure Pivot p; //--- Set the pivot's time p.time = iTime(_Symbol, _Period, i); //--- Set the pivot's price depending on whether it is a high or low p.price = isPivotHigh ? currentHigh : currentLow; //--- Set the pivot type (true for swing high, false for swing low) p.isHigh = isPivotHigh; //--- Get the current size of the pivots array int size = ArraySize(pivots); //--- Increase the size of the pivots array by one ArrayResize(pivots, size + 1); //--- Add the new pivot to the array pivots[size] = p; } }
Hier identifizieren wir die Umkehrpunkte für das Hoch und das Tief auf dem Chart, indem wir die historischen Kursdaten analysieren. Zunächst setzen wir das Array „pivots“ der Umkehrpunkte mit der Funktion ArrayResize zurück, um eine neue Analyse zu gewährleisten. Anschließend wird mit der Funktion Bars die Gesamtzahl der Balken ermittelt und der Bereich für die Erkennung der Umkehrpunkte festgelegt, damit genügend linke und rechte Balken zum Vergleich zur Verfügung stehen.
Als Nächstes verwenden wir eine for-Schleife, um die Balken von „end-1“ bis „start“ zu durchlaufen, wobei wir davon ausgehen, dass jeder Balken ein potenzieller Umkehrpunkt sein könnte. Mit den Funktionen iHigh und iLow holen wir uns den Höchst- und Tiefstwert des Balkens. Wir vergleichen dann den aktuellen Balken mit den umliegenden Balken innerhalb des Bereichs „PivotLeft“ und „PivotRight“. Wenn ein Balken in diesem Bereich ein höheres Hoch hat, ist der aktuelle Balken kein Swing-Hoch; wenn ein Balken ein niedrigeres Tief hat, ist er kein Swing-Tief. Wenn ein Balken als Umkehrpunkt (pivot) qualifiziert ist, erstellen wir die Struktur „Pivot“, speichern seine Zeit mit der Funktion iTime, setzen seinen Preis je nachdem, ob es sich um ein Hoch oder ein Tief handelt, und bestimmen seinen Typ (true für swing high, false für swing low). Schließlich wird die Größe des Arrays „pivots“ mit ArrayResize angepasst und der identifizierte Pivot hinzugefügt. Wenn wir diese Daten mit der Funktion ArrayPrint drucken, erhalten wir das folgende Ergebnis.
Aus den Daten können wir die Umkehrpunkte extrahieren, und wenn wir genügend Umkehrpunkte haben, können wir die Muster analysieren und erkennen. Hier ist die Logik, mit der wir das erreichen.
//--- Determine the total number of pivots found int pivotCount = ArraySize(pivots); //--- If fewer than five pivots are found, the pattern cannot be formed if(pivotCount < 5) { //--- Reset pattern lock variables g_patternFormationBar = -1; g_lockedPatternX = 0; //--- Exit the OnTick function return; } //--- Extract the last five pivots as X, A, B, C, and D Pivot X = pivots[pivotCount - 5]; Pivot A = pivots[pivotCount - 4]; Pivot B = pivots[pivotCount - 3]; Pivot C = pivots[pivotCount - 2]; Pivot D = pivots[pivotCount - 1]; //--- Initialize a flag to indicate if a valid Butterfly pattern is found bool patternFound = false; //--- Check for the high-low-high-low-high (Bearish reversal) structure if(X.isHigh && (!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) { //--- Calculate the difference between pivot X and A double diff = X.price - A.price; //--- Ensure the difference is positive if(diff > 0) { //--- Calculate the ideal position for pivot B based on Fibonacci ratio double idealB = A.price + 0.786 * diff; //--- Check if actual B is within tolerance of the ideal position if(MathAbs(B.price - idealB) <= Tolerance * diff) { //--- Calculate the BC leg length double BC = B.price - C.price; //--- Verify that BC is within the acceptable Fibonacci range if((BC >= 0.382 * diff) && (BC <= 0.886 * diff)) { //--- Calculate the CD leg length double CD = D.price - C.price; //--- Verify that CD is within the acceptable Fibonacci range and that D is above X if((CD >= 1.27 * diff) && (CD <= 1.618 * diff) && (D.price > X.price)) patternFound = true; } } } }
Hier überprüfen wir, ob ein harmonisches Schmetterlingsmuster vorliegt, indem wir die letzten fünf identifizierten Umkehrpunkte analysieren. Zunächst wird mit der Funktion ArraySize die Gesamtzahl der Umkehrpunkte bestimmt. Wenn weniger als fünf Umkehrpunkte vorhanden sind, setzen wir die Mustersperrvariablen (“g_patternFormationBar“ und „g_lockedPatternX“) zurück und verlassen die Funktion OnTick, um falsche Signale zu vermeiden. Als Nächstes extrahieren wir die letzten fünf Umkehrpunkte und ordnen sie als „X“, „A“, „B“, „C“ und „D“ zu, wobei wir der geometrischen Struktur des Musters folgen. Dann initialisieren wir das Flag „patternFound“ mit false, um festzustellen, ob die Bedingungen für ein gültiges Schmetterlingsmuster erfüllt sind.
Für ein Abwärts-Umkehrmuster überprüfen wir die Abfolge der Umkehr-Hochs und -Tiefs: „X“ (hoch), „A“ (niedrig), „B“ (hoch), „C“ (niedrig) und „D“ (hoch). Wenn diese Struktur stimmt, berechnen wir die „XA“-Schenkeldifferenz und verwenden Fibonacci-Verhältnisse, um die erwarteten Positionen von „B“, „C“ und „D“ zu überprüfen. Der „B“-Pivot muss in der Nähe des „0,786“-Retracements von „XA“ liegen, „BC“ sollte zwischen „0,382“ und „0.886“ von „XA“ liegen, und „CD“ sollte sich zwischen „1,27“ und „1,618“ von „XA“ erstrecken, um sicherzustellen, dass „D“ über „X“ liegt. Wenn alle diese Bedingungen erfüllt sind, bestätigen wir das Muster, indem wir „patternFound“ auf true setzen. Ähnlich verfahren wir bei einem Aufwärts-Muster.
//--- Check for the low-high-low-high-low (Bullish reversal) structure if((!X.isHigh) && A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) { //--- Calculate the difference between pivot A and X double diff = A.price - X.price; //--- Ensure the difference is positive if(diff > 0) { //--- Calculate the ideal position for pivot B based on Fibonacci ratio double idealB = A.price - 0.786 * diff; //--- Check if actual B is within tolerance of the ideal position if(MathAbs(B.price - idealB) <= Tolerance * diff) { //--- Calculate the BC leg length double BC = C.price - B.price; //--- Verify that BC is within the acceptable Fibonacci range if((BC >= 0.382 * diff) && (BC <= 0.886 * diff)) { //--- Calculate the CD leg length double CD = C.price - D.price; //--- Verify that CD is within the acceptable Fibonacci range and that D is below X if((CD >= 1.27 * diff) && (CD <= 1.618 * diff) && (D.price < X.price)) patternFound = true; } } } }
Wenn das Muster gefunden wurde, können wir es im Chart visualisieren.
//--- Initialize a string to store the type of pattern detected string patternType = ""; //--- If a valid pattern is found, determine its type based on the relationship between D and X if(patternFound) { if(D.price > X.price) patternType = "Bearish"; //--- Bearish Butterfly indicates a SELL signal else if(D.price < X.price) patternType = "Bullish"; //--- Bullish Butterfly indicates a BUY signal } //--- If a valid Butterfly pattern is detected if(patternFound) { //--- Print a message indicating the pattern type and detection time Print(patternType, " Butterfly pattern detected at ", TimeToString(D.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- Create a unique prefix for all graphical objects related to this pattern string signalPrefix = "BF_" + IntegerToString(X.time); //--- Choose triangle color based on the pattern type color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed; //--- Draw the first triangle connecting pivots X, A, and B DrawTriangle(signalPrefix+"_Triangle1", X.time, X.price, A.time, A.price, B.time, B.price, triangleColor, 2, true, true); //--- Draw the second triangle connecting pivots B, C, and D DrawTriangle(signalPrefix+"_Triangle2", B.time, B.price, C.time, C.price, D.time, D.price, triangleColor, 2, true, true); //--- Draw boundary trend lines connecting the pivots for clarity DrawTrendLine(signalPrefix+"_TL_XA", X.time, X.price, A.time, A.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_AB", A.time, A.price, B.time, B.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_BC", B.time, B.price, C.time, C.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_CD", C.time, C.price, D.time, D.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_XB", X.time, X.price, B.time, B.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_BD", B.time, B.price, D.time, D.price, clrBlack, 2, STYLE_SOLID); }
Hier schließen wir die Erkennung des Schmetterlingsmusters ab, indem wir es entweder als Auf- oder Abwärts-Muster klassifizieren und visuell auf dem Chart markieren. Zunächst wird die Zeichenkette „patternType“ initialisiert, um zu speichern, ob das erkannte Muster Auf- oder Abwärts ist. Wenn „patternFound“ wahr ist, vergleichen wir Umkehrpunkt „D“ mit dem „X“ unter Verwendung der Eigenschaft „price“. Wenn „D“ höher als „X“ ist, stufen wir es als Abwärts-Muster ein, was eine potenzielle Verkaufsmöglichkeit signalisiert. Umgekehrt, wenn „D“ niedriger als „X“ ist, stufen wir es als Aufwärts-Muster ein, was eine potenzielle Kaufgelegenheit signalisiert.
Sobald ein Muster erkannt wird, wird mit der Funktion Print eine Meldung ausgedruckt, um den Mustertyp und die Erkennungszeit zu protokollieren. Mit der Funktion IntegerToString und „X.time“ wird ein eindeutiger „signalPrefix“ erzeugt, um sicherzustellen, dass jedes Muster eindeutige grafische Objekte hat. Anschließend verwenden wir die Funktion „DrawTriangle“, um die beiden dreieckigen Abschnitte zu markieren, die das Schmetterlingsmuster bilden. Die Dreiecke sind bei Aufwärts-Mustern clrBlue und bei Abwärts-Mustern „clrRed“ eingefärbt. Das erste Dreieck verbindet die Umkehrpunkte „X“, „A“ und „B“, während das zweite Dreieck die Umkehrpunkte „B“, „C“ und „D“ verbindet.
Um die Visualisierung weiter zu verbessern, verwenden wir die Funktion „DrawTrendLine“, um solide schwarze Trendlinien zu erstellen, die die wichtigsten Umkehrpunkte verbinden: „XA“, „AB“, „BC“, „CD“, „XB“ und „BD“. Diese Linien bieten eine klare Struktur zur Identifizierung des harmonischen Musters und seiner Symmetrie. Nach dem Kompilieren und Ausführen erhalten wir folgende Ergebnisse.
Anhand des Bildes können wir sehen, dass wir das Muster sowohl identifizieren als auch visualisieren können. Dann können wir mit der Beschriftung fortfahren, um ihre visuelle Klarheit zu verbessern.
//--- Retrieve the symbol's point size to calculate offsets for text positioning double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); //--- Calculate an offset (15 points) for positioning text above or below pivots double offset = 15 * point; //--- Determine the Y coordinate for each pivot label based on its type double textY_X = (X.isHigh ? X.price + offset : X.price - offset); double textY_A = (A.isHigh ? A.price + offset : A.price - offset); double textY_B = (B.isHigh ? B.price + offset : B.price - offset); double textY_C = (C.isHigh ? C.price + offset : C.price - offset); double textY_D = (D.isHigh ? D.price + offset : D.price - offset); //--- Draw text labels for each pivot with appropriate anchoring DrawTextEx(signalPrefix+"_Text_X", "X", X.time, textY_X, clrBlack, 11, X.isHigh); DrawTextEx(signalPrefix+"_Text_A", "A", A.time, textY_A, clrBlack, 11, A.isHigh); DrawTextEx(signalPrefix+"_Text_B", "B", B.time, textY_B, clrBlack, 11, B.isHigh); DrawTextEx(signalPrefix+"_Text_C", "C", C.time, textY_C, clrBlack, 11, C.isHigh); DrawTextEx(signalPrefix+"_Text_D", "D", D.time, textY_D, clrBlack, 11, D.isHigh); //--- Calculate the central label's time as the midpoint between pivots X and B datetime centralTime = (X.time + B.time) / 2; //--- Set the central label's price at pivot D's price double centralPrice = D.price; //--- Create the central text label indicating the pattern type if(ObjectCreate(0, signalPrefix+"_Text_Center", OBJ_TEXT, 0, centralTime, centralPrice)) { ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_TEXT, (patternType=="Bullish") ? "Bullish Butterfly" : "Bearish Butterfly"); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_COLOR, clrBlack); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_FONTSIZE, 11); ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_FONT, "Arial Bold"); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_ALIGN, ALIGN_CENTER); }
Hier fügen wir Textbeschriftungen hinzu, um das Schmetterlingsmuster auf dem Chart zu markieren. Zunächst verwenden wir die Funktion SymbolInfoDouble, um den Wert von SYMBOL_POINT des Symbols zu ermitteln und einen Versatz für die Textpositionierung zu berechnen. Die Beschriftungen für die Umkehrpunkte („X“, „A“, „B“, „C“, „D“) werden oben oder unten positioniert, je nachdem, ob es sich um Höchst- oder Tiefststände handelt. Wir verwenden die Funktion „DrawTextEx“, um diese Beschriftungen mit schwarzer Schriftfarbe und Größe 11 zu platzieren. Eine zentrale Kennzeichnung durch die Aufschrift „Bullish Butterfly“ oder „Bearish Butterfly“ wird in der Mitte zwischen „X“ und „B“ erstellt, wobei ObjectCreate, ObjectSetString und ObjectSetInteger verwendet werden, um Text, Farbe, Schriftgröße und Ausrichtung für eine klare Sichtbarkeit festzulegen. Dies ist das Ergebnis nach der Ausführung des Programms.
Da wir nun die Beschriftungen haben, können wir nun die Einstiegs- und Ausstiegsebenen hinzufügen.
//--- Define start and end times for drawing horizontal dotted lines for obj_Trade levels datetime lineStart = D.time; datetime lineEnd = D.time + PeriodSeconds(_Period)*2; //--- Declare variables for entry price and take profit levels double entryPriceLevel, TP1Level, TP2Level, TP3Level, tradeDiff; //--- Calculate obj_Trade levels based on whether the pattern is Bullish or Bearish if(patternType=="Bullish") { //--- Bullish → BUY signal //--- Use the current ASK price as the entry entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Set TP3 at pivot C's price TP3Level = C.price; //--- Calculate the total distance to be covered by the obj_Trade tradeDiff = TP3Level - entryPriceLevel; //--- Set TP1 at one-third of the total move TP1Level = entryPriceLevel + tradeDiff/3; //--- Set TP2 at two-thirds of the total move TP2Level = entryPriceLevel + 2*tradeDiff/3; } else { //--- Bearish → SELL signal //--- Use the current BID price as the entry entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Set TP3 at pivot C's price TP3Level = C.price; //--- Calculate the total distance to be covered by the obj_Trade tradeDiff = entryPriceLevel - TP3Level; //--- Set TP1 at one-third of the total move TP1Level = entryPriceLevel - tradeDiff/3; //--- Set TP2 at two-thirds of the total move TP2Level = entryPriceLevel - 2*tradeDiff/3; } //--- Draw dotted horizontal lines to represent the entry and TP levels DrawDottedLine(signalPrefix+"_EntryLine", lineStart, entryPriceLevel, lineEnd, clrMagenta); DrawDottedLine(signalPrefix+"_TP1Line", lineStart, TP1Level, lineEnd, clrForestGreen); DrawDottedLine(signalPrefix+"_TP2Line", lineStart, TP2Level, lineEnd, clrGreen); DrawDottedLine(signalPrefix+"_TP3Line", lineStart, TP3Level, lineEnd, clrDarkGreen); //--- Define a label time coordinate positioned just to the right of the dotted lines datetime labelTime = lineEnd + PeriodSeconds(_Period)/2; //--- Construct the entry label text with the price string entryLabel = (patternType=="Bullish") ? "BUY (" : "SELL ("; entryLabel += DoubleToString(entryPriceLevel, _Digits) + ")"; //--- Draw the entry label on the chart DrawTextEx(signalPrefix+"_EntryLabel", entryLabel, labelTime, entryPriceLevel, clrMagenta, 11, true); //--- Construct and draw the TP1 label string tp1Label = "TP1 (" + DoubleToString(TP1Level, _Digits) + ")"; DrawTextEx(signalPrefix+"_TP1Label", tp1Label, labelTime, TP1Level, clrForestGreen, 11, true); //--- Construct and draw the TP2 label string tp2Label = "TP2 (" + DoubleToString(TP2Level, _Digits) + ")"; DrawTextEx(signalPrefix+"_TP2Label", tp2Label, labelTime, TP2Level, clrGreen, 11, true); //--- Construct and draw the TP3 label string tp3Label = "TP3 (" + DoubleToString(TP3Level, _Digits) + ")"; DrawTextEx(signalPrefix+"_TP3Label", tp3Label, labelTime, TP3Level, clrDarkGreen, 11, true);
Hier berechnen wir den Handelseinstieg und den Take Profit (TP) auf der Grundlage des erkannten Musters. Wir beginnen mit der Funktion PeriodSeconds, um die Dauer für das Zeichnen der horizontalen Handelsstufen zu bestimmen. Anschließend verwenden wir die Funktion SymbolInfoDouble, um den Einstiegskurs zu ermitteln, wobei wir SYMBOL_ASK für einen Kauf und SYMBOL_BID für einen Verkauf anwenden. Wir setzen TP3 über die Variable „C.price“ und berechnen die gesamte Handelsspanne. Wir berechnen TP1 und TP2, indem wir diesen Bereich in Drittel teilen. Wir verwenden die Funktion „DrawDottedLine“, um die Einstiegs- und Endkurse mit unterschiedlichen Farben zu zeichnen. Als Nächstes bestimmen wir mit der Funktion PeriodSeconds eine geeignete Zeitkoordinate für die Kennzeichnung, um es besser zu positionieren. Wir konstruieren die Eintragsbezeichnung mit der Funktion DoubleToString, um den Preis genau zu formatieren. Schließlich wenden wir die Funktion „DrawTextEx“ an, um die Kennzeichnung der Eröffnung und des TP auf dem Chart anzuzeigen. Nach der Kompilierung erhalten wir folgendes Ergebnis.
Abwärts-Muster:
Aufwärts-Muster:
Anhand der Bilder können wir sehen, dass wir beide Muster erkennen und korrekt darstellen können. Wenn das Muster immer noch existiert, bedeutet dies, dass es sich nicht wiederholt hat, sodass wir fortfahren können, die entsprechenden Positionen von der Einstiegsebene aus zu eröffnen. Hier ist die Logik, mit der wir das erreichen.
//--- Retrieve the index of the current bar int currentBarIndex = Bars(_Symbol, _Period) - 1; //--- If no pattern has been previously locked, lock the current pattern formation if(g_patternFormationBar == -1) { g_patternFormationBar = currentBarIndex; g_lockedPatternX = X.time; //--- Print a message that the pattern is detected and waiting for confirmation Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar."); return; } //--- If still on the same formation bar, the pattern is considered to be repainting if(currentBarIndex == g_patternFormationBar) { Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No obj_Trade yet."); return; } //--- If we are on a new bar compared to the locked formation if(currentBarIndex > g_patternFormationBar) { //--- Check if the locked pattern still corresponds to the same X pivot if(g_lockedPatternX == X.time) { Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening obj_Trade on bar ", currentBarIndex, "."); //--- Update the pattern formation bar to the current bar g_patternFormationBar = currentBarIndex; //--- Only proceed with trading if allowed and if there is no existing position if(AllowTrading && !PositionSelect(_Symbol)) { double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0; point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); bool tradeResult = false; //--- For a Bullish pattern, execute a BUY obj_Trade if(patternType=="Bullish") { //--- BUY signal entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double diffTrade = TP2Level - entryPriceTrade; stopLoss = entryPriceTrade - diffTrade * 3; takeProfit = TP2Level; tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Butterfly Signal"); if(tradeResult) Print("Buy order opened successfully."); else Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); } //--- For a Bearish pattern, execute a SELL obj_Trade else if(patternType=="Bearish") { //--- SELL signal entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_BID); double diffTrade = entryPriceTrade - TP2Level; stopLoss = entryPriceTrade + diffTrade * 3; takeProfit = TP2Level; tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Butterfly Signal"); if(tradeResult) Print("Sell order opened successfully."); else Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); } } else { //--- If a position is already open, do not execute a new obj_Trade Print("A position is already open for ", _Symbol, ". No new obj_Trade executed."); } } else { //--- If the pattern has changed, update the lock with the new formation bar and X pivot g_patternFormationBar = currentBarIndex; g_lockedPatternX = X.time; Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation."); return; } } } else { //--- If no valid Butterfly pattern is detected, reset the pattern lock variables g_patternFormationBar = -1; g_lockedPatternX = 0; }
Dieser Abschnitt verwaltet das Sperren des Musters und die Handelsausführung. Zunächst wird mit der Funktion Bars der aktuelle Balkenindex ermittelt und „currentBarIndex“ zugewiesen. Wenn kein Muster gesperrt wurde, was durch „g_patternFormationBar“ == -1 angezeigt wird, weisen wir „currentBarIndex“ „g_patternFormationBar“ zu und speichern die Umkehrzeit von X in „g_lockedPatternX“, wobei wir mit der Funktion „Print“ eine Meldung ausgeben, dass ein Muster erkannt wurde und auf eine Bestätigung wartet. Wenn sich das erkannte Muster immer noch auf demselben Balken bildet, verwenden wir die Funktion Print, um eine Meldung anzuzeigen, die darauf hinweist, dass das Muster neu gezeichnet wird, und es wird kein Handel ausgeführt.
Wenn der aktuelle Balken über den gesperrten Formationsbalken hinausgeht, prüfen wir, ob das Muster weiterhin gültig ist, indem wir „g_lockedPatternX“ mit der aktuellen X-Umkehr-Zeit vergleichen. Wenn es übereinstimmt, bestätigen wir das Muster und bereiten die Ausführung des Handels vor. Bevor wir einen Auftrag erteilen, verwenden wir die Funktion PositionSelect, um sicherzustellen, dass keine Position vorhanden ist, und überprüfen „AllowTrading“. Wird ein Aufwärts-Muster bestätigt, ermitteln wir den Briefkurs mit der Funktion SymbolInfoDouble mit SYMBOL_ASK, berechnen den Stop-Loss und den Take-Profit auf Basis von „TP2Level“ und führen einen Kaufauftrag mit der Funktion „obj_Trade.Buy“ aus. Wenn der Handel erfolgreich war, wird mit der Funktion „Print“ eine Bestätigungsmeldung angezeigt; andernfalls wird mit der Funktion „obj_Trade.ResultRetcodeDescription“ der Grund für den Misserfolg gedruckt.
Bei einem Abwärts-Muster wird der Geldkurs mit der Funktion SymbolInfoDouble mit SYMBOL_BID abgefragt, die Handelsstufen berechnet und eine Verkaufsorder mit der Funktion „obj_Trade.Sell“ ausgeführt, wobei entsprechende Erfolgs- oder Misserfolgsmeldungen mit der Funktion Print ausgedruckt werden. Besteht bereits eine Position, wird kein neuer Abschluss getätigt und eine Meldung über die Funktion „Drucken“ ausgedruckt. Wenn sich der gesperrte X-Punkt ändert, werden „g_patternFormationBar“ und „g_lockedPatternX“ aktualisiert, um anzuzeigen, dass sich das Muster geändert hat und eine Bestätigung erwartet wird. Wenn kein gültiges Muster erkannt wird, werden „g_patternFormationBar“ und „g_lockedPatternX“ zurückgesetzt, um vorherige Sperren zu löschen.
Nach der Kompilierung erhalten wir folgendes Ergebnis.
Aus dem Bild können wir ersehen, dass wir das Schmetterlingsmuster zeichnen und immer noch in der Lage sind, es entsprechend zu handeln, sobald bestätigt wird, dass es stabil ist, und somit unser Ziel zu erreichen, das Muster zu identifizieren, aufzuzeichnen und zu handeln. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.
Backtest und Optimierung
Nach einem gründlichen Backtest haben wir die folgenden Ergebnisse.
Backtest-Grafik:
Backtest-Bericht:
Der Testzeitraum von einem halben Jahr auf einem 5-Minuten-Chart mit 65 Handelsgeschäfte zeigt, dass das Schmetterlingsmuster selten ist, und je höher der Toleranzprozentsatz, desto höher die Anzahl der Signale.
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir erfolgreich einen MQL5 Expert Advisor (EA) entwickelt haben, der das harmonische Schmetterlingsmuster mit Präzision erkennt und handelt. Durch die Nutzung von Mustererkennung, Umkehr-Validierung und automatischer Handelsausführung haben wir ein System geschaffen, das sich dynamisch an die Marktbedingungen anpasst.
Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Marktbedingungen können unvorhersehbar sein. Die skizzierte Strategie bietet zwar einen strukturierten Ansatz für den harmonischen Handel, ist aber keine Garantie für Rentabilität. Umfassende Backtests und ein angemessenes Risikomanagement sind unerlässlich, bevor dieses Programm in einer Live-Umgebung eingesetzt wird.
Durch die Anwendung dieser Techniken können Sie Ihre Fähigkeiten im Handel mit harmonischen Mustern verfeinern, Ihre technische Analyse verbessern und Ihre algorithmischen Handelsstrategien weiterentwickeln. Viel Glück auf Ihrer Handelsreise!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17223





- 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.
Sehen Sie sich den neuen Artikel an: Automatisieren von Handelsstrategien in MQL5 (Teil 8): Aufbau eines Expert Advisors mit Butterfly Harmonic Patterns.
Autor: Allan Munene Mutiiria
Sehen Sie sich den neuen Artikel an: Automatisieren von Handelsstrategien in MQL5 (Teil 8): Aufbau eines Expert Advisors mit Butterfly Harmonic Patterns.
Autor: Allan Munene Mutiiria