English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 15): Price Action Harmonic Cypher Pattern mit Visualisierung

Automatisieren von Handelsstrategien in MQL5 (Teil 15): Price Action Harmonic Cypher Pattern mit Visualisierung

MetaTrader 5Handel |
105 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorangegangenen Artikel (Teil 14) haben wir die Strategie eines Handelsrasters entwickelt, die den Moving Average Convergence Divergence (MACD) und den Relative Strength Indicator (RSI) mit statistischen Methoden nutzt, um Positionen in Trendmärkten dynamisch zu skalieren. In Teil 15 konzentrieren wir uns nun auf die Automatisierung des Cypher harmonischen Musters, ein Fibonacci-basiertes Umkehrmuster, mit einem Expert Advisor (EA), der diese Struktur in MetaQuotes Language 5 (MQL5) erkennt, visualisiert und handelt. Wir werden die folgenden Themen behandeln:

  1. Verstehen der Cypher-Musterarchitektur
  2. Implementation in MQL5
  3. Backtest und Optimierung
  4. Schlussfolgerung

Am Ende dieses Artikels werden Sie ein voll funktionsfähiges Programm haben, das Cypher-Muster identifiziert, Charts mit klaren Grafiken kommentiert und Handelsgeschäfte mit Präzision ausführt - legen wir los!


Verstehen der Cypher-Musterarchitektur

Das Cypher-Muster ist eine harmonische Handelsformation, die durch fünf wichtige Umkehrpunkte - X, A, B, C und D - definiert wird und in zwei Formen existiert: ein Aufwärts-Muster und ein Abwärts-Muster. Bei einem Aufwärts-Cypher bildet die Struktur eine Tief-Hoch-Tief-Hoch-Tief-Sequenz, bei der Punkt X einen tiefen Umkehrpunkt, Punkt A einen hohen Umkehrpunkt, Punkt B einen tiefen Umkehrpunkt, Punkt C einen hohen Umkehrpunkt und Punkt D einen tiefen Umkehrpunkt ist (wobei D unter X liegt). Umgekehrt bildet ein Abwärts-Cypher eine Hoch-Tief-Hoch-Tief-Hoch-Sequenz mit dem Punkt X als hoher Umkehrpunkt und dem Punkt D oberhalb von X. Nachstehend sind die Mustertypen visualisiert.

Bullish Cypher Harmonic Pattern:

AUFWÄRTS-CYPHER

Bearish Cypher Harmonic Pattern:

ABWÄRTS-CYPHER

Um die Muster zu erkennen, gehen wir wie folgt strukturiert vor:

  • Die Definition des XA-Schenkels: Die anfängliche Bewegung von Punkt X zu Punkt A legt die Referenzdistanz für das Muster fest und bestimmt die Richtung (steigend für abwärts, fallend für aufwärts).
  • Die Einrichtung des AB-Schenkels: Bei beiden Mustertypen sollte Punkt B zwischen 38,2 % und 61,8 % der XA-Bewegung zurückgehen, was eine moderate Korrektur der ursprünglichen Bewegung bestätigt.
  • Analyse der BC-Schenkels: Dieser Schenkel sollte sich zwischen 127,2 % und 141,4 % des AB-Schenkels erstrecken, um eine starke Gegenbewegung vor dem letzten Schenkel zu gewährleisten.
  • Einstellung des CD-Schenkels: Der letzte Abschnitt sollte etwa 78,6 % der XC-Bewegung (von X nach C) zurückverfolgen, was die potenzielle Umkehrzone markiert.

Durch die Anwendung dieser Geometrie und den Fibonacci-Kriterien wird unser Handelssystem systematisch gültige Cypher-Muster in historischen Kursdaten erkennen. Sobald ein Muster bestätigt wird, visualisiert das Programm die Formation auf dem Chart mit kommentierten Dreiecken, Trendlinien und Beschriftungen für die Punkte X, A, B, C und D sowie für die Handelsstufen. Dieses Setup ermöglicht eine automatische Handelsausführung auf der Grundlage der berechneten Einstiegs-, Stop-Loss- und Take-Profit-Niveaus und nutzt die Vorhersagekraft des Musters für Marktumkehrungen.


Implementation in MQL5

Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner der Indikatoren, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald das erledigt 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 Cypher Strategy with visualization"
#property strict //--- Forces strict coding rules to catch errors early

//--- Include the Trade library from MQL5 to handle trading operations like buying and selling
#include <Trade\Trade.mqh>
//--- Create an instance (object) of the CTrade class to use for placing trades
CTrade obj_Trade;

//--- Input parameters let the user customize the EA without editing the code
input int    SwingHighCount    = 5;      // How many bars to check on the left to find a swing point (high or low)
input int    SwingLowCount     = 5;      // How many bars to check on the right to confirm a swing point
input double FibonacciTolerance = 0.10;  // Allowed error margin (10%) for Fibonacci ratios in the pattern
input double TradeVolume       = 0.01;   // Size of the trade (e.g., 0.01 lots is small for testing)
input bool   TradingEnabled    = true;   // True = EA can trade; False = only visualize patterns

//--- Define the Cypher pattern rules as a comment for reference
//--- Bullish Cypher: X (low), A (high), B (low), C (high), D (low)
//---   XA > 0; AB = 0.382-0.618 XA; BC = 1.272-1.414 AB; CD = 0.786 XC; D < X
//--- Bearish Cypher: X (high), A (low), B (high), C (low), D (high)
//---   XA > 0; AB = 0.382-0.618 XA; BC = 1.272-1.414 AB; CD = 0.786 XC; D > X

//--- Define a structure (like a custom data type) to store swing point info
struct SwingPoint {  
   datetime TimeOfSwing;    //--- When the swing happened (date and time of the bar)
   double   PriceAtSwing;   //--- Price at the swing (high or low)
   bool     IsSwingHigh;    //--- True = swing high; False = swing low
};  

//--- Create a dynamic array to hold all detected swing points
SwingPoint SwingPoints[];

Hier beginnen wir mit der Implementierung des Cypher-Musterhandelssystems in MetaQuotes Language 5, indem wir die Bibliothek „Trade.mqh“ einbinden, um Handelsoperationen zu ermöglichen, und eine Instanz der Klasse „CTrade“ mit dem Namen „obj_Trade“ für die Handelsausführung erstellen.

Wir definieren die Eingabeparameter - „SwingHighCount“ und „SwingLowCount“ (beide 5) für das Erkennen von Umkehrpunkten, „FibonacciTolerance“ (0,10) für die Flexibilität der Fibonacci-Verhältnisse, „TradeVolume“ (0,01 Lots) für die Handelsgröße und „TradingEnabled“ (true), um den Handel zu aktivieren - und ermöglichen so eine individuelle Anpassung durch den Nutzer.

Die Struktur „SwingPoint“ wird mit „TimeOfSwing“ (datetime), „PriceAtSwing“ (double) und „IsSwingHigh“ (boolean) definiert, um Umkehrpunkt-Details zu speichern, und ein dynamisches „SwingPoints“-Array enthält alle erkannten Umkehrpunkte für die Musteranalyse. 

Als Nächstes können wir Funktionen definieren, die uns helfen, die Muster im Chart zu visualisieren.

//+------------------------------------------------------------------+
//| Helper: Draw a filled triangle                                   |  
//+------------------------------------------------------------------+  
//--- Function to draw a triangle on the chart to highlight pattern segments
void DrawTriangle(string TriangleName, datetime Time1, double Price1, datetime Time2, double Price2, datetime Time3, double Price3, color LineColor, int LineWidth, bool FillTriangle, bool DrawBehind) {  
   //--- Create a triangle object using three points (time, price) on the chart
   if(ObjectCreate(0, TriangleName, OBJ_TRIANGLE, 0, Time1, Price1, Time2, Price2, Time3, Price3)) {  
      ObjectSetInteger(0, TriangleName, OBJPROP_COLOR, LineColor);      //--- Set the triangle’s color (e.g., blue or red)
      ObjectSetInteger(0, TriangleName, OBJPROP_STYLE, STYLE_SOLID);    //--- Use a solid line style
      ObjectSetInteger(0, TriangleName, OBJPROP_WIDTH, LineWidth);      //--- Set the line thickness
      ObjectSetInteger(0, TriangleName, OBJPROP_FILL, FillTriangle);    //--- Fill the triangle with color if true
      ObjectSetInteger(0, TriangleName, OBJPROP_BACK, DrawBehind);      //--- Draw behind candles if true
   }  
}  

Hier implementieren wir die Funktion „DrawTriangle“, um die Visualisierung des Cypher-Musters zu verbessern, indem wir ein ausgefülltes Dreieck auf dem Chart des MetaTrader 5 zeichnen, das bestimmte Segmente des Musters hervorhebt, damit der Händler sie besser versteht. Die Funktion akzeptiert mehrere Parameter, um das Aussehen und die Position des Dreiecks zu definieren: „TriangleName“ (string) liefert einen eindeutigen Bezeichner für das Objekt, „Time1“, „Time2“ und „Time3“ (datetime) geben die Zeitkoordinaten der drei Eckpunkte des Dreiecks an, während „Price1“, „Price2“ und „Price3“ (double) die entsprechenden Preisniveaus festlegen.

Weitere Parameter sind „LineColor“ (color), um die Farbe des Umrisses zu bestimmen (z.B., blau für Aufwärts-, rot für Abwärts-Muster), „LineWidth“ (int), um die Dicke der Dreiecksumrandung festzulegen, „FillTriangle“ (bool), um zu entscheiden, ob das Dreieck mit Farbe gefüllt wird, und „DrawBehind“ (bool), um zu steuern, ob das Dreieck hinter den Kerzen des Charts dargestellt wird, um zu vermeiden, dass Kursdaten verdeckt werden.

Innerhalb der Funktion verwenden wir die Funktion ObjectCreate, um ein Dreiecksobjekt im Chart zu erstellen, wobei wir den Objekttyp als OBJ_TRIANGLE angeben und die angegebenen Zeit- und Preiskoordinaten für die drei Punkte übergeben. Die Funktion prüft, ob die Erstellung des Objekts erfolgreich war, bevor sie mit der Konfiguration seiner Eigenschaften fortfährt.

Wenn die Erstellung erfolgreich war, rufen wir die Funktion ObjectSetInteger mehrmals auf, um die Attribute des Dreiecks zu setzen: OBJPROP_COLOR weist den Wert „LineColor“ zu, „OBJPROP_STYLE“ ist auf „STYLE_SOLID“ für eine solide Kontur eingestellt, „OBJPROP_WIDTH“ wendet den Wert „LineWidth“ an, „OBJPROP_FILL“ verwendet den booleschen Wert „FillTriangle“, um die Füllung zu aktivieren oder zu deaktivieren, und OBJPROP_BACK verwendet den booleschen Wert „DrawBehind“, um sicherzustellen, dass das Dreieck hinter den Kerzen erscheint, wenn dieser Wert wahr ist.

Wir können nun die übrigen Hilfsfunktionen nach derselben Logik definieren.

//+------------------------------------------------------------------+  
//| Helper: Draw a trend line                                        |  
//+------------------------------------------------------------------+  
//--- Function to draw a straight line between two points on the chart
void DrawTrendLine(string LineName, datetime StartTime, double StartPrice, datetime EndTime, double EndPrice, color LineColor, int LineWidth, int LineStyle) {  
   //--- Create a trend line object connecting two points (start time/price to end time/price)
   if(ObjectCreate(0, LineName, OBJ_TREND, 0, StartTime, StartPrice, EndTime, EndPrice)) {  
      ObjectSetInteger(0, LineName, OBJPROP_COLOR, LineColor);      //--- Set the line color
      ObjectSetInteger(0, LineName, OBJPROP_STYLE, LineStyle);      //--- Set line style (e.g., solid or dashed)
      ObjectSetInteger(0, LineName, OBJPROP_WIDTH, LineWidth);      //--- Set line thickness
      ObjectSetInteger(0, LineName, OBJPROP_BACK, true);           
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw a dotted trend line                                 |  
//+------------------------------------------------------------------+  
//--- Function to draw a horizontal dotted line (e.g., for entry or take-profit levels)
void DrawDottedLine(string LineName, datetime StartTime, double LinePrice, datetime EndTime, color LineColor) {  
   //--- Create a horizontal line from start time to end time at a fixed price
   if(ObjectCreate(0, LineName, OBJ_TREND, 0, StartTime, LinePrice, EndTime, LinePrice)) {  
      ObjectSetInteger(0, LineName, OBJPROP_COLOR, LineColor);      //--- Set the line color
      ObjectSetInteger(0, LineName, OBJPROP_STYLE, STYLE_DOT);      //--- Use dotted style
      ObjectSetInteger(0, LineName, OBJPROP_WIDTH, 1);              //--- Thin line
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw anchored text label (for pivots and levels)         |  
//+------------------------------------------------------------------+  
//--- Function to place text labels (e.g., "X" or "TP1") on the chart
void DrawTextLabel(string LabelName, string LabelText, datetime LabelTime, double LabelPrice, color TextColor, int FontSize, bool IsAbove) {  
   //--- Create a text object at a specific time and price
   if(ObjectCreate(0, LabelName, OBJ_TEXT, 0, LabelTime, LabelPrice)) {  
      ObjectSetString(0, LabelName, OBJPROP_TEXT, LabelText);          //--- Set the text to display
      ObjectSetInteger(0, LabelName, OBJPROP_COLOR, TextColor);        //--- Set text color
      ObjectSetInteger(0, LabelName, OBJPROP_FONTSIZE, FontSize);      //--- Set text size
      ObjectSetString(0, LabelName, OBJPROP_FONT, "Arial Bold");       //--- Use bold Arial font
      //--- Position text below if it’s a high point, above if it’s a low point
      ObjectSetInteger(0, LabelName, OBJPROP_ANCHOR, IsAbove ? ANCHOR_BOTTOM : ANCHOR_TOP);  
      ObjectSetInteger(0, LabelName, OBJPROP_ALIGN, ALIGN_CENTER);     //--- Center the text
   }  
}  

Hier implementieren wir die Funktion „DrawTrendLine“, um eine gerade Linie zu zeichnen, die die Umkehrpunkte des Cypher-Musters im Chart zeichnen, wobei die Parameter „LineName“ (string), „StartTime“, „EndTime“ (datetime), „StartPrice“, „EndPrice“ (double), „LineColor“ (color), „LineWidth“ (int), und „LineStyle“ (int). Wir verwenden die Funktion ObjectCreate, um mit „OBJ_TREND“ eine Trendlinie zu erstellen und, falls erfolgreich, „OBJPROP_COLOR“, „OBJPROP_STYLE“, „OBJPROP_WIDTH“ und „OBJPROP_BACK“ (true) mit „ObjectSetInteger“ für die Sichtbarkeit hinter Kerzen zu setzen.

Die Funktion „DrawDottedLine“ zeichnet eine horizontale gepunktete Linie für Handelsstufen unter Verwendung von „LineName“ (String), „StartTime“, „EndTime“ (datetime), „LinePrice“ (double) und „LineColor“ (Farbe). Wir erstellen ein OBJ_TREND-Objekt mit „ObjectCreate“ zu einem festen Preis und setzen „OBJPROP_COLOR“, „OBJPROP_STYLE“ auf „STYLE_DOT“ und „OBJPROP_WIDTH“ auf 1 mit ObjectSetInteger für eine subtile Markierung.

Die Funktion „DrawTextLabel“ platziert Textbeschriftungen für Umkehrpunkte oder Handelsstufen, wobei „LabelName“, „LabelText“ (string), „LabelTime“ (datetime), „LabelPrice“ (double), „TextColor“ (color), „FontSize“ (int), und „IsAbove“ (bool). Wir erstellen ein OBJ_TEXT-Objekt mit „ObjectCreate“ und verwenden „ObjectSetString“ für „OBJPROP_TEXT“ und „OBJPROP_FONT“ (“Arial Bold“), und „ObjectSetInteger“ für „OBJPROP_COLOR“, „OBJPROP_FONTSIZE“, „OBJPROP_ANCHOR“ (“ANCHOR_BOTTOM“ oder „ANCHOR_TOP“) und „OBJPROP_ALIGN“ (ALIGN_CENTER), um eindeutige Beschriftungen sicherzustellen.

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                                             |
//+------------------------------------------------------------------+
//--- Main function that runs every time a new price tick arrives
void OnTick() {  
   //--- Use a static variable to track the last bar’s time so we only process new bars
   static datetime LastProcessedBarTime = 0;  
   //--- Get the time of the second-to-last bar (latest complete bar)
   datetime CurrentBarTime = iTime(_Symbol, _Period, 1);  
   //--- If no new bar has formed, exit to avoid over-processing
   if(CurrentBarTime == LastProcessedBarTime)  
      return;  
   LastProcessedBarTime = CurrentBarTime;  //--- Update to the current bar
}

In OnTick, das als Hauptereignis fungiert und jedes Mal ausgeführt wird, wenn ein neuer Preis-Tick in der MetaTrader 5-Plattform eintrifft, deklarieren wir eine statische Variable „LastProcessedBarTime“, um den Zeitstempel des zuletzt verarbeiteten Balkens zu verfolgen und sicherzustellen, dass die Funktion nur neue Balken verarbeitet, um die Leistung zu optimieren.

Mit der Funktion iTime wird die Zeit des vorletzten Balkens (des letzten vollständigen Balkens) abgerufen und in „CurrentBarTime“ gespeichert. Dann vergleichen wir „CurrentBarTime“ mit „LastProcessedBarTime“, um zu prüfen, ob sich ein neuer Balken gebildet hat; wenn sie gleich sind, verlassen wir die Funktion mit einer Return-Anweisung, um redundante Verarbeitung zu vermeiden.

Wenn ein neuer Balken erkannt wird, aktualisieren wir „LastProcessedBarTime“ auf „CurrentBarTime“, sodass die Funktion mit der nachfolgenden Logik zur Analyse der Preisdaten und zur Erkennung von Cypher-Mustern fortfahren kann. Als Nächstes müssen wir Variablen definieren, die uns helfen, die Schwellenwerte zu bestimmen.

//--- Clear the SwingPoints array to start fresh each time
ArrayResize(SwingPoints, 0);  
//--- Get the total number of bars on the chart
int TotalBars = Bars(_Symbol, _Period);  
int StartBarIndex = SwingHighCount;         //--- Start checking swings after SwingHighCount bars
int EndBarIndex = TotalBars - SwingLowCount; //--- Stop before the last SwingLowCount bars

Wir verwenden die Funktion ArrayResize, um das Array „SwingPoints“ zu leeren, indem wir seine Größe auf 0 setzen und so einen Neustart für die Speicherung neuer Umkehrpunkte bei jedem neuen Takt sicherstellen. Anschließend wird die Gesamtzahl der Balken im Chart mit der Funktion Balken abgerufen und das Ergebnis in „TotalBars“ gespeichert, das den Umfang der zu analysierenden historischen Daten definiert.

Um sich auf relevante Bars für die Swing-Erkennung zu konzentrieren, setzen wir „StartBarIndex“ auf den Wert von „SwingHighCount“ und markieren damit den frühesten Bar, der auf Swings überprüft werden soll, und berechnen „EndBarIndex“ als „TotalBars“ minus „SwingLowCount“, um sicherzustellen, dass wir vor den letzten Bars aufhören, um genügend Daten für die Bestätigung von Umkehrpunkten zu erhalten.

Mit diesen können wir eine Schleife bilden und Daten über Umkehrpunkte sammeln.

//--- Loop through bars to find swing highs and lows (swing points)
for(int BarIndex = EndBarIndex - 1; BarIndex >= StartBarIndex; BarIndex--) {  
   bool IsSwingHigh = true;   //--- Assume it’s a high until proven otherwise
   bool IsSwingLow = true;    //--- Assume it’s a low until proven otherwise
   double CurrentBarHigh = iHigh(_Symbol, _Period, BarIndex); //--- Get the high of this bar
   double CurrentBarLow = iLow(_Symbol, _Period, BarIndex);   //--- Get the low of this bar
   //--- Check bars to the left and right to confirm it’s a swing point
   for(int NeighborIndex = BarIndex - SwingHighCount; NeighborIndex <= BarIndex + SwingLowCount; NeighborIndex++) {  
      if(NeighborIndex < 0 || NeighborIndex >= TotalBars || NeighborIndex == BarIndex) //--- Skip invalid bars or current bar
         continue;  
      if(iHigh(_Symbol, _Period, NeighborIndex) > CurrentBarHigh) //--- If any bar is higher, not a high
         IsSwingHigh = false;  
      if(iLow(_Symbol, _Period, NeighborIndex) < CurrentBarLow)   //--- If any bar is lower, not a low
         IsSwingLow = false;  
   }  
   //--- If it’s a high or low, store it in the SwingPoints array
   if(IsSwingHigh || IsSwingLow) {  
      SwingPoint NewSwing;  
      NewSwing.TimeOfSwing = iTime(_Symbol, _Period, BarIndex); //--- Store the bar’s time
      NewSwing.PriceAtSwing = IsSwingHigh ? CurrentBarHigh : CurrentBarLow; //--- Store high or low price
      NewSwing.IsSwingHigh = IsSwingHigh;              //--- Mark as high or low
      int CurrentArraySize = ArraySize(SwingPoints);   //--- Get current array size
      ArrayResize(SwingPoints, CurrentArraySize + 1);  //--- Add one more slot
      SwingPoints[CurrentArraySize] = NewSwing;        //--- Add the swing to the array
   }  
}  

Hier implementieren wir die Logik zur Erkennung von Umkehrpunkten, um hohe und tiefe Umkehrpunkte für das Cypher-Muster zu identifizieren. Wir verwenden eine for-Schleife, um die Balken von „EndBarIndex - 1“ bis „StartBarIndex“ in absteigender Reihenfolge zu durchlaufen, wobei „BarIndex“ den aktuellen Balken angibt. Für jeden Balken initialisieren wir „IsSwingHigh“ und „IsSwingLow“ als wahr, wobei wir davon ausgehen, dass der Balken ein Umkehrpunkt ist, bis er widerlegt ist, und rufen die Höchst- und Tiefstpreise des Balkens mithilfe der Funktionen iHigh und iLow ab und speichern sie in „CurrentBarHigh“ und „CurrentBarLow“. Eine verschachtelte for-Schleife prüft benachbarte Bars von „BarIndex - SwingHighCount“ bis „BarIndex + SwingLowCount“, wobei „NeighborIndex“ verwendet wird, um ungültige Indizes oder den aktuellen Bar selbst mit einer continue-Anweisung zu überspringen.

Wenn das Hoch eines benachbarten Balkens „CurrentBarHigh“ übersteigt oder das Tief unter „CurrentBarLow“ fällt (über iHigh und iLow), setzen wir „IsSwingHigh“ bzw. „IsSwingLow“ auf false. Bleibt beides wahr, erstellen wir eine „SwingPoint“-Instanz mit dem Namen „NewSwing“, wobei wir „TimeOfSwing“ mit der Zeit des Balkens aus iTime, „PriceAtSwing“ als „CurrentBarHigh“ oder „CurrentBarLow“ basierend auf „IsSwingHigh“ und „IsSwingHigh“ entsprechend zuweisen.

Wir verwenden dann die Funktion „ArraySize“, um die aktuelle Größe von „SwingPoints“ zu ermitteln, erweitern sie mit ArrayResize um eins und speichern „NewSwing“ im Array „SwingPoints“ am neuen Index, wodurch die Sammlung von Umkehrpunkten für die Musteranalyse entsteht. Wenn wir die Daten mit der Funktion ArrayPrint(SwingPoints) ausdrucken, erhalten wir das folgende Ergebnis.

ERGEBNIS DER STRUKTURIERTEN DATEN

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.

//--- Check if we have enough swing points (need 5 for Cypher: X, A, B, C, D)
int TotalSwingPoints = ArraySize(SwingPoints);  
if(TotalSwingPoints < 5)  
   return;  //--- Exit if not enough swing points

//--- Assign the last 5 swing points to X, A, B, C, D (most recent is D)
SwingPoint PointX = SwingPoints[TotalSwingPoints - 5];  
SwingPoint PointA = SwingPoints[TotalSwingPoints - 4];  
SwingPoint PointB = SwingPoints[TotalSwingPoints - 3];  
SwingPoint PointC = SwingPoints[TotalSwingPoints - 2];  
SwingPoint PointD = SwingPoints[TotalSwingPoints - 1];  

//--- Variables to track if we found a pattern and its type
bool PatternFound = false;  
string PatternDirection = "";  

//--- Check for Bearish Cypher pattern
if(PointX.IsSwingHigh && !PointA.IsSwingHigh && PointB.IsSwingHigh && !PointC.IsSwingHigh && PointD.IsSwingHigh) {  
   double LegXA = PointX.PriceAtSwing - PointA.PriceAtSwing;  //--- Calculate XA leg (should be positive)
   if(LegXA > 0) {  
      double LegAB = PointB.PriceAtSwing - PointA.PriceAtSwing; //--- AB leg
      double LegBC = PointB.PriceAtSwing - PointC.PriceAtSwing; //--- BC leg
      double LegXC = PointX.PriceAtSwing - PointC.PriceAtSwing; //--- XC leg
      double LegCD = PointD.PriceAtSwing - PointC.PriceAtSwing; //--- CD leg
      //--- Check Fibonacci rules and D > X for bearish
      if(LegAB >= 0.382 * LegXA && LegAB <= 0.618 * LegXA &&  
         LegBC >= 1.272 * LegAB && LegBC <= 1.414 * LegAB &&  
         MathAbs(LegCD - 0.786 * LegXC) <= FibonacciTolerance * LegXC && PointD.PriceAtSwing > PointX.PriceAtSwing) {  
         PatternFound = true;  
         PatternDirection = "Bearish";  
      }  
   }  
}  
//--- Check for Bullish Cypher pattern
else if(!PointX.IsSwingHigh && PointA.IsSwingHigh && !PointB.IsSwingHigh && PointC.IsSwingHigh && !PointD.IsSwingHigh) {  
   double LegXA = PointA.PriceAtSwing - PointX.PriceAtSwing;  //--- Calculate XA leg (should be positive)
   if(LegXA > 0) {  
      double LegAB = PointA.PriceAtSwing - PointB.PriceAtSwing; //--- AB leg
      double LegBC = PointC.PriceAtSwing - PointB.PriceAtSwing; //--- BC leg
      double LegXC = PointC.PriceAtSwing - PointX.PriceAtSwing; //--- XC leg
      double LegCD = PointC.PriceAtSwing - PointD.PriceAtSwing; //--- CD leg
      //--- Check Fibonacci rules and D < X for bullish
      if(LegAB >= 0.382 * LegXA && LegAB <= 0.618 * LegXA &&  
         LegBC >= 1.272 * LegAB && LegBC <= 1.414 * LegAB &&  
         MathAbs(LegCD - 0.786 * LegXC) <= FibonacciTolerance * LegXC && PointD.PriceAtSwing < PointX.PriceAtSwing) {  
         PatternFound = true;  
         PatternDirection = "Bullish";  
      }  
   }  
}  

Hier fahren wir fort, das Cypher-Muster zu validieren, indem wir auf ausreichende Umkehrpunkte achten und die letzten fünf auf die Musterbildung hin analysieren. Wir verwenden die Funktion ArraySize, um die Anzahl der Elemente im Array „SwingPoints“ zu ermitteln und in „TotalSwingPoints“ zu speichern, und beenden das Programm mit einer Return-Anweisung, wenn „TotalSwingPoints“ kleiner als 5 ist, da das Cypher-Muster fünf Punkte (X, A, B, C, D) erfordert. Wenn genügend Punkte vorhanden sind, werden die letzten fünf Umkehrpunkte „PointX“, „PointA“, „PointB“, „PointC“ und „PointD“ zugewiesen aus dem Array „SwingPoints“ zu, mit Indizes von „TotalSwingPoints - 5“ bis „TotalSwingPoints - 1“, wobei „PointD“ der jüngste ist.

Anschließend wird „PatternFound“ mit false initialisiert, um festzustellen, ob ein gültiges Muster erkannt wurde, und „PatternDirection“ mit einem leeren String, um den Mustertyp zu speichern. Um zu prüfen, ob es sich um einen Abwärts-Cypher handelt, überprüfen wir, ob „PointX.IsSwingHigh“ wahr ist, „PointA.IsSwingHigh“ falsch ist, „PointB.IsSwingHigh“ wahr ist, „PointC.IsSwingHigh“ falsch ist und „PointD.IsSwingHigh“ wahr ist, sodass die Abfolge hoch-tief-hoch-tief-hoch gewährleistet ist.

Wenn ja, berechnen wir die Längen der Schenkel: “LegXA“ als „PointX.PriceAtSwing“ minus „PointA.PriceAtSwing“ (positiv für abwärts), „LegAB“ als „PointB.PriceAtSwing“ minus „PointA.PriceAtSwing“, „LegBC“ als „PointB.PriceAtSwing“ minus „PointC.PriceAtSwing“, „LegXC“ als „PointX.PriceAtSwing“ minus „PointC.PriceAtSwing“, und „LegCD“ als „PointD.PriceAtSwing“ minus „PointC.PriceAtSwing“.

Wir überprüfen die Fibonacci-Verhältnisse, indem wir sicherstellen, dass „LegAB“ 38,2 % bis 61,8 % von „LegXA“, „LegBC“ 127,2 % bis 141,4 % von „LegAB“, „LegCD“ innerhalb der „FibonacciToleranz“ von 78.6% von „LegXC“ unter Verwendung der Funktion „MathAbs“, und „PointD.PriceAtSwing“ übersteigt „PointX.PriceAtSwing“, wodurch „PatternFound“ auf true und „PatternDirection“ auf „Bearish“ gesetzt wird, wenn alle Bedingungen erfüllt sind.

Bei einem Aufwärts-Cypher wird die umgekehrte Reihenfolge geprüft: “PunktX.IsSwingHigh“ falsch, „PunktA.IsSwingHigh“ wahr, „PunktB.IsSwingHigh“ falsch, „PunktC.IsSwingHigh“ wahr und „PunktD.IsSwingHigh“ falsch.

Wir berechnen „LegXA“ als „PointA.PriceAtSwing“ minus „PointX.PriceAtSwing“ (positiv für aufwärts), „LegAB“ als „PointA.PriceAtSwing“ minus „PointB.PriceAtSwing“, „LegBC“ als „PointC.PriceAtSwing“ minus „PointB.PriceAtSwing“, „LegXC“ als „PointC.PriceAtSwing“ minus „PointX.PriceAtSwing“, und „LegCD“ als „PointC.PriceAtSwing“ minus „PointD.PriceAtSwing“.

Es gelten dieselben Fibonacci-Prüfungen, wobei „PointD.PriceAtSwing“ kleiner ist als „PointX.PriceAtSwing“, wodurch „PatternFound“ auf „true“ und „PatternDirection“ auf „Bullish“ aktualisiert wird, wenn es gültig ist, was die anschließende Visualisierung und Handelslogik ermöglicht. Wenn das Muster gefunden wurde, können wir es im Chart visualisieren.

//--- If a pattern is found, visualize it and trade
if(PatternFound) {  
   //--- Log the pattern detection in the Experts tab
   Print(PatternDirection, " Cypher pattern detected at ", TimeToString(PointD.TimeOfSwing, TIME_DATE|TIME_MINUTES));  
   
   //--- Create a unique prefix for all chart objects using D’s time
   string ObjectPrefix = "CY_" + IntegerToString(PointD.TimeOfSwing);  
   //--- Set triangle color: blue for bullish, red for bearish
   color TriangleColor = (PatternDirection == "Bullish") ? clrBlue : clrRed;  
   
   //--- **Visualization Steps**
   //--- 1. Draw two filled triangles to highlight the pattern
   DrawTriangle(ObjectPrefix + "_Triangle1", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, TriangleColor, 2, true, true);  
   DrawTriangle(ObjectPrefix + "_Triangle2", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, TriangleColor, 2, true, true);  
}

Wir fahren fort mit der Visualisierung eines erkannten Cypher-Musters, wenn „PatternFound“ wahr ist. Wir verwenden die Funktion Print, um die Mustererkennung in der Registerkarte Experten zu protokollieren. Dabei wird „PatternDirection“ gefolgt von einer Meldung ausgegeben, die angibt, dass ein Cypher-Muster erkannt wurde, wobei die Zeit mit der Funktion TimeToString unter Verwendung von „PointD.TimeOfSwing“ und den Flags „TIME_DATE|TIME_MINUTES“ zur besseren Lesbarkeit formatiert wird.

Um die Chartobjekte zu organisieren, erstellen wir ein eindeutiges Präfix „ObjectPrefix“, indem wir „CY_“ mit der String-Darstellung von „PointD.TimeOfSwing“ verknüpfen, die wir über die Funktion IntegerToString erhalten haben, um sicherzustellen, dass die Objekte jedes Musters eindeutig benannt sind. Dann setzen wir „TriangleColor“ mit einem ternären Operator, indem wir „clrBlue“ für ein Aufwärts-Muster oder „clrRed“ für ein Abwärts-Muster basierend auf „PatternDirection“ zuweisen.

Zur Visualisierung rufen wir die Funktion „DrawTriangle“ zweimal auf: zunächst, um ein Dreieck mit dem Namen „ObjectPrefix + '_Triangle1'“ zu zeichnen, das „PointX“, „PointA“ und „PointB“ unter Verwendung ihrer „TimeOfSwing“- und „PriceAtSwing“-Werte verbindet, und dann für „ObjectPrefix + '_Triangle2'“, das „PointB“, „PointC“ und „PointD“ verbindet, beide mit „TriangleColor“, einer Linienbreite von 2 und „true“ zum Füllen und Zeichnen hinter den Kerzen, um die Struktur des Musters im Chart hervorzuheben. Hier ist, was wir bisher erreicht haben.

ABBILDUNG DER ZIFFERNHARMONISCHEN DREIECKE

Anhand des Bildes können wir sehen, dass wir das erkannte Muster korrekt abbilden und visualisieren können. Jetzt müssen wir die Trendlinien weiter kartieren, um sie innerhalb der Grenzen vollständig sichtbar zu machen und sie zur leichteren Identifizierung der Ebenen zu beschriften.

//--- 2. Draw six trend lines connecting the swing points
DrawTrendLine(ObjectPrefix + "_Line_XA", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_AB", PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_BC", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_CD", PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_XB", PointX.TimeOfSwing, PointX.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_BD", PointB.TimeOfSwing, PointB.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  

//--- 3. Draw labels for each swing point (X, A, B, C, D)
double LabelOffset = 15 * SymbolInfoDouble(_Symbol, SYMBOL_POINT); //--- Offset in points for label placement
DrawTextLabel(ObjectPrefix + "_Label_X", "X", PointX.TimeOfSwing, PointX.PriceAtSwing + (PointX.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointX.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_A", "A", PointA.TimeOfSwing, PointA.PriceAtSwing + (PointA.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointA.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_B", "B", PointB.TimeOfSwing, PointB.PriceAtSwing + (PointB.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointB.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_C", "C", PointC.TimeOfSwing, PointC.PriceAtSwing + (PointC.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointC.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_D", "D", PointD.TimeOfSwing, PointD.PriceAtSwing + (PointD.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointD.IsSwingHigh);  

//--- 4. Draw a central label to identify the pattern
datetime CenterTime = (PointX.TimeOfSwing + PointB.TimeOfSwing) / 2;  //--- Middle time between X and B
double CenterPrice = PointD.PriceAtSwing;                            //--- Place it at D’s price level
if(ObjectCreate(0, ObjectPrefix + "_Label_Center", OBJ_TEXT, 0, CenterTime, CenterPrice)) {  
   ObjectSetString(0, ObjectPrefix + "_Label_Center", OBJPROP_TEXT, "Cypher"); //--- Label as "Cypher"
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_COLOR, clrBlack);  
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_FONTSIZE, 11);  
   ObjectSetString(0, ObjectPrefix + "_Label_Center", OBJPROP_FONT, "Arial Bold");  
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_ALIGN, ALIGN_CENTER);  
}  

Wir setzen den Visualisierungsprozess fort, um das Cypher-Muster im Chart weiter zu veranschaulichen. Wir rufen die Funktion „DrawTrendLine“ sechsmal auf, um durchgehende schwarze Linien zu zeichnen, die die Umkehrpunkte miteinander verbinden und jeweils mit „ObjectPrefix“ plus einem eindeutigen Suffix (z. B. „_Line_XA“) benannt sind. Diese Linien verbinden „PunktX“ mit „PunktA“, „PunktA“ mit „PunktB“, „PunktB“ mit „PunktC“, „PunktC“ mit „PunktD“, „PunktX“ mit „PunktB“und „PunktB“ mit „PunktD“ unter Verwendung ihrer jeweiligen „TimeOfSwing“- und „PriceAtSwing“-Werte, mit einer Linienbreite von 2 und STYLE_SOLID zur klaren Abgrenzung der Struktur des Musters.

Als Nächstes fügen wir Textbeschriftungen für jeden Schwingungspunkt hinzu, indem wir „LabelOffset“ als das 15-fache der Punktgröße des Symbols berechnen, die über die Funktion SymbolInfoDouble mit SYMBOL_POINT abgerufen wird, um die Beschriftungen entsprechend zu positionieren. Wir rufen die Funktion „DrawTextLabel“ fünfmal auf, um „PointX“, „PointA“, „PointB“, „PointC“ und „PunktD“ mit Namen wie „ObjectPrefix + „_Label_X“ und den Text „X“, „A“, „B“, „C“, „D“. Jede Kennzeichnung verwendet die „TimeOfSwing“ und „PriceAtSwing“ des Punktes, angepasst durch „LabelOffset“ (addiert, wenn „IsSwingHigh“ wahr ist, subtrahiert, wenn falsch), wobei die Farbe „clrBlack“, die Schriftgröße 11 und „IsSwingHigh“ die Platzierung über oder unter dem Punkt bestimmen.

Schließlich erstellen wir ein zentrales Label, um das Muster zu identifizieren, indem wir „CenterTime“ als Durchschnitt von „PointX.TimeOfSwing“ und „PointB.TimeOfSwing“ berechnen und „CenterPrice“ auf „PointD.PriceAtSwing“ setzen. Wir verwenden die Funktion ObjectCreate, um ein Textobjekt mit dem Namen „ObjectPrefix + „_Label_Center“ vom Typ „OBJ_TEXT“ an diesen Koordinaten zu erstellen. Bei Erfolg konfigurieren wir es mit „ObjectSetString“, um „OBJPROP_TEXT“ auf „Cypher“ und „OBJPROP_FONT“ auf „Arial Bold“ zu setzen, und mit ObjectSetInteger, um „OBJPROP_COLOR“ auf „clrBlack“, „OBJPROP_FONTSIZE“ auf 11 und „OBJPROP_ALIGN“ auf „ALIGN_CENTER“ zu setzen, wodurch das Muster im Chart deutlich gekennzeichnet wird. Nach dem Compilieren ergibt sich das folgende Ergebnis.

MUSTER MIT KANTEN UND KENNZEICHNUNG

Auf dem Bild können wir sehen, dass wir die Ränder und die Kennzeichnung zum Muster hinzugefügt haben, um es aufschlussreicher und anschaulicher zu machen. Als Nächstes müssen wir die Handelsniveaus für dieses Muster bestimmen.

//--- 5. Draw trade levels (entry, take-profits) as dotted lines
datetime LineStartTime = PointD.TimeOfSwing;                    //--- Start at D’s time
datetime LineEndTime = PointD.TimeOfSwing + PeriodSeconds(_Period) * 2; //--- Extend 2 bars to the right
double EntryPrice, StopLossPrice, TakeProfitPrice, TakeProfit1Level, TakeProfit2Level, TakeProfit3Level, TradeDistance;  
if(PatternDirection == "Bullish") {  
   EntryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Buy at current ask price
   StopLossPrice = PointX.PriceAtSwing;                //--- Stop-loss at X (below entry for bullish)
   TakeProfitPrice = PointC.PriceAtSwing;              //--- Take-profit at C (target level)
   TakeProfit3Level = PointC.PriceAtSwing;             //--- Highest TP at C
   TradeDistance = TakeProfit3Level - EntryPrice;      //--- Distance to TP3
   TakeProfit1Level = EntryPrice + TradeDistance / 3;  //--- First TP at 1/3 of the distance
   TakeProfit2Level = EntryPrice + 2 * TradeDistance / 3; //--- Second TP at 2/3
} else {  
   EntryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Sell at current bid price
   StopLossPrice = PointX.PriceAtSwing;                //--- Stop-loss at X (above entry for bearish)
   TakeProfitPrice = PointC.PriceAtSwing;              //--- Take-profit at C
   TakeProfit3Level = PointC.PriceAtSwing;             //--- Lowest TP at C
   TradeDistance = EntryPrice - TakeProfit3Level;      //--- Distance to TP3
   TakeProfit1Level = EntryPrice - TradeDistance / 3;  //--- First TP at 1/3
   TakeProfit2Level = EntryPrice - 2 * TradeDistance / 3; //--- Second TP at 2/3
}  

DrawDottedLine(ObjectPrefix + "_EntryLine", LineStartTime, EntryPrice, LineEndTime, clrMagenta); //--- Entry line
DrawDottedLine(ObjectPrefix + "_TP1Line", LineStartTime, TakeProfit1Level, LineEndTime, clrForestGreen); //--- TP1 line
DrawDottedLine(ObjectPrefix + "_TP2Line", LineStartTime, TakeProfit2Level, LineEndTime, clrGreen);      //--- TP2 line
DrawDottedLine(ObjectPrefix + "_TP3Line", LineStartTime, TakeProfit3Level, LineEndTime, clrDarkGreen);  //--- TP3 line

//--- 6. Draw labels for trade levels
datetime LabelTime = LineEndTime + PeriodSeconds(_Period) / 2; //--- Place labels further right
string EntryLabelText = (PatternDirection == "Bullish") ? "BUY (" : "SELL (";  
EntryLabelText += DoubleToString(EntryPrice, _Digits) + ")"; //--- Add price to label
DrawTextLabel(ObjectPrefix + "_EntryLabel", EntryLabelText, LabelTime, EntryPrice, clrMagenta, 11, true);  

string TP1LabelText = "TP1 (" + DoubleToString(TakeProfit1Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP1Label", TP1LabelText, LabelTime, TakeProfit1Level, clrForestGreen, 11, true);  

string TP2LabelText = "TP2 (" + DoubleToString(TakeProfit2Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP2Label", TP2LabelText, LabelTime, TakeProfit2Level, clrGreen, 11, true);  

string TP3LabelText = "TP3 (" + DoubleToString(TakeProfit3Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP3Label", TP3LabelText, LabelTime, TakeProfit3Level, clrDarkGreen, 11, true);  

Hier fahren wir fort, die Handelsstufen für das Cypher-Muster zu visualisieren, indem wir gestrichelte Linien und Kennzeichnungen einzeichnen. Wir setzen „LineStartTime“ auf „PointD.TimeOfSwing“ und „LineEndTime“ auf zwei Balken darüber hinaus, indem wir die Funktion PeriodSeconds mit 2 multiplizieren und so den Zeitbereich für horizontale Linien definieren.

Für ein Aufwärts-Muster (wenn „PatternDirection“ „Bullish“ ist), setzen wir „EntryPrice“ auf den aktuellen Briefkurs via SymbolInfoDouble mit SYMBOL_ASK, „StopLossPrice“ auf „PointX.PriceAtSwing“, „TakeProfitPrice“ und „TakeProfit3Level“ auf „PointC.PriceAtSwing“, berechnen „TradeDistance“ als „TakeProfit3Level“ minus „EntryPrice“, und berechnen „TakeProfit1Level“ und „TakeProfit2Level“ als ein Drittel und zwei Drittel von „TradeDistance“, addiert zu „EntryPrice“.

Für ein Abwärts-Muster verwenden wir SYMBOL_BID für „EntryPrice“, setzen „StopLossPrice“ und „TakeProfit3Level“ in ähnlicher Weise, berechnen „TradeDistance“ als „EntryPrice“ minus „TakeProfit3Level minus „TakeProfit3Level“, und ziehen ein Drittel und zwei Drittel von „TradeDistance“ vom „EntryPrice“ für „TakeProfit1Level“ und „TakeProfit2Level“ ab.

Anschließend rufen wir die Funktion „DrawDottedLine“ viermal auf, um horizontale Linien zu zeichnen: “ObjectPrefix + '_EntryLine'“ bei „EntryPrice“ in „clrMagenta“, und „ObjectPrefix + '_TP1Line'“, „ObjectPrefix + '_TP2Line'“, „ObjectPrefix + '_TP3Line'“ bei „TakeProfit1Level“, „TakeProfit2Level“ und „TakeProfit3Level“ in „clrForestGreen“, „clrGreen“ und „clrDarkGreen“, jeweils von „LineStartTime“ bis „LineEndTime“.

Für die Kennzeichnung setzen wir „LabelTime“ auf „LineEndTime“ plus die Hälfte der Dauer eines Balkens mit „PeriodSeconds“. Wir erstellen „EntryLabelText“ als „BUY“ (oder „SELL“) basierend auf „PatternDirection“, fügen „EntryPrice“ formatiert mit „DoubleToString“ mit „_Digits“, und rufen „DrawTextLabel“ für „ObjectPrefix + '_EntryLabel'“ bei „EntryPrice“ in „clrMagenta“ auf.

Ähnlich definieren wir „TP1LabelText“, „TP2LabelText“ und „TP3LabelText“ mit „TakeProfit1Level“, „TakeProfit2Level“ und „TakeProfit3Level“ formatierten Preisen und rufen „DrawTextLabel“ für die jeweiligen Levels in „clrForestGreen“, „clrGreen“ und „clrDarkGreen“, alle mit Schriftgröße 11 und oberhalb des Preises platziert, was die Übersichtlichkeit der Handelsebenen verbessert. Hier ist das Ergebnis.

Abwärts-Muster:

ABWÄRTS-MUSTER

Aufwärts-Muster:

AUFWÄRTS-MUSTER

Anhand der Bilder können wir sehen, dass wir die Handelsstufen richtig zugeordnet haben. Was wir jetzt tun müssen, ist, die eigentlichen Handelspositionen zu initiieren, und das ist alles.

//--- **Trading Logic**
//--- Check if trading is allowed and no position is already open
if(TradingEnabled && !PositionSelect(_Symbol)) {  
   //--- Place a buy or sell order based on pattern type
   bool TradeSuccessful = (PatternDirection == "Bullish") ?  
      obj_Trade.Buy(TradeVolume, _Symbol, EntryPrice, StopLossPrice, TakeProfitPrice, "Cypher Buy") :  
      obj_Trade.Sell(TradeVolume, _Symbol, EntryPrice, StopLossPrice, TakeProfitPrice, "Cypher Sell");  
   //--- Log the result of the trade attempt
   if(TradeSuccessful)  
      Print(PatternDirection, " order opened successfully.");  
   else  
      Print(PatternDirection, " order failed: ", obj_Trade.ResultRetcodeDescription());  
}  

//--- Force the chart to update and show all drawn objects
ChartRedraw();  

Hier implementieren wir die Handelslogik, um Handelsgeschäfte für das Cypher-Muster auszuführen, wenn die Bedingungen erfüllt sind. Wir prüfen, ob „TradingEnabled“ wahr ist und keine bestehende Position für das aktuelle Symbol offen ist, indem wir die Funktion PositionSelect mit _Symbol verwenden. So stellen wir sicher, dass Geschäfte nur dann getätigt werden, wenn sie erlaubt sind und keine kollidierenden Positionen existieren. Wenn beide Bedingungen erfüllt sind, verwenden wir einen ternären Operator, um einen Handel auf der Grundlage von „PatternDirection“ zu platzieren: Für ein Aufwärts-Muster rufen wir die Funktion „obj_Trade.Buy“ mit den Parametern „TradeVolume“, _Symbol, „EntryPrice“, „StopLossPrice“, „TakeProfitPrice“ und dem Kommentar „Cypher Buy“ auf, während wir für ein Abwärts-Muster die Funktion „obj_Trade.Sell“ mit denselben Parametern, aber dem Kommentar „Cypher Sell“ auf und speichern das Ergebnis in „TradeSuccessful“.

Anschließend protokollieren wir das Ergebnis mit der Funktion Print und geben „PatternDirection“ und „Order opened successfully“ aus, wenn „TradeSuccessful“ true ist, oder „Order failed“ mit der Fehlerbeschreibung aus „obj_Trade.ResultRetcodeDescription“, wenn false. Schließlich rufen wir die Funktion ChartRedraw auf, um die Aktualisierung des MetaTrader 5-Charts zu erzwingen und sicherzustellen, dass alle gezeichneten Objekte, wie Dreiecke, Linien und Beschriftungen, für den Nutzer sofort sichtbar sind. 

Zum Schluss müssen wir nur noch die Muster aus dem Chart löschen, wenn wir das Programm entfernen.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
//--- Runs when the EA stops (e.g., removed from chart)
void OnDeinit(const int reason) {  
   //--- Remove all objects starting with "CY_" (our Cypher pattern objects)
   ObjectsDeleteAll(0, "CY_");  
}  

Innerhalb der Ereignisbehandlung von OnDeinit verwenden wir die Funktion ObjectsDeleteAll, um alle Chartobjekte zu entfernen, deren Namen mit dem Präfix „CY_“ beginnen. Dadurch wird sichergestellt, dass alle mit Cypher-Mustern verbundenen Visualisierungen wie Dreiecke, Trendlinien und Beschriftungen aus dem Chart gelöscht werden und ein sauberer Arbeitsbereich erhalten bleibt, wenn das System nicht mehr aktiv ist. Nach der Kompilierung erhalten wir folgendes Ergebnis.

HANDELSBESTÄTIGUNG

Aus dem Bild können wir ersehen, dass wir das Cypher-Muster aufzeichnen und trotzdem in der Lage sind, es entsprechend zu handeln, sobald es bestätigt ist, und somit unser Ziel zu erreichen, das Muster zu identifizieren, aufzuzeichnen und zu handeln. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.


Backtest und Optimierung

Während der anfänglichen Backtests stellten wir ein kritisches Problem fest: Das System neigte dazu, Muster zu übermalen. Repainting trat auf, wenn ein Cypher-Muster auf einem Balken gültig erschien, sich aber änderte oder verschwand, als neue Kursdaten eintrafen, was zu unzuverlässigen Handelssignalen führte. Dieses Problem führte zu Fehlalarmen, bei denen Geschäfte auf der Grundlage von Mustern ausgeführt wurden, die sich später als ungültig erwiesen, was sich negativ auf die Leistung auswirkte. Hier ist ein Beispiel dafür, was wir meinen.

REPAINTED MUSTER

Um dieses Problem zu beheben, haben wir einen Mechanismus zum Sperren von Mustern implementiert, der die globalen Variablen „g_patternFormationBar“ und „g_lockedPatternX“ verwendet, um das Muster bei der Erkennung zu sperren und es beim nächsten Balken zu bestätigen, um sicherzustellen, dass der X-Umkehrpunkt konsistent bleibt. Diese Korrektur reduzierte das Repainting erheblich, was durch nachfolgende Tests bestätigt wurde, die eine stabilere Mustererkennung und weniger ungültige Handelsgeschäfte zeigten. Hier ist ein Beispielcode zum Sperren des Musters, um sicherzustellen, dass wir warten, bis das Muster stabil ist, bevor wir es handeln.

//--- If the pattern has changed, update the lock
g_patternFormationBar = CurrentBarIndex;
g_lockedPatternX = PointX.TimeOfSwing;
Print("Cypher pattern has changed; updating lock on bar ", CurrentBarIndex, ". Waiting for confirmation.");
return;

Wir fügen eine Bestätigungslogik hinzu, um immer zu warten, bis das Muster bestätigt und für einen zusätzlichen Takt stabil ist, damit wir nicht zu früh in die Position einsteigen, nur um dann festzustellen, dass es sich um den Beginn der Musterbildung handelt. Nach dem Hinzufügen des Sperrmusters ist das Problem nun gelöst.

VERBESSERTE MUSTERSPERRE

Nach der Korrektur und gründlichen Backtests haben wir die folgenden Ergebnisse.

Grafik des Backtests:

GRAFIK

Bericht des Backtest:

BERICHT


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir erfolgreich einen Expert Advisor in MetaQuotes Language 5 entwickelt haben, der das Cypher Harmonic Pattern mit Präzision erkennt und handelt. Durch die Integration von Swing-Point-Erkennung, Fibonacci-basierter Validierung, umfassender Visualisierung und einen Sperrmechanismus des Musters, der ein Repainting vermeidet, haben wir ein robustes 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/17865

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (4)
Kyle Young Sangster
Kyle Young Sangster | 3 Mai 2025 in 19:30
Hallo, toller Artikel. Vielen Dank für Ihre harte Arbeit. Könnten Sie die fertige Codedatei zur Verfügung stellen? Sie am Ende des Artikels anhängen?
Herzlichen Dank!
Allan Munene Mutiiria
Allan Munene Mutiiria | 5 Mai 2025 in 07:04
Kyle Young Sangster #:
Hallo, toller Artikel. Vielen Dank für Ihre harte Arbeit. Könnten Sie die fertige Codedatei zur Verfügung stellen? Sie am Ende des Artikels anhängen?
Herzlichen Dank!

Gern geschehen. Haben Sie das überhaupt überprüft? Ja, danke.

Muhammad Syamil Bin Abdullah
Muhammad Syamil Bin Abdullah | 7 Juni 2025 in 11:08
Vielen Dank für diesen Artikel. Nützlicher Code, um andere Oberschwingungsmuster zu implementieren.
Allan Munene Mutiiria
Allan Munene Mutiiria | 7 Juni 2025 in 17:15
Muhammad Syamil Bin Abdullah #:
Vielen Dank für diesen Artikel. Nützlicher Code, um andere Oberschwingungsmuster zu implementieren.

Sicher. Willkommen.

Integration des AI-Modells in eine bereits bestehende MQL5-Handelsstrategie Integration des AI-Modells in eine bereits bestehende MQL5-Handelsstrategie
Dieses Thema konzentriert sich auf die Einbindung eines trainierten KI-Modells (z. B. eines Verstärkungslernmodells wie LSTM oder eines auf maschinellem Lernen basierenden Prognosemodells) in eine bestehende MQL5-Handelsstrategie.
Vom Neuling zum Experten: Programmieren von Kerzen Vom Neuling zum Experten: Programmieren von Kerzen
In diesem Artikel machen wir den ersten Schritt in die MQL5-Programmierung, auch für absolute Anfänger. Wir zeigen Ihnen, wie Sie bekannte Kerzenmuster in einen voll funktionsfähigen nutzerdefinierten Indikator verwandeln können. Kerzenmuster sind wertvoll, da sie reale Kursbewegungen widerspiegeln und Marktverschiebungen signalisieren. Anstatt die Charts manuell zu scannen - ein Ansatz, der fehleranfällig und ineffizient ist - werden wir besprechen, wie Sie den Prozess mit einem Indikator automatisieren können, der Muster für Sie identifiziert und kennzeichnet. Auf dem Weg dorthin werden wir uns mit Schlüsselkonzepten wie Indexierung, Zeitreihen, Average True Range (für Genauigkeit bei schwankender Marktvolatilität) und der Entwicklung einer nutzerdefinierten, wiederverwendbaren Bibliothek von Kerzen-Mustern für den Einsatz in zukünftigen Projekten beschäftigen.
Erstellen eines Handelsadministrator-Panels in MQL5 (Teil X): Externe, ressourcenbasierte Schnittstelle Erstellen eines Handelsadministrator-Panels in MQL5 (Teil X): Externe, ressourcenbasierte Schnittstelle
Heute machen wir uns die Möglichkeiten von MQL5 zunutze, um externe Ressourcen - wie Bilder im BMP-Format - zu nutzen und eine einzigartig gestaltete Nutzeroberfläche für das Trading Administrator Panel zu erstellen. Die hier gezeigte Strategie ist besonders nützlich, wenn mehrere Ressourcen, einschließlich Bilder, Töne und mehr, für eine rationelle Verteilung zusammengefasst werden. Nehmen Sie an dieser Diskussion teil und erfahren Sie, wie diese Funktionen implementiert werden, um eine moderne und visuell ansprechende Oberfläche für unser New_Admin_Panel EA zu schaffen.
Formulierung eines dynamischen Multi-Pair EA (Teil 2): Portfolio-Diversifizierung und -Optimierung Formulierung eines dynamischen Multi-Pair EA (Teil 2): Portfolio-Diversifizierung und -Optimierung
Portfolio-Diversifizierung und -Optimierung sorgt für eine strategische Streuung der Anlagen auf mehrere Vermögenswerte, um das Risiko zu minimieren und gleichzeitig die ideale Mischung von Vermögenswerten auszuwählen, um die Renditen auf der Grundlage risikobereinigter Performance-Kennzahlen zu maximieren.