English
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 44): Erkennung des Change of Character (CHoCH) mit Durchbrechen der hohen und tiefen Umkehrpunkte

Automatisieren von Handelsstrategien in MQL5 (Teil 44): Erkennung des Change of Character (CHoCH) mit Durchbrechen der hohen und tiefen Umkehrpunkte

MetaTrader 5Handelssysteme |
22 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 43) haben wir eine adaptive Strategie eines linearen Regressionskanals in MetaQuotes Language 5 (MQL5) entwickelt, die Regressionslinien mit Abweichungsbändern berechnet, nur bei ausreichender Steigung aktiviert, bei Abweichungen dynamisch erweitert oder neu erstellt, normale/umgekehrte Modi unterstützt, bei Durchbrüchen von innen geöffnet, bei Mittellinienkreuzungen geschlossen, Positionen begrenzt und gefüllte Zonen mit Etiketten/Pfeilen visualisiert. In Teil 44 entwickeln wir ein Erkennungssystem für den Change of Character (CHoCH) mit Durchbrüchen der hohen und tiefen Umkehrpunkte ( swing high/low).

Dieses System scannt Balken, um hohe/tiefe Umkehrpunkte zu identifizieren und als HH/LH/LL/HL zur Trendbestimmung zu kennzeichnen. Es handelt bei Durchbrüche, die Umkehrungen signalisieren (Käufe über den Hochs in Abwärtstrends, Verkäufe unter den Tiefs in Aufwärtstrends), bietet Modi pro Balken/Tick, feste Handelsniveaus mit Risiko/Ertrags-Verhältnis, Handelslimits, Trailing Stops und eine visuelle Darstellung mit Symbolen, Etiketten und Break-Linien sowie dynamische Schriftarten. Wir werden die folgenden Themen behandeln:

  1. Verstehen der Strategie des Change of Character (CHoCH)
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende werden Sie eine funktionierende MQL5-Strategie zum Erkennen und Handeln von CHoCH-Umkehrungen mit anpassbaren Scans, Risikomanagement und klarem visuellem Feedback haben – fangen wir an!


Verstehen der Strategie des Change of Character (CHoCH)

Dabei handelt es sich um ein Preisaktionskonzept, das eine potenzielle Trendumkehr signalisiert, wenn der Preis einen kürzlich erreichten hohen oder tiefen Umkehrpunkt auf eine Weise durchbricht, die der etablierten Trendrichtung widerspricht. Wir identifizieren hohe Umkehrpunkte (Punkte, die höher sind als die umliegenden Balken) und tiefe Umkehrpunkte (niedriger als die Umgebung) und kennzeichnen sie dann auf der Grundlage des Vergleichs mit dem vorherigen Swing: HH (höheres Hoch) oder LH (niedrigeres Hoch) für Höchstwerte, LL (tiefstes Tief) oder HL (höheres Tief) für Tiefstwerte.

Eine Sequenz von HH/HL deutet auf einen Aufwärtstrend hin, während LH/LL einen Abwärtstrend signalisiert; CHoCH tritt auf, wenn der Kurs den jüngsten hohen Umkehrpunkt während eines Abwärtstrends (Aufwärtsumkehr) oder den jüngsten tiefen Umkehrpunkt während eines Aufwärtstrends (Abwärtsumkehr) durchbricht, was einen „Change“ anzeigt, bei dem Käufer/Verkäufer die Kontrolle übernehmen. In einem Abwärtstrend (definiert durch LH oder LL) wird ein Aufwärts-CHoCH ausgelöst, wenn der Kurs über dem jüngsten hohen Umkehrpunkt schließt und damit bestätigt, dass die Käufer die vorherige Struktur durchbrochen haben – steigen Sie ein und kaufen Sie mit einem Stop-Loss unterhalb des Durchbruchsniveaus. Umgekehrt wird in einem Aufwärtstrend (HH oder HL) ein Abwärts-CHoCH bei einem Schlusskurs unter dem jüngsten tiefen Umkehrpunkt ausgelöst, wobei der Verkauf mit einem Stop-Loss oberhalb und einem Take-Profit nach unten erfolgt.

Unser Plan ist es, die Balken um jede neue Kerze herum zu scannen, um Schwungbewegungen nach oben oder unten zu erkennen und als HH/LH/LL/HL zu kennzeichnen, den aktuellen Trend anhand von Kennzeichnungssequenzen zu bestimmen, CHoCH-Käufe bei Durchbrüchen über Hochs in Abwärtstrends oder Verkäufe unter Tiefs in Aufwärtstrends auszulösen, die Gesamtzahl der offenen Handelsgeschäfte zu begrenzen, Fixpunkt-Handel mit einstellbaren Risiko-Ertrags-Verhältnissen (R:R), optionale punktbasierte Trailing-Stops nach einem Gewinnschwellenwert und Visualisierung mit farbigen Symbolen/Etiketten bei Schwankungen sowie Pfeillinien/Text bei CHoCH-Durchbrüchen, mit dynamischer Schriftgrößenanpassung bei Änderungen des Chartmaßstabs. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

CHoCH-SYSTEM


Implementation in MQL5

Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner „Experts“, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                                                     CHoCH EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade/Trade.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Trade object
int   object_code     = 174;                                      //--- Object code
int   current_font_size = 10;                                     //--- Current font size
long  magic_number    = 123456789;                                //--- Magic number

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum TradeMode {                                                  // Define trade mode enum
   PerBar,                                                        // Per Bar
   PerTick                                                        // Per Tick
};

enum TrailingTypeEnum {                                           // Define enum for trailing stop types
   Trailing_None   = 0,                                           // None
   Trailing_Points = 2                                            // By Points
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "EA GENERAL SETTINGS"
input double inpLot             = 0.01;                           // Lotsize
input int    sl_pts             = 300;                            // Stop Loss Points
input int    tp_pts             = 300;                            // Take Profit Points
input double r2r_ratio          = 
1 ;                              // Risk : Reward Ratio
input int    totalTrades        = 1;                              // Total Possible Open Trades
input color  def_clr_up         = clrBlue;                        // Swing High Color
input color  def_clr_down       = clrRed;                         // Swing Low Color
input int    ext_bars           = 5;                              // CHoCH Scan Length in Bars
input bool   prt                = true;                           // Print Statements
input int    width              = 2;                              // Width
input TradeMode trade_mode      = PerBar;                         // Trade Mode
input TrailingTypeEnum TrailingType = Trailing_None;              // Trailing Stop Type
input double Trailing_Stop_Pips = 30.0;                           // Trailing Stop in Pips (for Points type)
input double Min_Profit_To_Trail_Pips = 50.0;                     // Min Profit to Start Trailing in Pips

Wir beginnen die Implementierung, indem wir die Handelsbibliothek mit „#include <Trade/Trade.mqh>“ einbinden, wodurch wir Zugang zur Klasse CTrade für Auftrags- und Positionsoperationen erhalten. Dann deklarieren wir mehrere globale Variablen: „obj_Trade“ als Instanz von „CTrade“ für die Handhabung von Handelsgeschäften, „object_code“ auf 174 für das spezifische Symbol Wingdings, das wir in der Grafik verwenden, „current_font_size“ initialisiert auf 10 für die dynamische Textgröße und „magic_number“ als 123456789 zur eindeutigen Identifizierung unserer Handelsgeschäfte. Wenn Sie möchten, können Sie all diese Daten eingeben. Wir definieren zwei Enumerationen für Nutzerkonfigurationen. Die Enumeration „TradeMode“ bietet „PerBar“ für die Erkennung von Durchbrüchen des Schlusskurses des Balkens und „PerTick“ für die Tick-basierte Erkennung in Echtzeit. Die Enumeration „TrailingTypeEnum“ bietet „Trailing_None“, um das Trailing zu deaktivieren und „Trailing_Points“, um punktbasierte Trailing-Stops zu aktivieren.

Wir gruppieren die Eingabeparameter unter „EA GENERAL SETTINGS“ für eine übersichtliche Darstellung im Eigenschaftsdialog. Dazu gehören „inpLot“ für die Losgröße, „sl_pts“ und „tp_pts“ für Stop-Loss- und Take-Profit-Abstände in Punkten, „r2r_ratio“ für den Risk-to-Reward-Multiplikator (wird für die TP-Berechnung auf SL angewandt, Sie können ihn aber auch anders festlegen), „totalTrades“ für die Begrenzung der gleichzeitig offenen Positionen, „def_clr_up“ und „def_clr_down“ für die Farben der hohen (blau) und tiefen (rot) Umkehrpunkte, „ext_bars“ als Scanlänge für die CHoCH-Erkennung, „prt“ zum Umschalten der Druckprotokollierung, „width“ für die Liniendicke in der Grafik, „trade_mode“ unter Verwendung der Enumeration für pro Balken oder pro Tick, „TrailingType“ mit seiner Enumeration,, „Trailing_Stop_Pips“ für den Trailing-Abstand und „Min_Profit_To_Trail_Pips“ für die Mindestgewinnschwelle, bevor das Trailing aktiviert wird. Wir können nun einige Hilfsfunktionen definieren, um das Programm zu modularisieren.

//+------------------------------------------------------------------+
//| Update font sizes function                                       |
//+------------------------------------------------------------------+
void UpdateFontSizes() {
   long scale = 0;                                                //--- Init scale
   if (ChartGetInteger(0, CHART_SCALE, 0, scale)) {               //--- Get scale
      current_font_size = (int)(7 + scale * 0.7);                 //--- Calc font size
      if (current_font_size < 6) current_font_size = 6;           //--- Min font size
      if (current_font_size > 15) current_font_size = 15;         //--- Max font size
      for (int i = ObjectsTotal(0, -1, -1) - 1; i >= 0; i--) {    //--- Iterate objects
         string name = ObjectName(0, i, -1, -1);                  //--- Get name
         long type = ObjectGetInteger(0, name, OBJPROP_TYPE);     //--- Get type
         if (type == OBJ_TEXT) {                                  //--- Check text
            ObjectSetInteger(0, name, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
         }
      }
      ChartRedraw(0);                                             //--- Redraw chart
   }
}

//+------------------------------------------------------------------+
//| High function                                                    |
//+------------------------------------------------------------------+
double high(int index) {
   return (iHigh(_Symbol,_Period,index));                         //--- Return high
}

//+------------------------------------------------------------------+
//| Low function                                                     |
//+------------------------------------------------------------------+
double low(int index) {
   return (iLow(_Symbol,_Period,index));                          //--- Return low
}

//+------------------------------------------------------------------+
//| Close function                                                   |
//+------------------------------------------------------------------+
double close(int index) {
   return (iClose(_Symbol,_Period,index));                        //--- Return close
}

//+------------------------------------------------------------------+
//| Time function                                                    |
//+------------------------------------------------------------------+
datetime time(int index) {
   return (iTime(_Symbol,_Period,index));                         //--- Return time
}

//+------------------------------------------------------------------+
//| Draw swing point                                                 |
//+------------------------------------------------------------------+
void drawSwingPoint(string objName,datetime time,double price,int arrCode,
   color clr,int direction,string label) {
   UpdateFontSizes();                                             //--- Update font sizes
   if (ObjectFind(0,objName) < 0) {                               //--- Check no object
      // Draw icon as OBJ_TEXT with Wingdings
      string iconName = objName + "_icon";                        //--- Icon name
      ObjectCreate(0,iconName,OBJ_TEXT,0,time,price);             //--- Create icon
      ObjectSetString(0,iconName,OBJPROP_FONT,"Wingdings");       //--- Set font
      ObjectSetInteger(0,iconName,OBJPROP_FONTSIZE,current_font_size); //--- Set size
      ObjectSetString(0,iconName,OBJPROP_TEXT,CharToString((uchar)arrCode)); //--- Set text
      ObjectSetInteger(0,iconName,OBJPROP_COLOR,clr);             //--- Set color
      if (direction == 1){
         ObjectSetInteger(0,iconName,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);   //--- Set anchor
      }
      else if (direction == -1){
         ObjectSetInteger(0,iconName,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER);   //--- Set anchor
      }
      // Draw text label
      string txt = label;                                         //--- Set text
      string objNameDescr = objName + txt;                        //--- Descr name
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time,price);         //--- Create descr
      ObjectSetString(0,objNameDescr,OBJPROP_FONT,"Arial");       //--- Set font
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);         //--- Set color
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,current_font_size); //--- Set size
      ObjectSetString(0,objNameDescr,OBJPROP_TEXT,txt);           //--- Set text
      if (direction == 1){
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER); //--- Set anchor
      }
      else if (direction == -1){
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); //--- Set anchor
      }
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw break level                                                 |
//+------------------------------------------------------------------+
void drawBreakLevel(string objName,datetime time1,double price1,
   datetime time2,double price2,color clr,int direction) {
   UpdateFontSizes();                                              //--- Update font sizes
   if (ObjectFind(0,objName) < 0) {                                //--- Check no object
      ObjectCreate(0,objName,OBJ_ARROWED_LINE,0,time1,price1,time2,price2); //--- Create arrowed line
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);            //--- Set time1
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);           //--- Set price1
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);            //--- Set time2
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);           //--- Set price2
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);               //--- Set color
      ObjectSetInteger(0,objName,OBJPROP_WIDTH,width);             //--- Set width
      string txt = "CHoCH";                                        //--- Set text
      string objNameDescr = objName + txt;                         //--- Descr name
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time2,price2);        //--- Create descr
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);          //--- Set color
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,current_font_size); //--- Set size
      if (direction > 0) {                                         //--- Check positive
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); //--- Set anchor
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);  //--- Set text
      }
      if (direction < 0) {                                         //--- Check negative
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER); //--- Set anchor
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);  //--- Set text
      }
   }
   ChartRedraw(0);                                                 //--- Redraw chart
}

Für die Hilfsfunktionen implementieren wir zunächst die Funktion „UpdateFontSizes“, um die Textgröße dynamisch an den aktuellen Maßstab des Charts anzupassen und sicherzustellen, dass die Beschriftungen beim Vergrößern oder Verkleinern lesbar bleiben. Wir initialisieren „scale“ auf 0 und rufen den Skalenwert des Charts mit der Funktion ChartGetInteger unter Verwendung von CHART_SCALE ab. Bei Erfolg wird „current_font_size“ als 7 plus 70 % der Skala berechnet, wobei der Wert zwischen 6 und 15 liegt, um Extreme zu vermeiden. Dann wird eine Schleife rückwärts durch alle Objekte im Chart über ObjectsTotal mit -1 für alle Fenster und Typen gezogen, wobei jeder Name mit ObjectName und der Typ über ObjectGetInteger und „OBJPROP_TYPE“ abgeholt wird. Bei allen OBJ_TEXT-Objekten wird die Schriftgröße mit ObjectSetInteger und OBJPROP_FONTSIZE auf „current_font_size“ aktualisiert und das Chart dann mit der Funktion ChartRedraw neu gezeichnet.

Dann erstellen wir einfache Wrapper-Funktionen für den schnellen Zugriff auf die Daten des Balkens: „high“ liefert den Höchstkurs bei einem bestimmten Index über iHigh für das aktuelle Symbol und den aktuellen Zeitraum, „low“ liefert das Tief mit iLow, „close“ liefert den Schlusskurs mit iClose und „time“ liefert die Eröffnungszeit mit der Funktion iTime. Diese vereinfachen den Code für die Schwungerkennung und das Zeichnen ohne wiederholte Funktionsaufrufe. Wir definieren die Funktion „drawSwingPoint“, um erkannte Schwunghochs oder -tiefs zu visualisieren. Zuerst rufen wir „UpdateFontSizes“ auf, um die aktuelle Größe sicherzustellen, dann prüfen wir mit ObjectFind, ob ein Objekt mit dem angegebenen Namen bereits existiert – wenn nicht, erstellen wir ein Wingdings-Symbol als „OBJ_TEXT“ mit einem angehängten „_icon“ Name zum angegebenen Zeitpunkt und Preis, wobei die Schriftart auf „Wingdings“, die Größe auf „current_font_size“, der Text auf das Zeichen aus „arrCode“ via CharToString, die Farbe auf „clr“ und der Anker als rechts-oben für Tiefs (Richtung 1) oder rechts-unten für Hochs (Richtung -1) gesetzt wird.

Wir zeichnen dann die Textbeschriftung selbst mit der angegebenen „Beschriftung“ (z. B. „HH“ oder „LL“) als ein weiteres „OBJ_TEXT“ mit einem angehängten Namen, unter Verwendung der Schriftart Arial, derselben Farbe und Größe, des Beschriftungstextes und des linken oberen oder linken unteren Ankers je nach Richtung. Abschließend wird das Chart neu gezeichnet. Falls die Wingdings-Codes für Sie neu sind, sehen Sie sich unten die bereits bereitgestellten Codes der MQL5 Wingdings an.

MQL5 WINGDINGS

Weiterhin implementieren wir die Funktion „drawBreakLevel“ zur Markierung von CHoCH-Pausen. Wir beginnen mit „UpdateFontSizes“, dann, wenn kein Objekt vorhanden ist, erstellen wir eine OBJ_ARROWED_LINE von time1 zu price1 nach time2 zu price2, wobei wir explizit die Koordinaten über „OBJPROP_TIME“ und OBJPROP_PRICE für beide Punkte setzen und die angegebene Farbe und „width“ anwenden. Wir fügen eine „CHoCH“-Textbeschriftung als OBJ_TEXT bei time2 und price2 ein, mit passender Farbe und „current_font_size“, Verankerung rechts-oben für positive Richtung oder rechts-unten für negative Richtung, mit vorangestelltem Leerzeichen für besseren Abstand. Mit diesen Funktionen ausgestattet, können wir mit der Umsetzung beginnen. Wir setzen die magische Zahl bei der Initialisierung und aktualisieren alle vorhandenen Etiketten.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(magic_number);                  //--- Set magic number
   UpdateFontSizes();                                             //--- Update font sizes
   return(INIT_SUCCEEDED);                                        //--- Return success
}

In der Ereignishandlung von OnInit, das einmal ausgeführt wird, wenn das Programm geladen oder an einen Chart angehängt wird, setzen wir zunächst die „magic_number“ auf „obj_Trade“ mit „SetExpertMagicNumber“, um sicherzustellen, dass alle Handelsgeschäfte mit unserer eindeutigen Kennung versehen werden. Dann rufen wir „UpdateFontSizes“ auf, um die Textgrößen auf der Grundlage des aktuellen Chartmaßstabs zu initialisieren. Abschließend geben wir INIT_SUCCEEDED zurück, um den erfolgreichen Start anzuzeigen. Ein Kinderspiel. Der nächste Schritt ist die Erkennung des Trends anhand von Umkehrpunkten.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static bool isNewBar = false;                                  //--- New bar flag
   int currBars = iBars(_Symbol,_Period);                         //--- Get current bars
   static int prevBars = currBars;                                //--- Previous bars
   if (prevBars == currBars) {                                    //--- Check same bars
      isNewBar = false;                                           //--- Set not new
   } else if (prevBars != currBars) {                             //--- Check new bars
      isNewBar = true;                                            //--- Set new bar
      prevBars = currBars;                                        //--- Update prev
   }
   const int length = ext_bars;                                   //--- Set length
   const int limit = ext_bars;                                    //--- Set limit
   int right_index, left_index;                                   //--- Indices
   bool isSwingHigh = true, isSwingLow = true;                    //--- Swing flags
   static double current_swing_high = -1.0, current_swing_low = -1.0; //--- Current swings
   static datetime swing_high_time = 0, swing_low_time = 0;       //--- Swing times
   static int current_trend = 0;                                  //--- Current trend (1 up, -1 down, 0 unknown)
   int curr_bar = limit;                                          //--- Set curr bar
   if (isNewBar) {                                                //--- Check new bar
      UpdateFontSizes();                                          //--- Update font sizes
      for (int j=1; j<=length; j++) {                             //--- Iterate length
         right_index = curr_bar - j;                              //--- Calc right
         left_index = curr_bar + j;                               //--- Calc left
         if ( (high(curr_bar) <= high(right_index)) || (high(curr_bar) < high(left_index)) ) { //--- Check not high
            isSwingHigh = false;                                  //--- Set not high
         }
         if ( (low(curr_bar) >= low(right_index)) || (low(curr_bar) > low(left_index)) ) { //--- Check not low
            isSwingLow = false;                                   //--- Set not low
         }
      }
      if (isSwingHigh) {                                          //--- Check swing high
         double new_high = high(curr_bar);                        //--- Get new high
         string label = "H";                                      //--- Init label
         color clr = def_clr_up;                                  //--- Set color
         if (current_swing_high > 0) {                            //--- Check existing high
            if (new_high > current_swing_high) {                  //--- Check higher
               label = "HH";                                      //--- Set HH
               current_trend = 1;                                 //--- Set up trend
            } else {                                              //--- Lower
               label = "LH";                                      //--- Set LH
               clr = def_clr_down;                                //--- Set down color
               current_trend = -1;                                //--- Set down trend
            }
         }
         if (prt) {                                               //--- Check print
            Print("SWING HIGH @ BAR INDEX ",curr_bar," of High: ",new_high, " Label: ",label); //--- Log high
         }
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),new_high,object_code,clr,-1,label); //--- Draw high
         current_swing_high = new_high;                           //--- Update high
         swing_high_time = time(curr_bar);                        //--- Update time
      }
      if (isSwingLow) {                                           //--- Check swing low
         double new_low = low(curr_bar);                          //--- Get new low
         string label = "L";                                      //--- Init label
         color clr = def_clr_down;                                //--- Set color
         if (current_swing_low > 0) {                             //--- Check existing low
            if (new_low < current_swing_low) {                    //--- Check lower
               label = "LL";                                      //--- Set LL
               current_trend = -1;                                //--- Set down trend
            } else {                                              //--- Higher
               label = "HL";                                      //--- Set HL
               clr = def_clr_up;                                  //--- Set up color
               current_trend = 1;                                 //--- Set up trend
            }
         }
         if (prt) {                                               //--- Check print
            Print("SWING LOW @ BAR INDEX ",curr_bar," of Low: ",new_low, " Label: ",label); //--- Log low
         }
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),new_low,object_code,clr,1,label); //--- Draw low
         current_swing_low = new_low;                             //--- Update low
         swing_low_time = time(curr_bar);                         //--- Update time
      }
   }
}

In der Funktion OnTick verwenden wir die statischen Variablen „isNewBar“ und „prevBars“, um auf neue Balken zu prüfen: Wir holen die aktuellen Gesamtbalken mit iBars auf dem Symbol und der Periode in „currBars“, dann vergleichen wir mit „prevBars“ – wenn unverändert, setzen wir „isNewBar“ auf false; wenn erhöht, setzen wir „isNewBar“ auf true und aktualisieren „prevBars“ auf „currBars“. Dadurch wird sichergestellt, dass umfangreiche Berechnungen wie die Suche nach Umkehrpunkten nur einmal pro abgeschlossenem Balken ausgeführt werden. Wir setzen „length“ und „limit“ auf die Eingabe „ext_bars“, deklarieren Indizes für Links-/Rechts-Prüfungen, initialisieren die booleschen Flags „isSwingHigh“ und „isSwingLow“ auf true und verwenden statische Globals für die Verfolgung der letzten „current_swing_high“, „current_swing_low“, deren Zeiten und „current_trend“ (1 für up, -1 für down, 0 unknown). Wir legen „curr_bar“ auf „limit“ als Zielbalken für das Scannen fest (normalerweise der früheste im Fenster).

Wenn „isNewBar“ wahr ist, rufen wir „UpdateFontSizes“ auf, um die Darstellung zu aktualisieren, und führen dann eine Schleife von 1 bis „length“ durch, um zu überprüfen, ob „curr_bar“ ein echter Umkehrpunkt ist: Bei Hochs wird geprüft, ob das Hoch über den Hochs des rechten (neueren) und des linken (älteren) Balkens liegt – wenn dies nicht der Fall ist, wird „isSwingHigh“ auf „false“ gesetzt; bei Tiefs wird sichergestellt, dass das Tief unter den umliegenden Tiefs liegt oder „isSwingLow“ auf „false“ gesetzt. Wenn „isSwingHigh“ wahr bleibt, erfassen wir das Hoch in „new_high“, initialisieren das Label als „H“ und die Farbe als „def_clr_up“. Wenn ein vorheriger „current_swing_high“ existiert, vergleichen wir: wenn er höher ist, kennzeichnen wir „HH“ und setzen den „current_trend“ auf 1 (aufwärts); wenn er niedriger ist, beschriften wir „LH“, wechseln die Farbe zu „def_clr_down“ und setzen den Trend auf -1 (abwärts). Wenn „prt“ aktiviert ist, protokollieren wir die Schwungdetails und rufen dann „drawSwingPoint“ mit dem Zeitstring des Balkens, der Zeit, dem Preis, dem „object_code“, der Farbe, der Richtung -1 (für Hochs) und der Kennzeichnung auf. Wir aktualisieren „current_swing_high“ und „swing_high_time“ auf die neuen Werte. Wir spiegeln das Verfahren zur Erkennung von tiefen Umkehrpunkten. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ERKENNUNG VON UMKEHRPUNKTEN

Mit den Umkehrpunkten können wir auf Trendumkehrungen achten, sie auf dem Chart markieren und sie unterwegs handeln. Wir beginnen mit einem Szenario, bei dem sich die Charaktereigenschaften nach oben verändern.

double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get ask
double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get bid
bool buy_break = (trade_mode == PerTick) ? (Bid > current_swing_high) : (Bid > current_swing_high && close(1) > current_swing_high); //--- Check buy break
bool sell_break = (trade_mode == PerTick) ? (Ask < current_swing_low) : (Ask < current_swing_low && close(1) < current_swing_low); //--- Check sell break
if (current_trend == -1 && current_swing_high > 0 && buy_break) { //--- Check up CHoCH
   if (prt) {                                                   //--- Check print
      Print("CHoCH UP NOW");                                    //--- Log up CHoCH
   }
   int swing_H_index = 0;                                       //--- Init index
   for (int i=0; i<=length*2+1000; i++) {                       //--- Iterate search
      double high_sel = high(i);                                //--- Get high
      if (high_sel == current_swing_high) {                     //--- Check match
         swing_H_index = i;                                     //--- Set index
         if (prt) {                                             //--- Check print
            Print("BREAK HIGH @ BAR ",swing_H_index);           //--- Log break
         }
         break;                                                 //--- Break loop
      }
   }
   drawBreakLevel(TimeToString(time(0)),swing_high_time,current_swing_high, 
   time(0+1),current_swing_high,def_clr_up,-1);                 //--- Draw break level
   current_swing_high = -1.0;                                   //--- Reset high
   //--- Open Buy
   double trade_lots = NormalizeDouble(inpLot, 2);              //--- Normalize lots
   double SL_Buy = NormalizeDouble(Bid-sl_pts*r2r_ratio*_Point,_Digits); //--- Calc buy SL
   double TP_Buy = NormalizeDouble(Bid+tp_pts*_Point,_Digits);  //--- Calc buy TP
   if (PositionsTotal() < totalTrades) {                        //--- Check positions limit
      obj_Trade.Buy(trade_lots,_Symbol,Ask,SL_Buy,TP_Buy,"CHoCH Up BUY"); //--- Open buy
   }
   return;                                                      //--- Return
}

Hier übernehmen wir nun die Erkennung des Durchbruchs und die Handelsausführung bei einem Aufwärts-Charakterwechsel. Wir ermitteln den aktuellen Briefkurs mit SymbolInfoDouble unter Verwendung von SYMBOL_ASK und normalisieren ihn auf die Dezimalstellen des Symbols in „Ask“, dasselbe machen wir für Bid mit SYMBOL_BID in „Bid“. Wir definieren „buy_break“ auf der Grundlage von „trade_mode“: bei „PerTick“ prüfen wir, ob „Bid“ über „current_swing_high“ liegt; bei „PerBar“ müssen sowohl „Bid“ als auch der Schlusskurs des vorherigen Balkens (über „close(1)“) über dem Schlusskurs liegen, damit eine Bestätigung beim Schließen erfolgt. Wir setzen „sell_break“ in ähnlicher Weise, aber invertiert. Wenn wir einen Abwärtstrend (“current_trend == -1“), ein gültiges „current_swing_high“ über 0 und „buy_break“ true haben, erkennen wir einen Aufwärts-CHoCH: wenn „prt“ aktiviert ist, protokollieren wir „CHoCH UP NOW“. Dann suchen wir nach dem exakten Balkenindex dieses hohen Umkehrpunkts, indem wir eine Schleife bis zur doppelten Scan-Länge plus 1000 Balken durchlaufen und jeden „high(i)“ mit „current_swing_high“ vergleichen – bei Übereinstimmung speichern wir den Index in „swing_H_index“, protokollieren den Durchbruchsbalken, wenn „prt“, und unterbrechen die Schleife.

Wir zeichnen das Durchbruchsniveau mit „drawBreakLevel“ vom aktuellen Zeitstring, „swing_high_time“ bei „current_swing_high“ bis einen Balken voraus zum gleichen Preis, mit „def_clr_up“ und Richtung -1. Wir setzen „current_swing_high“ auf -1,0 zurück, um für den nächsten Umkehrpunkt bereit zu sein. Für den Handel normalisieren wir Lots auf 2 Dezimalstellen in „trade_lots“, berechnen SL für den Kauf als „Bid“ minus „sl_pts * r2r_ratio * _Point“ normalisiert, TP als „Bid“ plus „tp_pts * _Point“ normalisiert. Was die Zuordnung der Handelsstufen betrifft, so können Sie Ihre eigene Entscheidung treffen. Sie können wählen, ob Sie das Risiko-Ertrags-Verhältnis haben wollen oder statische Werte in Betracht ziehen. Wir haben die beiden Optionen als Eingaben hinzugefügt, und hier entscheiden Sie über Ihr Schicksal, Ihre Entscheidung. Wenn dann die Gesamtpositionen unter „totalTrades“ liegen, eröffnen wir eine Kaufposition mit „obj_Trade.Buy“ unter Verwendung von „trade_lots“, Symbol, „Ask“, SL, TP und dem Kommentar „CHoCH Up BUY“ und kehren dann vorzeitig zurück, um eine weitere Verarbeitung dieses Ticks zu vermeiden. Wenn Sie das System ausführen, erhalten Sie das folgende Ergebnis.

AUFWÄRTS-CHoCH

Aus dem Bild können wir ersehen, dass wir die Veränderung des Aufwärts-Charakters erkennen und darauf reagieren. Wir müssen nur das Gleiche für einen Wechsel zu einem Abwärts-Charakter tun, mit umgekehrter Logik. Siehe unten den Ansatz.

else if (current_trend == 1 && current_swing_low > 0 && sell_break) { //--- Check down CHoCH
   if (prt) {                                                   //--- Check print
      Print("CHoCH DOWN NOW");                                  //--- Log down CHoCH
   }
   int swing_L_index = 0;                                       //--- Init index
   for (int i=0; i<=length*2+1000; i++) {                       //--- Iterate search
      double low_sel = low(i);                                  //--- Get low
      if (low_sel == current_swing_low) {                       //--- Check match
         swing_L_index = i;                                     //--- Set index
         if (prt) {                                             //--- Check print
            Print("BREAK LOW @ BAR ",swing_L_index);            //--- Log break
         }
         break;                                                 //--- Break loop
      }
   }
   drawBreakLevel(TimeToString(time(0)),swing_low_time,current_swing_low, 
   time(0+1),current_swing_low,def_clr_down,1);                 //--- Draw break level
   current_swing_low = -1.0;                                    //--- Reset low
   //--- Open Sell
   double trade_lots = NormalizeDouble(inpLot, 2);              //--- Normalize lots
   double SL_Sell = NormalizeDouble(Ask+sl_pts*r2r_ratio*_Point,_Digits); //--- Calc sell SL
   double TP_Sell = NormalizeDouble(Ask-tp_pts*_Point,_Digits); //--- Calc sell TP
   if (PositionsTotal() < totalTrades) {                        //--- Check positions limit
      obj_Trade.Sell(trade_lots,_Symbol,Bid,SL_Sell,TP_Sell,"CHoCH Down SELL"); //--- Open sell
   }
   return;                                                      //--- Return
}

Hier wenden wir dieselbe Logik wie beim Aufwärtsszenario an, nur mit umgekehrten Bedingungen, um einen Abwärts-Charakterwechsel zu erkennen, ihn im Chart zu markieren und zu handeln. Nach der Kompilierung erhalten wir die folgenden Ergebnisse.

ABWÄRTS-CHoCH

Da wir nun alle Konstellationen für den Charakterwechsel erkannt haben, müssen wir nur noch einen Trailing-Stopp hinzufügen, um die Gewinne zu maximieren, wenn sich der Markt zu unseren Gunsten bewegt, und das ist alles.

//+------------------------------------------------------------------+
//| Apply Points Trailing Stop                                       |
//+------------------------------------------------------------------+
void ApplyPointsTrailing() {
   double point = _Point;                                            //--- Get point value
   for (int i = PositionsTotal() - 1; i >= 0; i--) {                 //--- Iterate positions reverse
      if (PositionGetTicket(i) > 0) {                                //--- Check valid ticket
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { //--- Check symbol and magic
            double sl = PositionGetDouble(POSITION_SL);              //--- Get SL
            double tp = PositionGetDouble(POSITION_TP);              //--- Get TP
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
            ulong ticket = PositionGetInteger(POSITION_TICKET);      //--- Get ticket
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            }
         }
      }
   }
}

//--- Call the function in the tick event handler
// Points trailing can run anytime
if (TrailingType == Trailing_Points && PositionsTotal() > 0) {      //--- Check trailing
   ApplyPointsTrailing();                                           //--- Apply trailing
}

//--- We added this incase we are manually re-scaling the chart
//+------------------------------------------------------------------+
//| Chart event function                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_CHART_CHANGE) {                             //--- Check chart change
      UpdateFontSizes();                                            //--- Update font sizes
   }
}

Was das Trailing betrifft, so implementieren wir die Funktion „ApplyPointsTrailing“, um punktbasierte Trailing-Stops zu verwalten, wenn diese aktiviert sind, und passen den Stop-Loss dynamisch an, wenn sich der Preis günstig entwickelt. Wir beginnen damit, dass wir den Punktwert des Symbols „point“ mit _Point zuweisen. Anschließend werden alle offenen Positionen über PositionsTotal in einer Rückwärtsschleife abgearbeitet, um Änderungen sicher zu verarbeiten, wobei die Gültigkeit jedes Tickets mit der Funktion PositionGetTicket überprüft wird. Für Positionen, die mit unserem Symbol und der „magic_number“ übereinstimmen, rufen wir den Stop-Loss mit PositionGetDouble und „POSITION_SL“, den Take-Profit mit POSITION_TP, den Eröffnungspreis mit „POSITION_PRICE_OPEN“ und das Ticket mit „POSITION_TICKET“ ab. Für Käufe (POSITION_TYPE_BUY) berechnen wir einen neuen Stop-Loss als aktuelles Bid minus „Trailing_Stop_Pips * Punkt“, normalisiert auf Ziffern – wenn enger als der aktuelle SL und der Gewinn „Min_Profit_To_Trail_Pips * Punkt“ überschreitet, aktualisieren wir mit „obj_Trade.PositionModify“. Wir spiegeln dies für den Verkaufsfall wider.

Innerhalb der Tick-Funktion, wenn „TrailingType“ „Trailing_Points“ ist und Positionen per „PositionsTotal“ existieren, rufen wir „ApplyPointsTrailing“ auf, um diese Anpassungen bei jedem Tick für Echtzeitschutz anzuwenden. Die Funktion OnChartEvent reagiert auch auf Ereignisse wie Skalenänderungen: Wenn die ID CHARTEVENT_CHART_CHANGE lautet, wird „UpdateFontSizes“ aufgerufen, um alle Textobjekte zu aktualisieren und sicherzustellen, dass sich die Darstellung nahtlos an die Nutzerinteraktionen anpasst. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

CHoCH GIF

Anhand der Visualisierung können wir den Bruch von Strukturen erkennen, handeln und verwalten und somit unsere Ziele erreichen. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.


Backtests

Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.

Backtest-Grafik:

GRAPH

Bericht des Backtests:

BERICHT


Schlussfolgerung

Zusammenfassend haben wir ein Erkennungssystem für Change of Character (CHoCH) in MQL5 entwickelt, das Balken abtastet, um hohe/tiefe Umkehrpunkte für die Trendbestimmung zu identifizieren und zu kennzeichnen, Handelsgeschäfte bei Durchbrüchen auslöst, die Umkehrungen signalisieren, die Modi Balken oder Ticks, feste Positionskennzeichnungen, Handelsgeschäfts-Limits und optionale punktbasierte Trailing-Stops unterstützt. Das System visualisiert Umkehrpunkte mit farbigen Symbolen/Kennzeichnungen und CHoCH-Durchbrüchen mit Pfeillinien/Text, aktualisiert dynamisch die Schriftgröße bei Maßstabsänderungen und enthält eine Druckprotokollierung zur Fehlersuche.

Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Volatilität der Märkte kann zu Verlusten führen. Gründliche Backtests und sorgfältiges Risikomanagement sind entscheidend, bevor Sie dieses Programm auf den Live-Märkten einsetzen.

Mit dieser Strategie des Change of Character, die Umkehrpunktdurchbrüche für einen Wechsel erkennt, sind Sie für den Handel mit Price-Action-Signalen gerüstet und bereit für weitere Optimierungen auf Ihrer Handelsreise. Viel Spaß beim Handeln!

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20355

Beigefügte Dateien |
CHoCH_EA.mq5 (46.57 KB)
Der MQL5 Standard Library Explorer (Teil 5): Experte für mehrere Signale Der MQL5 Standard Library Explorer (Teil 5): Experte für mehrere Signale
In dieser Sitzung werden wir einen ausgeklügelten Multi-Signal-Expert Advisor unter Verwendung der MQL5-Standardbibliothek erstellen. Dieser Ansatz ermöglicht es uns, integrierte Signale nahtlos mit unserer eigenen Logik zu kombinieren und so einen leistungsstarken und flexiblen Handelsalgorithmus zu entwickeln. Klicken Sie hier, um mehr zu erfahren.
Statistische Arbitrage durch kointegrierte Aktien (Teil 8): Rolling-Windows-Eigenvektor-Vergleich für Portfolio-Rebalancing Statistische Arbitrage durch kointegrierte Aktien (Teil 8): Rolling-Windows-Eigenvektor-Vergleich für Portfolio-Rebalancing
In diesem Artikel wird die Verwendung des Rolling-Windows-Eigenvektor-Vergleichs für die frühzeitige Diagnose von Ungleichgewichten und das Rebalancing des Portfolios in einer statistischen Arbitragestrategie der Rückkehr zum Mittelwert (Mean-Reversion) auf der Grundlage kointegrierter Aktien vorgeschlagen. Sie stellt diese Technik der traditionellen In-Sample/Out-of-Sample-ADF-Validierung gegenüber und zeigt, dass Eigenvektorverschiebungen die Notwendigkeit einer Neugewichtung signalisieren können, selbst wenn die IS/OOS-ADF immer noch eine stationäre Streuung anzeigt. Obwohl die Methode hauptsächlich für die Überwachung des Live-Handels gedacht ist, kommt der Artikel zu dem Schluss, dass der Eigenvektorvergleich auch in das Scoring-System integriert werden könnte – obwohl sein tatsächlicher Beitrag zur Leistung noch getestet werden muss.
Klassische Strategien neu interpretieren (Teil 19): Tiefes Eintauchen in das Kreuzen von gleitenden Durchschnitten Klassische Strategien neu interpretieren (Teil 19): Tiefes Eintauchen in das Kreuzen von gleitenden Durchschnitten
In diesem Artikel wird die klassische Strategie des Kreuzens von gleitenden Durchschnitten wieder aufgegriffen und untersucht, warum sie in bewegten, schnelllebigen Märkten oft scheitert. Es werden fünf alternative Filtermethoden vorgestellt, die die Signalqualität verbessern und schwache oder unrentable Handelsgeschäfte entfernen sollen. Die Diskussion zeigt, wie statistische Modelle lernen und Fehler korrigieren können, die der menschlichen Intuition und traditionellen Regeln entgehen. Die Leser erhalten ein besseres Verständnis dafür, wie man eine veraltete Strategie modernisieren kann und welche Fallstricke es gibt, wenn man sich bei der Finanzmodellierung ausschließlich auf Kennzahlen wie den RMSE verlässt.
Entwicklung des Price Action Analysis Toolkit (Teil 53): Pattern Density Heatmap zur Entdeckung von Unterstützungs- und Widerstandszonen Entwicklung des Price Action Analysis Toolkit (Teil 53): Pattern Density Heatmap zur Entdeckung von Unterstützungs- und Widerstandszonen
In diesem Artikel wird die Pattern Density Heatmap vorgestellt, ein Zuordnungsinstrument des Preis-Aktions-Mappings, das wiederholte Erkennungen von Kerzenmustern in statistisch signifikante Unterstützungs- und Widerstandszonen umwandelt. Anstatt jedes Signal isoliert zu behandeln, fasst der EA die Erkennungen in festen Preisbereichen (bins) zusammen, bewertet ihre Dichte mit einer optionalen Aktualitätsgewichtung und bestätigt die Werte anhand von Daten mit höherem Zeitrahmen. Die sich daraus ergebende Heatmap zeigt, wo der Markt in der Vergangenheit reagiert hat – Werte, die proaktiv für das Handels-Timing, das Risikomanagement und das Vertrauen in die Strategie für jeden Handelsstil genutzt werden können.