English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 38): Versteckter RSI-Divergenzhandel mit Steigungswinkel-Filtern

Automatisieren von Handelsstrategien in MQL5 (Teil 38): Versteckter RSI-Divergenzhandel mit Steigungswinkel-Filtern

MetaTrader 5Handel |
28 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorangegangenen Artikel (Teil 37) haben wir ein Regular RSI Divergence Convergence System in MetaQuotes Language 5 (MQL5) entwickelt, das regelmäßige Auf- und Abwärts-Divergenzen zwischen Kursschwankungen und Relative Strength Index (RSI)-Werten erkennt, Handelsgeschäfte auf Signale mit optionalen Risikokontrollen ausführt und Visualisierungen auf dem Chart für eine verbesserte Analyse bietet. In Teil 38 entwickeln wir ein Handelssystem der versteckten RSI-Divergenzen mit Filtern der Steigungswinkel.

Dieses System identifiziert versteckte Aufwärts- und Abwärtsdivergenzen mit Hilfe von Umkehrpunkte, wendet saubere Kontrollen mit Balkenbereiche und Toleranzen an, filtert Signale über anpassbare Steigungswinkel auf Preis- und RSI-Linien, führt Handelsgeschäfte mit Risikomanagement aus und beinhaltet visuelle Marker mit Winkelanzeigen auf Charts. Wir werden die folgenden Themen behandeln:

  1. Verstehen der versteckte RSI-Divergenzstrategie
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie eine funktionierende MQL5-Strategie für den Handel mit versteckten RSI-Divergenzen, die Sie anpassen können – legen wir los!



Verstehen der versteckte RSI-Divergenzstrategie

Die Strategie versteckter RSI-Divergenzen konzentriert sich auf die Identifizierung von Gelegenheiten zur Trendfortsetzung, indem sie spezifische Diskrepanzen zwischen Kursschwankungen und dem Relative Strength Index (RSI) Oszillator aufspürt, der die zugrunde liegende Momentum-Stärke in laufenden Trends hervorhebt. Bei einer versteckten Aufwärts-Divergenz bildet der Kurs ein höheres Tief, während der RSI ein niedrigeres Tief bildet, was darauf hindeutet, dass die Rücksetzer nach unten schwächer werden und der Aufwärtstrend wieder aufgenommen werden kann. Bei einer versteckten Abwärts-Divergenz bildet der Kurs ein niedrigeres Hoch, der RSI zeigt jedoch ein höheres Hoch an, was darauf hindeutet, dass die Aufwärtskorrekturen nachlassen und der Abwärtstrend anhalten könnte.

Wir beabsichtigen, die Zuverlässigkeit zu erhöhen, indem wir Divergenzen mit Steigungswinkeln sowohl auf den Preis- als auch auf den RSI-Linien filtern, um eine ausreichende Steilheit oder Flachheit zu bestätigen, Toleranzschwellen für saubere Muster ohne Durchbrüche anzuwenden und dementsprechend Handelsgeschäfte einzugehen – Käufe bei versteckten Aufwärts-Signalen oder Verkäufe bei versteckten Abwärts-Signalen – mit definierten Risikoparametern wie Stopps, Gewinne und Trailing-Mechanismen. Wenn wir uns diese Elemente zunutze machen, können wir in etablierten Trends mit hoher Wahrscheinlichkeit Fortsetzungsgeschäfte tätigen. Sehen Sie sich unten die verschiedenen Möglichkeiten an, die wir haben.

Versteckte Aufwärts-Divergenzeinstellung:

VERSTECKTE AUFWÄRTS-RSI-DIVERGENZ

Versteckte Abwärts-Divergenzeinstellung:

VERSTECKTE ABWÄRTS-RSI-DIVERGENZ

Unser Plan ist es, hohe und tiefe Umkehrpunkte mit Bestätigungsstärke zu erkennen, versteckte Divergenzen durch saubere Checks innerhalb festgelegter Balkenbereiche und Toleranzen zu validieren, optionale Steigungswinkel-Filter auf Preis und RSI für die Signalqualität anzuwenden, automatisierte Handelsgeschäfte mit anpassbarer Losgröße und Risikokontrolle auszuführen und visuelle Hilfen wie farbige Linien und Etiketten mit Winkelanzeigen auf beiden Charts bereitzustellen, um ein effektives System für den Handel mit versteckten Divergenzen aufzubauen. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

GESAMTPLAN


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 das erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                         RSI Hidden Divergence Convergence 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"
#property strict

#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "RSI Settings"
input int               RSI_Period   = 14;                // RSI Period
input ENUM_APPLIED_PRICE RSI_Applied = PRICE_CLOSE;       // RSI Applied Price
input group "Swing Settings"
input int   Swing_Strength    = 5;                        // Bars to confirm swing high/low
input int   Min_Bars_Between  = 5;                        // Min bars between swings for divergence
input int   Max_Bars_Between  = 50;                       // Max bars between swings for divergence
input double Tolerance        = 0.1;                      // Tolerance for clean divergence check
input group "Price Divergence Filter"
input bool   Use_Price_Slope_Filter   = false;            // Use Slope Angle Filter for Price
input double Price_Min_Slope_Degrees  = 10.0;             // Minimum Slope Angle in Degrees for Price (0 to disable min)
input double Price_Max_Slope_Degrees  = 80.0;             // Maximum Slope Angle in Degrees for Price (90 to disable max)
input group "RSI Divergence Filter"
input bool   Use_RSI_Slope_Filter    = true;              // Use Slope Angle Filter for RSI
input double RSI_Min_Slope_Degrees   = 1.0;               // Minimum Slope Angle in Degrees for RSI (0 to disable min)
input double RSI_Max_Slope_Degrees   = 89.0;              // Maximum Slope Angle in Degrees for RSI (90 to disable max)
input group "Trade Settings"
input double Lot_Size      = 0.01;                        // Fixed Lot Size
input int    Magic_Number  = 123456789;                   // Magic Number
input double SL_Pips       = 300.0;                       // Stop Loss in Pips (0 to disable)
input double TP_Pips       = 300.0;                       // Take Profit in Pips (0 to disable)
input group "Trailing Stop Settings"
input bool   Enable_Trailing_Stop     = true;             // Enable Trailing Stop
input double Trailing_Stop_Pips       = 30.0;             // Trailing Stop in Pips
input double Min_Profit_To_Trail_Pips = 50.0;             // Minimum Profit to Start Trailing in Pips
input group "Visualization"
input bool         Mark_Swings_On_Price = true;           // Mark Swing Points on Price Chart
input bool         Mark_Swings_On_RSI   = true;           // Mark Swing Points on RSI
input color        Bull_Color           = clrGreen;       // Bullish Divergence Color
input color        Bear_Color           = clrRed;         // Bearish Divergence Color
input color        Swing_High_Color     = clrRed;         // Color for Swing High Labels
input color        Swing_Low_Color      = clrGreen;       // Color for Swing Low Labels
input int          Line_Width           = 2;              // Divergence Line Width
input ENUM_LINE_STYLE Line_Style        = STYLE_SOLID;    // Divergence Line Style
input int          Font_Size            = 8;              // Swing Point Font Size

//+------------------------------------------------------------------+
//| Indicator Handles and Trade Object                               |
//+------------------------------------------------------------------+

int    RSI_Handle = INVALID_HANDLE;                               //--- RSI indicator handle
CTrade obj_Trade;                                                 //--- Trade object for position management

Wir beginnen mit der Einbindung der Bibliothek „Trade“ mit „#include <Trade\Trade.mqh>“, um integrierte Funktionen für die Verwaltung von Positionen und Aufträgen zu ermöglichen. Als Nächstes definieren wir verschiedene Eingabeparameter, die nach Kategorien gruppiert sind und vom Nutzer angepasst werden können. Unter „RSI-Einstellungen“ setzen wir „RSI_Period“ auf 14 für die RSI-Berechnungsdauer und „RSI_Applied“ auf PRICE_CLOSE, um ihn mit Schlusskursen zu rechnen. Dies sind nur die Standardeinstellungen. Sie können sie nach Belieben anpassen. Unter „Swing Settings“ wird „Swing_Strength“ auf 5 gesetzt, um die für die Bestätigung von hohe und tiefe Umkehrpunkte benötigten Balken zu bestimmen, während „Min_Bars_Between“ und „Max_Bars_Between“ die Divergenzerkennung auf 5 bis 50 Balken begrenzen und „Tolerance“ mit 0,1 einen kleinen Puffer für saubere Divergenzprüfungen ermöglicht, genau wie bei der regulären Version.

Für die Gruppe „Price Divergence Filter“ ist „Use_Price_Slope_Filter“ standardmäßig auf false eingestellt, um optional eine winkelbasierte Filterung zu aktivieren, wobei „Price_Min_Slope_Degrees“ auf 10,0 und „Price_Max_Slope_Degrees“ auf 80,0 eingestellt ist, um akzeptable Steigungsbereiche in Grad zu definieren (min deaktiviert bei 0, max bei 90). In ähnlicher Weise hat der „RSI Divergence Filter“ „Use_RSI_Slope_Filter“ auf true, mit „RSI_Min_Slope_Degrees“ auf 1.0 und „RSI_Max_Slope_Degrees“ auf 89.0 für die Steigungen der RSI-Linien. Der Rest der Parameter ist identisch mit der vorherigen regulären Version, mit der Ausnahme, dass wir die Option der Gradfilterung hinzugefügt haben.

Schließlich deklarieren wir globale Variablen: „RSI_Handle“, initialisiert auf INVALID_HANDLE, um die RSI-Indikatorreferenz zu speichern, und „obj_Trade“ als CTrade-Instanz für die Abwicklung von Handelsoperationen. Nun müssen wir die globalen Variablen für die Schwingungspunkte definieren und sie initialisieren.

//+------------------------------------------------------------------+
//| Swing Variables                                                  |
//+------------------------------------------------------------------+
double   Last_High_Price = 0.0;                                   //--- Last swing high price
datetime Last_High_Time  = 0;                                     //--- Last swing high time
double   Prev_High_Price = 0.0;                                   //--- Previous swing high price
datetime Prev_High_Time  = 0;                                     //--- Previous swing high time
double   Last_Low_Price  = 0.0;                                   //--- Last swing low price
datetime Last_Low_Time   = 0;                                     //--- Last swing low time
double   Prev_Low_Price  = 0.0;                                   //--- Previous swing low price
datetime Prev_Low_Time   = 0;                                     //--- Previous swing low time
double   Last_High_RSI   = 0.0;                                   //--- Last swing high RSI value
double   Prev_High_RSI   = 0.0;                                   //--- Previous swing high RSI value
double   Last_Low_RSI    = 0.0;                                   //--- Last swing low RSI value
double   Prev_Low_RSI    = 0.0;                                   //--- Previous swing low RSI value

Wir fahren fort, indem wir eine Reihe von globalen Variablen im Abschnitt „Swing Variables“ deklarieren, um Details über die letzten und vorherigen Umkehrpunkte für Hochs und Tiefs zu speichern. Dazu gehören „Last_High_Price“ und „Last_High_Time“ für den Preis und den Zeitstempel des jüngsten hohe Umkehrpunkte sowie „Prev_High_Price“ und „Prev_High_Time“ für das davor. Ähnlich verhält es sich bei den tiefen Umkehrpunkte: „Last_Low_Price“, „Last_Low_Time“, „Prev_Low_Price“, und „Prev_Low_Time“. Um diese mit den Indikatordaten zu verknüpfen, fügen wir „Last_High_RSI“ und „Prev_High_RSI“ für die RSI-Werte an diesen Hochpunkten sowie „Last_Low_RSI“ und „Prev_Low_RSI“ für die Tiefststände hinzu. Alle werden auf Null initialisiert, damit wir sie während der Laufzeit zur Erkennung von Divergenzen dynamisch aktualisieren und vergleichen können. Damit sind wir gut gerüstet. Wir müssen nur das Programm initialisieren, insbesondere den RSI-Indikator, und sicherstellen, dass wir auf sein Fenster verweisen können, damit wir später darauf zeichnen können, aber diesmal mit einer Graddarstellung.

//+------------------------------------------------------------------+
//| Expert Initialization Function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   RSI_Handle = iRSI(_Symbol, _Period, RSI_Period, RSI_Applied);  //--- Create RSI indicator handle
   if (RSI_Handle == INVALID_HANDLE) {                            //--- Check if RSI creation failed
      Print("Failed to create RSI indicator");                    //--- Log error
      return(INIT_FAILED);                                        //--- Return initialization failure
   }
   long chart_id = ChartID();                                     //--- Get current chart ID
   string rsi_name = "RSI(" + IntegerToString(RSI_Period) + ")";  //--- Generate RSI indicator name
   int rsi_subwin = ChartWindowFind(chart_id, rsi_name);          //--- Find RSI subwindow
   if (rsi_subwin == -1) {                                        //--- Check if RSI subwindow not found
      if (!ChartIndicatorAdd(chart_id, 1, RSI_Handle)) {          //--- Add RSI to chart subwindow
         Print("Failed to add RSI indicator to chart");           //--- Log error
      }
   }
   obj_Trade.SetExpertMagicNumber(Magic_Number);                  //--- Set magic number for trade object
   Print("RSI Hidden Divergence EA initialized");                 //--- Log initialization success
   return(INIT_SUCCEEDED);                                        //--- Return initialization success
}

In OnInit erstellen wir zunächst das RSI-Indikator-Handle mit der Funktion iRSI und übergeben das aktuelle Symbol, den Zeitrahmen, die RSI-Periode und den angewandten Preistyp, um den Oszillator einzurichten. Anschließend wird geprüft, ob „RSI_Handle“ INVALID_HANDLE ist; ist dies der Fall, wird mit Print eine Fehlermeldung protokolliert und „INIT_FAILED“ zurückgegeben, um die Initialisierung zu beenden. Als Nächstes rufen wir die aktuelle Chart-ID mit ChartID ab und konstruieren den RSI-Indikatornamen als String, der „RSI(“ mit der über die Funktion IntegerToString konvertierten Periode kombiniert.

Wir versuchen, das RSI-Subfenster mithilfe von ChartWindowFind mit der Chart-ID und dem Namen zu finden; wenn es nicht gefunden wird (rsi_subwin == -1), fügen wir den Indikator über ChartIndicatorAdd zum Subfenster 1 hinzu und protokollieren einen Fehler, wenn dies nicht gelingt. Danach konfigurieren wir das Handelsobjekt, indem wir „obj_Trade.SetExpertMagicNumber(Magic_Number)“ aufrufen, um unser eindeutiges Kennzeichen mit den Handelsgeschäften zu verknüpfen. Schließlich geben wir eine Erfolgsmeldung aus und geben INIT_SUCCEEDED zurück, um die ordnungsgemäße Einrichtung zu bestätigen. Nach der Initialisierung erhalten wir das folgende Ergebnis.

ANFANGSLAUF

Nun, da wir das Programm initialisieren und den Indikator zu seinem Unterfenster hinzufügen können, auf das wir Bezug nehmen können, müssen wir die Umkehrpunkte auf dem Chart überprüfen und einzeichnen, damit wir sie zur Identifizierung der Divergenzen oder Konvergenzen verwenden können. Lassen Sie uns dafür einige Hilfsfunktionen definieren.

//+------------------------------------------------------------------+
//| Check for Swing High                                             |
//+------------------------------------------------------------------+
bool CheckSwingHigh(int bar, double& highs[]) {
   if (bar < Swing_Strength || bar + Swing_Strength >= ArraySize(highs)) return false; //--- Return false if bar index out of range for swing strength
   double current = highs[bar];                                   //--- Get current high price
   for (int i = 1; i <= Swing_Strength; i++) {                    //--- Iterate through adjacent bars
      if (highs[bar - i] >= current || highs[bar + i] >= current) return false; //--- Return false if not a swing high
   }
   return true;                                                   //--- Return true if swing high
}
//+------------------------------------------------------------------+
//| Check for Swing Low                                              |
//+------------------------------------------------------------------+
bool CheckSwingLow(int bar, double& lows[]) {
   if (bar < Swing_Strength || bar + Swing_Strength >= ArraySize(lows)) return false; //--- Return false if bar index out of range for swing strength
   double current = lows[bar];                                    //--- Get current low price
   for (int i = 1; i <= Swing_Strength; i++) {                    //--- Iterate through adjacent bars
      if (lows[bar - i] <= current || lows[bar + i] <= current) return false; //--- Return false if not a swing low
   }
   return true;                                                   //--- Return true if swing low
}

Wir definieren die Funktion „CheckSwingHigh“, die einen ganzzahligen Balkenindex und einen Verweis auf ein Array von Höchstkursen annimmt, um festzustellen, ob ein hoher Umkehrpunkt an diesem Balken existiert. Sie prüft zunächst, ob der Balken auf der Grundlage von „Swing_Strength“ außerhalb der Grenzen liegt, indem sie die Funktion ArraySize verwendet, um Indexfehler zu vermeiden, und gibt in diesem Fall false zurück. Anschließend wird der aktuelle Höchstkurs abgerufen und eine Schleife von 1 bis „Swing_Strength“ durchlaufen, wobei überprüft wird, ob keine links oder rechts angrenzenden Balken Höchstkurse aufweisen, die größer oder gleich dem aktuellen sind; ist dies der Fall, wird false zurückgegeben, andernfalls true, um ein hoher Umkehrpunkt zu bestätigen.

In ähnlicher Weise erstellen wir die Funktion „CheckSwingLow“ mit der gleichen Struktur, aber für niedrige Preise, wobei wir sicherstellen, dass der Balken im Bereich liegt, den aktuellen Tiefstwert ermitteln und in der Schleife prüfen, ob keine benachbarten Balken Tiefstwerte aufweisen, die kleiner oder gleich dem aktuellen sind, und nur dann true zurückgeben, wenn es sich um einen gültigen Tiefstwert handelt.

//+------------------------------------------------------------------+
//| Check for Clean Divergence                                       |
//+------------------------------------------------------------------+
bool CleanDivergence(double rsi1, double rsi2, int shift1, int shift2, double& rsi_data[], bool bearish) {
   if (shift1 <= shift2) return false;                            //--- Return false if shifts invalid
   for (int b = shift2 + 1; b < shift1; b++) {                    //--- Iterate between shifts
      double interp_factor = (double)(b - shift2) / (shift1 - shift2); //--- Calculate interpolation factor
      double interp_rsi = rsi2 + interp_factor * (rsi1 - rsi2);   //--- Calculate interpolated RSI
      if (bearish) {                                              //--- Check for bearish divergence
         if (rsi_data[b] > interp_rsi + Tolerance) return false;  //--- Return false if RSI exceeds line plus tolerance
      } else {                                                    //--- Check for bullish divergence
         if (rsi_data[b] < interp_rsi - Tolerance) return false;  //--- Return false if RSI below line minus tolerance
      }
   }
   return true;                                                   //--- Return true if divergence is clean
}
//+------------------------------------------------------------------+
//| Calculate Visual Angle                                           |
//+------------------------------------------------------------------+
double CalculateVisualAngle(long chart_id, int sub_window, datetime time1, double val1, datetime time2, double val2) {
   int x1 = 0, y1 = 0, x2 = 0, y2 = 0;                            //--- Initialize pixel coordinates
   bool ok1 = ChartTimePriceToXY(chart_id, sub_window, time1, val1, x1, y1); //--- Convert first point to XY
   bool ok2 = ChartTimePriceToXY(chart_id, sub_window, time2, val2, x2, y2); //--- Convert second point to XY
   if (!ok1 || !ok2 || x1 == x2) return 0.0;                      //--- Return zero if conversion failed or same x
   double dx = (double)(x2 - x1);                                 //--- Calculate delta x
   double dy = (double)(y2 - y1);                                 //--- Calculate delta y
   if (dx == 0.0) return (dy > 0.0 ? -90.0 : 90.0);               //--- Handle vertical line case
   double angle = MathArctan(-dy / dx) * 180.0 / M_PI;            //--- Calculate angle in degrees
   return MathAbs(angle);                                         //--- Return absolute angle
}

Wir implementieren die Funktion „CleanDivergence“, um zu überprüfen, ob die Divergenzlinie zwischen zwei RSI-Punkten nicht von RSI-Zwischenwerten überschritten wird, um ein „sauberes“ Muster ohne Verletzungen zu gewährleisten. Sie akzeptiert Parameter wie „rsi1“ und „rsi2“ für die RSI-Werte bei den Swings, „shift1“ und „shift2“ für ihre Balkenverschiebungen (wobei „shift1“ erwartungsgemäß größer ist), einen Verweis auf das Array „rsi_data“ und einen booleschen Wert zur Unterscheidung der Divergenzart. Zunächst werden die Verschiebungen validiert, wobei false zurückgegeben wird, wenn sie ungültig sind. Dann wird eine Schleife durch die Balken zwischen „shift2 + 1“ und „shift1 – 1“ gezogen, wobei ein „interp_factor“ als normalisierte Position und ein „interp_rsi“ als linear interpolierter Wert zwischen „rsi1“ und „rsi2“ berechnet wird. Im Falle eines Abwärtstrends prüfen wir, ob „rsi_data[b]“ den Wert „interp_rsi + Toleranz“ überschreitet und geben bei Überschreitung „false“ zurück; im Falle eines Aufwärtstrends stellen wir sicher, dass „rsi_data[b]“ nicht unter „interp_rsi – Toleranz“ fällt. Wenn alle Prüfungen erfolgreich sind, geben wir „true“ zurück, um zu bestätigen, dass die Divergenz sauber und zuverlässig für die Signalisierung ist.

Als Nächstes definieren wir die Funktion „CalculateVisualAngle“, um den visuellen Steigungswinkel in Grad zwischen zwei Punkten auf dem Chart zu berechnen, was bei der Divergenzfilterung hilfreich ist. Er benötigt „chart_id“ für die Kennung des Charts, „sub_window“ zur Angabe des Haupt- oder Unterfensters sowie „time1“, „val1“, „time2“ und „val2“ für die Koordinaten. Wir initialisieren die Pixelvariablen „x1“, „y1“, „x2“, „y2“ auf Null, konvertieren dann den Zeit und Preis des Charts in XY-Pixel mit ChartTimePriceToXY für beide Punkte und speichern den Erfolg in „ok1“ und „ok2“. Wenn die Umwandlung fehlschlägt oder die x-Koordinaten übereinstimmen, wird 0,0 zurückgegeben; andernfalls wird „dx“ als „x2-x1“ und „dy“ als „y2-y1“ berechnet. Bei vertikalen Linien, bei denen „dx“ gleich Null ist, wird -90,0 oder 90,0 zurückgegeben, je nach Vorzeichen von „dy“; andernfalls wird der „Winkel“ mit MathArctan auf „-dy / dx“ berechnet, durch Multiplikation mit 180,0 durch M_PI in Grad umgewandelt und sein absoluter Wert über MathAbs für ein positives Steigungsmaß zurückgegeben. Wir haben alle Funktionen, die uns bei der Identifizierung der Abweichungen helfen, und können diese in OnTick implementieren. Wir beginnen mit der versteckten Abwärts-Divergenz.

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime last_time = 0;                                 //--- Store last processed time
   datetime current_time = iTime(_Symbol, _Period, 0);            //--- Get current bar time
   if (current_time == last_time) return;                         //--- Exit if bar not new
   last_time = current_time;                                      //--- Update last time
   int data_size = 200;                                           //--- Set data size for analysis
   double high_data[], low_data[], rsi_data[];                    //--- Declare arrays for high, low, RSI data
   datetime time_data[];                                          //--- Declare array for time data
   CopyHigh(_Symbol, _Period, 0, data_size, high_data);           //--- Copy high prices
   CopyLow(_Symbol, _Period, 0, data_size, low_data);             //--- Copy low prices
   CopyTime(_Symbol, _Period, 0, data_size, time_data);           //--- Copy time values
   CopyBuffer(RSI_Handle, 0, 0, data_size, rsi_data);             //--- Copy RSI values
   ArraySetAsSeries(high_data, true);                             //--- Set high data as series
   ArraySetAsSeries(low_data, true);                              //--- Set low data as series
   ArraySetAsSeries(time_data, true);                             //--- Set time data as series
   ArraySetAsSeries(rsi_data, true);                              //--- Set RSI data as series
   long chart_id = ChartID();                                     //--- Get current chart ID
   int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Find RSI subwindow
   // Find latest swing high
   int last_high_bar = -1, prev_high_bar = -1;                    //--- Initialize swing high bars
   for (int b = 1; b < data_size - Swing_Strength; b++) {         //--- Iterate through bars
      if (CheckSwingHigh(b, high_data)) {                         //--- Check for swing high
         if (last_high_bar == -1) {                               //--- Check if first swing high
            last_high_bar = b;                                    //--- Set last high bar
         } else {                                                 //--- Second swing high found
            prev_high_bar = b;                                    //--- Set previous high bar
            break;                                                //--- Exit loop
         }
      }
   }
   if (last_high_bar > 0 && time_data[last_high_bar] > Last_High_Time) { //--- Check new swing high
      Prev_High_Price = Last_High_Price;                          //--- Update previous high price
      Prev_High_Time = Last_High_Time;                            //--- Update previous high time
      Last_High_Price = high_data[last_high_bar];                 //--- Set last high price
      Last_High_Time = time_data[last_high_bar];                  //--- Set last high time
      Prev_High_RSI = Last_High_RSI;                              //--- Update previous high RSI
      Last_High_RSI = rsi_data[last_high_bar];                    //--- Set last high RSI
      string high_type = "H";                                     //--- Set default high type
      if (Prev_High_Price > 0.0) {                                //--- Check if previous high exists
         high_type = (Last_High_Price > Prev_High_Price) ? "HH" : "LH"; //--- Set high type
      }
      bool lower_high = Last_High_Price < Prev_High_Price;        //--- Check for lower high
      bool higher_rsi_high = Last_High_RSI > Prev_High_RSI;       //--- Check for higher RSI high
      int bars_diff = prev_high_bar - last_high_bar;              //--- Calculate bars between highs
      bool hidden_bear_div = false;                               //--- Initialize hidden bearish divergence flag
      double price_angle = 0.0;                                   //--- Initialize price angle
      double rsi_angle = 0.0;                                     //--- Initialize RSI angle
      if (Prev_High_Price > 0.0 && lower_high && higher_rsi_high && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check hidden bearish divergence conditions
         if (CleanDivergence(Prev_High_RSI, Last_High_RSI, prev_high_bar, last_high_bar, rsi_data, true)) { //--- Check clean divergence
            price_angle = CalculateVisualAngle(chart_id, 0, Prev_High_Time, Prev_High_Price, Last_High_Time, Last_High_Price); //--- Calculate price angle
            rsi_angle = CalculateVisualAngle(chart_id, rsi_window, Prev_High_Time, Prev_High_RSI, Last_High_Time, Last_High_RSI); //--- Calculate RSI angle
            if ((!Use_Price_Slope_Filter || (price_angle >= Price_Min_Slope_Degrees && price_angle <= Price_Max_Slope_Degrees)) &&
                (!Use_RSI_Slope_Filter || (rsi_angle >= RSI_Min_Slope_Degrees && rsi_angle <= RSI_Max_Slope_Degrees))) { //--- Check slope filters
               hidden_bear_div = true;                               //--- Set hidden bearish divergence flag
               // Draw divergence lines
               string line_name = "DivLine_HiddenBear_" + TimeToString(Last_High_Time); //--- Set divergence line name
               ObjectCreate(chart_id, line_name, OBJ_TREND, 0, Prev_High_Time, Prev_High_Price, Last_High_Time, Last_High_Price); //--- Create trend line for price divergence
               ObjectSetInteger(chart_id, line_name, OBJPROP_COLOR, Bear_Color); //--- Set line color
               ObjectSetInteger(chart_id, line_name, OBJPROP_WIDTH, Line_Width); //--- Set line width
               ObjectSetInteger(chart_id, line_name, OBJPROP_STYLE, Line_Style); //--- Set line style
               ObjectSetInteger(chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
               ObjectSetInteger(chart_id, line_name, OBJPROP_BACK, false); //--- Set to foreground
               if (rsi_window != -1) {                               //--- Check if RSI subwindow found
                  string rsi_line = "DivLine_RSI_HiddenBear_" + TimeToString(Last_High_Time); //--- Set RSI divergence line name
                  ObjectCreate(chart_id, rsi_line, OBJ_TREND, rsi_window, Prev_High_Time, Prev_High_RSI, Last_High_Time, Last_High_RSI); //--- Create trend line for RSI divergence
                  ObjectSetInteger(chart_id, rsi_line, OBJPROP_COLOR, Bear_Color); //--- Set line color
                  ObjectSetInteger(chart_id, rsi_line, OBJPROP_WIDTH, Line_Width); //--- Set line width
                  ObjectSetInteger(chart_id, rsi_line, OBJPROP_STYLE, Line_Style); //--- Set line style
                  ObjectSetInteger(chart_id, rsi_line, OBJPROP_RAY, false); //--- Disable ray
                  ObjectSetInteger(chart_id, rsi_line, OBJPROP_BACK, false); //--- Set to foreground
               }
            }
         }
      }
      // Draw swing label on price if enabled
      if (Mark_Swings_On_Price) {                                    //--- Check if marking swings on price enabled
         string swing_name = "SwingHigh_" + TimeToString(Last_High_Time); //--- Set swing high label name
         if (ObjectFind(chart_id, swing_name) < 0) {                 //--- Check if label exists
            string high_label_text = " " + high_type + (hidden_bear_div ? " Hidden Bear Div " + DoubleToString(price_angle, 1) + "°" : ""); //--- Set label text with angle if divergence
            ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_High_Time, Last_High_Price); //--- Create swing high label
            ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, high_label_text); //--- Set label text
            ObjectSetInteger(chart_id, swing_name, OBJPROP_COLOR, Swing_High_Color); //--- Set label color
            ObjectSetInteger(chart_id, swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Set label anchor
            ObjectSetInteger(chart_id, swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Set label font size
         }
      }
      // Draw corresponding swing label on RSI if enabled
      if (Mark_Swings_On_RSI && rsi_window != -1) {                  //--- Check if marking swings on RSI enabled and subwindow found
         string rsi_swing_name = "RSI_SwingHigh_" + TimeToString(Last_High_Time); //--- Set RSI swing high label name
         if (ObjectFind(chart_id, rsi_swing_name) < 0) {             //--- Check if label exists
            string high_label_text_rsi = " " + high_type + (hidden_bear_div ? " Hidden Bear Div " + DoubleToString(rsi_angle, 1) + "°" : ""); //--- Set label text with RSI angle if divergence
            ObjectCreate(chart_id, rsi_swing_name, OBJ_TEXT, rsi_window, Last_High_Time, Last_High_RSI); //--- Create RSI swing high label
            ObjectSetString(chart_id, rsi_swing_name, OBJPROP_TEXT, high_label_text_rsi); //--- Set label text
            ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_COLOR, Swing_High_Color); //--- Set label color
            ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Set label anchor
            ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Set label font size
         }
      }
      ChartRedraw(chart_id);                                         //--- Redraw chart
   }
}

In der Funktion OnTick, die bei jedem neuen Tick ausgeführt wird, verwenden wir zunächst eine statische Variable „last_time“, um den Zeitstempel des zuletzt verarbeiteten Balkens zu verfolgen, indem wir die aktuelle Balkenzeit mit iTime über _Symbol, _Period und 0 abrufen, bei unveränderten Werten vorzeitig beenden, um redundante Berechnungen zu vermeiden, und dann „last_time“ aktualisieren. Wir definieren eine „data_size“ von 200 Bars für die Analyse und deklarieren Arrays für „high_data“, „low_data“, „rsi_data“, und „time_data“und füllen sie mit CopyHigh, CopyLow, CopyTime und CopyBuffer, um die jüngsten Höchst- und Tiefstkurse, Zeitstempel und RSI-Werte von „RSI_Handle“ zu erhalten.

Wir setzen diese Arrays als Serien mit „ArraySetAsSeries“ für die umgekehrte Indizierung (jüngste zuerst) und erhalten die „chart_id“ über „ChartID“, zusammen mit dem Auffinden des RSI-Subwindow-Index mit ChartWindowFind basierend auf dem Perioden-formatierten Namen. Um die letzten hohen Umkehrpunkte zu identifizieren, initialisieren wir „last_high_bar“ und „prev_high_bar“ auf -1, führen eine Schleife von 1 bis „data_size – Swing_Strength“ durch und rufen „CheckSwingHigh“ auf, um die beiden letzten gültigen hohen Umkehrpunkte zu finden, setzen sie und brechen nach dem zweiten.

Wenn durch den Vergleich von „time_data[last_high_bar]“ mit „Last_High_Time“ ein neuer hoher Umkehrpunkt festgestellt wird, verschieben wir die Variablen für vorherige Hochs, um die aktuellen letzten Werte zu halten, aktualisieren „Last_High_Price“, „Last_High_Time“ und „Last_High_RSI“ aus den Arrays und weisen „high_type“ standardmäßig den Wert „H“ zu, oder „HH“ für ein höheres Hoch oder „LH“ für ein niedrigeres Hoch, wenn ein vorheriges existiert. Dann bewerten wir die versteckte Abwärts-Divergenz: Wenn „Prev_High_Price“ gesetzt ist, der Preis ein niedrigeres Hoch, der RSI ein höheres Hoch zeigt und die Balkendifferenz (“prev_high_bar – last_high_bar“) innerhalb von „Min_Bars_Between“ und „Max_Bars_Between“ liegt, rufen wir „CleanDivergence“ auf mit den vorherigen und letzten RSI-Hochs, ihren Verschiebungen, „rsi_data“, und true für fallend.

Wenn sauber, berechnen wir „price_angle“ mit „CalculateVisualAngle“ auf dem Hauptchart (Unterfenster 0) mit hohen Zeiten und Preisen und „rsi_angle“ auf dem RSI-Unterfenster mit hohen Zeiten und RSI-Werten. Wir überprüfen die Steigungsfilter: wenn kein Preisfilter verwendet wird oder „price_angle“ zwischen „Price_Min_Slope_Degrees“ und „Price_Max_Slope_Degrees“ liegt, und ähnlich für RSI mit „Use_RSI_Slope_Filter“, „rsi_angle“, „RSI_Min_Slope_Degrees“ und „RSI_Max_Slope_Degrees“, setzen wir „hidden_bear_div“ auf true, zeichnen Divergenzlinien und erstellen eine Trendlinie namens „DivLine_HiddenBear_“ plus Zeitstempel im Hauptchart mit ObjectCreate, das die Hochs verbindet, und konfigurieren Eigenschaften wie „OBJPROP_COLOR“ auf „Bear_Color“, „OBJPROP_WIDTH“ auf „Line_Width“, „OBJPROP_STYLE“ auf „Line_Style“, deaktivieren die Option Strahl und setzen den Vordergrunds über die Funktion ObjectSetInteger. Wenn das „rsi_window“ gültig ist, zeichnen wir ebenfalls eine RSI-Linie mit dem Namen „DivLine_RSI_HiddenBear_“ in das Unterfenster, die RSI-Hochs mit passenden Einstellungen verbindet.

Für die Kennzeichnung von Umkehrpunkten, wenn „Mark_Swings_On_Price“ wahr ist, prüfen wir, ob ein Textobjekt mit dem Namen „SwingHigh_“ plus Zeitstempel mit „ObjectFind“ existiert; falls nicht vorhanden, erstellen wir es mit „ObjectCreate“ am Hochpunkt, setzen den Text mit „ObjectSetString“, einschließlich „high_type“ und „Hidden Bear Div“ plus formatiertem „price_angle“, falls abweichend, setzen „Swing_High_Color“, den Anker mit „ANCHOR_LEFT_LOWER“ und „Font_Size“ mit „ObjectSetInteger“. Wenn „Mark_Swings_On_RSI“ wahr ist und ein Unterfenster gefunden wird, machen wir dasselbe für ein RSI-Label mit dem Namen „RSI_SwingHigh_“ am RSI-Hoch, mit Text einschließlich des „rsi_angle“, falls divergent, derselben Farbe, dem Anker und der Größe. Abschließend wird das Chart mit der Funktion ChartRedraw neu gezeichnet. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

VERSTECKTE ABWÄRTS-DIVERGENZ

Jetzt, da wir die versteckte Abwärtsdivergenz identifizieren und visualisieren können, müssen wir sie handeln, indem wir ein Verkaufsgeschäft eröffnen. Bevor wir jedoch ein Verkaufsgeschäft eröffnen, wollen wir alle offenen Positionen schließen, um ein Überhandeln zu vermeiden. Wenn Sie möchten, können Sie sie aber weiter halten. Hierfür benötigen wir einige Hilfsfunktionen.

//+------------------------------------------------------------------+
//| Open Buy Position                                                |
//+------------------------------------------------------------------+
void OpenBuy() {
   double ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);      //--- Get current ask price
   double sl = (SL_Pips > 0) ? NormalizeDouble(ask_price - SL_Pips * _Point, _Digits) : 0; //--- Calculate stop loss if enabled
   double tp = (TP_Pips > 0) ? NormalizeDouble(ask_price + TP_Pips * _Point, _Digits) : 0; //--- Calculate take profit if enabled
   if (obj_Trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, Lot_Size, 0, sl, tp)) { //--- Attempt to open buy position
      Print("Buy trade opened on hidden bullish divergence");     //--- Log buy trade open
   } else {                                                       //--- Handle open failure
      Print("Failed to open Buy: ", obj_Trade.ResultRetcodeDescription()); //--- Log error
   }
}
//+------------------------------------------------------------------+
//| Open Sell Position                                               |
//+------------------------------------------------------------------+
void OpenSell() {
   double bid_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);      //--- Get current bid price
   double sl = (SL_Pips > 0) ? NormalizeDouble(bid_price + SL_Pips * _Point, _Digits) : 0; //--- Calculate stop loss if enabled
   double tp = (TP_Pips > 0) ? NormalizeDouble(bid_price - TP_Pips * _Point, _Digits) : 0; //--- Calculate take profit if enabled
   if (obj_Trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, Lot_Size, 0, sl, tp)) { //--- Attempt to open sell position
      Print("Sell trade opened on hidden bearish divergence");    //--- Log sell trade open
   } else {                                                       //--- Handle open failure
      Print("Failed to open Sell: ", obj_Trade.ResultRetcodeDescription()); //--- Log error
   }
}
//+------------------------------------------------------------------+
//| Close All Positions                                              |
//+------------------------------------------------------------------+
void CloseAll() {
   for (int p = PositionsTotal() - 1; p >= 0; p--) {              //--- Iterate through positions in reverse
      if (PositionGetTicket(p) > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic_Number) { //--- Check position details
         obj_Trade.PositionClose(PositionGetTicket(p));           //--- Close position
      }
   }
}

Wir definieren die Funktion „OpenBuy“, um die Eröffnung von Kaufpositionen bei Erkennung einer versteckten Aufwärtsdivergenz zu steuern. Zunächst wird der aktuelle Briefkurs (ask) mit SymbolInfoDouble abgefragt, wobei _Symbol und „SYMBOL_ASK“ übergeben werden. Wenn „SL_Pips“ größer als Null ist, berechnen wir den Stop Loss als „ask_price – SL_Pips * _Point“, normalisiert auf „_Digits“ mit NormalizeDouble; andernfalls setzen wir ihn auf Null. Ähnlich verhält es sich mit dem Take Profit: Wenn „TP_Pips“ positiv ist, berechnen wir „ask_price + TP_Pips * _Point“ normalisiert, oder Null, wenn deaktiviert. Wir versuchen, die Kaufposition über „obj_Trade.PositionOpen“ zu eröffnen, wobei wir das Symbol, ORDER_TYPE_BUY, „Lot_Size“, Null Slippage und die berechneten Sl und Tp übergeben; bei Erfolg protokollieren wir eine Meldung mit „Print“, andernfalls drucken wir die Fehlerbeschreibung aus „obj_Trade.ResultRetcodeDescription“.

Als Nächstes erstellen wir die Funktion „OpenSell“ für Verkaufspositionen bei versteckten Abwärts-Signalen, wobei wir die Struktur widerspiegeln: Wir holen uns den Geldkurs mit „SymbolInfoDouble“ und übergeben „_Symbol“ und „SYMBOL_BID“, setzen sl als „bid_price + SL_Pips * _Point“, wenn aktiviert, tp als „bid_price – TP_Pips * _Point“, wenn gesetzt, und verwenden „obj_Trade.PositionOpen“ mit „ORDER_TYPE_SELL“. Wir protokollieren Erfolg oder Misserfolg auf ähnliche Weise.

Schließlich implementieren wir die Funktion „CloseAll“, um bestehende Handelsgeschäfte vor neuen zu schließen, wobei wir zur Sicherheit eine Rückwärtsschleife von der Funktion PositionsTotal minus 1 bis Null durchführen. Für jede Position, wenn PositionGetTicket mit p ein gültiges Ticket größer als Null liefert und mit unserem „_Symbol“ über „PositionGetString“ mit „POSITION_SYMBOL“ und „Magic_Number“ mit „PositionGetInteger“ mit „POSITION_MAGIC“ übereinstimmt, schließen wir es mit „obj_Trade.PositionClose“ auf dem Ticket. Jetzt müssen wir nur noch die Funktionen hinzufügen, um alle Positionen zu schließen und die entsprechenden Positionen zu öffnen. Wir nennen sie folgendermaßen.

// Hidden bearish divergence detected - Sell signal
CloseAll();                                           //--- Close all open positions
OpenSell();                                           //--- Open sell position

Nach der Kompilierung erhalten wir das folgende Ergebnis.

HANDELSBESTÄTIGUNG VERKAUFEN

Wir sehen, dass wir mit dem Signal der Abwärts-Divergenz handeln können. Eine ähnliche Logik müssen wir nun auch für die Aufwärts-Divergenz anwenden. Hier ist die Logik, die wir angepasst haben, um das zu erreichen.

// Find latest swing low
int last_low_bar = -1, prev_low_bar = -1;                      //--- Initialize swing low bars
for (int b = 1; b < data_size - Swing_Strength; b++) {         //--- Iterate through bars
   if (CheckSwingLow(b, low_data)) {                           //--- Check for swing low
      if (last_low_bar == -1) {                                //--- Check if first swing low
         last_low_bar = b;                                     //--- Set last low bar
      } else {                                                 //--- Second swing low found
         prev_low_bar = b;                                     //--- Set previous low bar
         break;                                                //--- Exit loop
      }
   }
}
if (last_low_bar > 0 && time_data[last_low_bar] > Last_Low_Time) { //--- Check new swing low
   Prev_Low_Price = Last_Low_Price;                            //--- Update previous low price
   Prev_Low_Time = Last_Low_Time;                              //--- Update previous low time
   Last_Low_Price = low_data[last_low_bar];                    //--- Set last low price
   Last_Low_Time = time_data[last_low_bar];                    //--- Set last low time
   Prev_Low_RSI = Last_Low_RSI;                                //--- Update previous low RSI
   Last_Low_RSI = rsi_data[last_low_bar];                      //--- Set last low RSI
   string low_type = "L";                                      //--- Set default low type
   if (Prev_Low_Price > 0.0) {                                 //--- Check if previous low exists
      low_type = (Last_Low_Price < Prev_Low_Price) ? "LL" : "HL"; //--- Set low type
   }
   bool higher_low = Last_Low_Price > Prev_Low_Price;          //--- Check for higher low
   bool lower_rsi_low = Last_Low_RSI < Prev_Low_RSI;           //--- Check for lower RSI low
   int bars_diff = prev_low_bar - last_low_bar;                //--- Calculate bars between lows
   bool hidden_bull_div = false;                               //--- Initialize hidden bullish divergence flag
   double price_angle = 0.0;                                   //--- Initialize price angle
   double rsi_angle = 0.0;                                     //--- Initialize RSI angle
   if (Prev_Low_Price > 0.0 && higher_low && lower_rsi_low && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check hidden bullish divergence conditions
      if (CleanDivergence(Prev_Low_RSI, Last_Low_RSI, prev_low_bar, last_low_bar, rsi_data, false)) { //--- Check clean divergence
         price_angle = CalculateVisualAngle(chart_id, 0, Prev_Low_Time, Prev_Low_Price, Last_Low_Time, Last_Low_Price); //--- Calculate price angle
         rsi_angle = CalculateVisualAngle(chart_id, rsi_window, Prev_Low_Time, Prev_Low_RSI, Last_Low_Time, Last_Low_RSI); //--- Calculate RSI angle
         if ((!Use_Price_Slope_Filter || (price_angle >= Price_Min_Slope_Degrees && price_angle <= Price_Max_Slope_Degrees)) &&
             (!Use_RSI_Slope_Filter || (rsi_angle >= RSI_Min_Slope_Degrees && rsi_angle <= RSI_Max_Slope_Degrees))) { //--- Check slope filters
            hidden_bull_div = true;                               //--- Set hidden bullish divergence flag
            // Hidden bullish divergence detected - Buy signal
            CloseAll();                                           //--- Close all open positions
            OpenBuy();                                            //--- Open buy position
            // Draw divergence lines
            string line_name = "DivLine_HiddenBull_" + TimeToString(Last_Low_Time); //--- Set divergence line name
            ObjectCreate(chart_id, line_name, OBJ_TREND, 0, Prev_Low_Time, Prev_Low_Price, Last_Low_Time, Last_Low_Price); //--- Create trend line for price divergence
            ObjectSetInteger(chart_id, line_name, OBJPROP_COLOR, Bull_Color); //--- Set line color
            ObjectSetInteger(chart_id, line_name, OBJPROP_WIDTH, Line_Width); //--- Set line width
            ObjectSetInteger(chart_id, line_name, OBJPROP_STYLE, Line_Style); //--- Set line style
            ObjectSetInteger(chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
            ObjectSetInteger(chart_id, line_name, OBJPROP_BACK, false); //--- Set to foreground
            if (rsi_window != -1) {                               //--- Check if RSI subwindow found
               string rsi_line = "DivLine_RSI_HiddenBull_" + TimeToString(Last_Low_Time); //--- Set RSI divergence line name
               ObjectCreate(chart_id, rsi_line, OBJ_TREND, rsi_window, Prev_Low_Time, Prev_Low_RSI, Last_Low_Time, Last_Low_RSI); //--- Create trend line for RSI divergence
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_COLOR, Bull_Color); //--- Set line color
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_WIDTH, Line_Width); //--- Set line width
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_STYLE, Line_Style); //--- Set line style
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_RAY, false); //--- Disable ray
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_BACK, false); //--- Set to foreground
            }
         }
      }
   }
   // Draw swing label on price if enabled
   if (Mark_Swings_On_Price) {                                    //--- Check if marking swings on price enabled
      string swing_name = "SwingLow_" + TimeToString(Last_Low_Time); //--- Set swing low label name
      if (ObjectFind(chart_id, swing_name) < 0) {                 //--- Check if label exists
         string low_label_text = " " + low_type + (hidden_bull_div ? " Hidden Bull Div " + DoubleToString(price_angle, 1) + "°" : ""); //--- Set label text with angle if divergence
         ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_Low_Time, Last_Low_Price); //--- Create swing low label
         ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, low_label_text); //--- Set label text
         ObjectSetInteger(chart_id, swing_name, OBJPROP_COLOR, Swing_Low_Color); //--- Set label color
         ObjectSetInteger(chart_id, swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Set label anchor
         ObjectSetInteger(chart_id, swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Set label font size
      }
   }
   // Draw corresponding swing label on RSI if enabled
   if (Mark_Swings_On_RSI && rsi_window != -1) {                  //--- Check if marking swings on RSI enabled and subwindow found
      string rsi_swing_name = "RSI_SwingLow_" + TimeToString(Last_Low_Time); //--- Set RSI swing low label name
      if (ObjectFind(chart_id, rsi_swing_name) < 0) {             //--- Check if label exists
         string low_label_text_rsi = " " + low_type + (hidden_bull_div ? " Hidden Bull Div " + DoubleToString(rsi_angle, 1) + "°" : ""); //--- Set label text with RSI angle if divergence
         ObjectCreate(chart_id, rsi_swing_name, OBJ_TEXT, rsi_window, Last_Low_Time, Last_Low_RSI); //--- Create RSI swing low label
         ObjectSetString(chart_id, rsi_swing_name, OBJPROP_TEXT, low_label_text_rsi); //--- Set label text
         ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_COLOR, Swing_Low_Color); //--- Set label color
         ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Set label anchor
         ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Set label font size
      }
   }
   ChartRedraw(chart_id);                                         //--- Redraw chart
}

Hier haben wir die gleiche Logik wie beim Signal der Abwärts-Divergenz angewandt, nur unter umgekehrten Bedingungen. Wir haben Kommentare hinzugefügt, damit es selbsterklärend ist. Nach dem Kompilieren erhalten wir folgendes Ergebnis.

HANDELSBESTÄTIGUNG KAUFEN

Aus dem Bild können wir ersehen, dass wir auch mit den Aufwärts-Divergenzen handeln können. Jetzt müssen wir die Gewinne optimieren, indem wir einen Trailing-Stop hinzufügen. Auch dafür werden wir eine Funktion definieren. Das hilft, den Code modular zu halten.

//+------------------------------------------------------------------+
//| Apply Trailing Stop to Positions                                 |
//+------------------------------------------------------------------+
void ApplyTrailingStop() {
   double point = _Point;                                         //--- Get symbol point value
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Iterate through positions in 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 current stop loss
            double tp = PositionGetDouble(POSITION_TP);           //--- Get current take profit
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
            ulong ticket = PositionGetInteger(POSITION_TICKET);   //--- Get position ticket
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Calculate new stop loss
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Check trailing conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);    //--- Modify position with new stop loss
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Calculate new stop loss
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Check trailing conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);    //--- Modify position with new stop loss
               }
            }
         }
      }
   }
}

Wir implementieren die Funktion „ApplyTrailingStop“, um Stop-Losses für offene Positionen dynamisch anzupassen, sobald sie einen profitablen Schwellenwert erreichen, und so Gewinne zu sichern, wenn sich der Markt günstig entwickelt. Wir beginnen damit, den Punktwert des Symbols „point“ zuzuweisen, indem wir _Point für Pip-Berechnungen verwenden. Dann gehen wir in einer Schleife rückwärts durch alle Positionen vom Ergebnis der Funktion PositionsTotal minus 1 bis Null, um Änderungen ohne Indexverschiebungen sicher zu behandeln.

Für jede Position, wenn PositionGetTicket für i ein gültiges Ticket größer als Null zurückgibt, überprüfen wir, ob es zu unserem Symbol gehört, indem wir PositionGetString mit „POSITION_SYMBOL“ aufrufen und prüfen, ob es gleich _Symbol ist, und unseren Kennzeichnung über „PositionGetInteger“ mit „POSITION_MAGIC“ mit „Magic_Number“ abgleichen. Wir holen uns den aktuellen „sl“ mit PositionGetDouble über „POSITION_SL“, „tp“ über „PositionGetDouble“ mit „POSITION_TP“, „openPrice“ von „PositionGetDouble“ mit „POSITION_PRICE_OPEN“, und das „ticket“ über „PositionGetInteger“ mit „POSITION_TICKET“.

Wenn es sich um eine Kaufposition handelt, die durch „PositionGetInteger“ mit „POSITION_TYPE“ gleich „POSITION_TYPE_BUY“ geprüft wurde, berechnen wir „newSL“ als das aktuelle Gebot, das aus „SymbolInfoDouble“ über „_Symbol“ und „SYMBOL_BID“, minus „Trailing_Stop_Pips * Punkt“, normalisiert auf „_Digits“ mit „NormalizeDouble“. Wir testen dann, ob dieser „newSL“ größer als der bestehende „sl“ ist und der Gewinn, berechnet als das Gebot aus „SymbolInfoDouble“ minus „openPrice“, „Min_Profit_To_Trail_Pips * point“ übersteigt; wenn ja, aktualisieren wir die Position mit „obj_Trade.PositionModify“ mit dem Ticket, dem neuen SL und dem unveränderten TP.

Für Verkaufspositionen, bei denen „POSITION_TYPE“ gleich POSITION_TYPE_SELL aus „PositionGetInteger“ ist, berechnen wir „newSL“ als den Ask aus „SymbolInfoDouble“ über „_Symbol“ und „SYMBOL_ASK“, plus „Trailing_Stop_Pips * point“, ebenfalls normalisiert. Wir prüfen, ob „newSL“ kleiner als der aktuelle „sl“ ist und der Gewinn, abgeleitet aus „openPrice“ minus Ask über „SymbolInfoDouble“, die Mindestschwelle übersteigt, und modifizieren dann entsprechend mit „obj_Trade.PositionModify“. Dadurch wird sichergestellt, dass das Trailing nur bei qualifizierten Handelsgeschäften aktiviert wird, ohne andere zu beeinträchtigen. Jetzt können wir die Funktion in unserem Tick- Ereignisbehandlung aufrufen, um die schwere Arbeit zu erledigen.

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (Enable_Trailing_Stop && PositionsTotal() > 0) {            //--- Check if trailing stop enabled
      ApplyTrailingStop();                                        //--- Apply trailing stop to positions
   }
   static datetime last_time = 0;                                 //--- Store last processed time
   datetime current_time = iTime(_Symbol, _Period, 0);            //--- Get current bar time
   
   //--- THE REST OF THE LOGIC

}

Wir rufen einfach die obige Trailing-Stop-Funktion bei jedem Tick auf, wenn wir Positionen offen haben, und erhalten das folgende Ergebnis.

TRAILING-STOP-AKTIVIERUNG

Nach der Anwendung des Trailing-Stopps ist das alles. Wir sind fertig. Wir müssen nun sicherstellen, dass wir unsere Chartobjekte löschen, wenn wir den Experten aus dem Chart entfernen, um Unordnung zu vermeiden.

//+------------------------------------------------------------------+
//| Expert Deinitialization Function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (RSI_Handle != INVALID_HANDLE) {                            //--- Check if RSI handle is valid
      long chart_id = ChartID();                                  //--- Get current chart ID
      string rsi_name = "RSI(" + IntegerToString(RSI_Period) + ")"; //--- Generate RSI indicator name
      int rsi_subwin = ChartWindowFind(chart_id, rsi_name);       //--- Find RSI subwindow
      if (rsi_subwin != -1) {                                     //--- Check if RSI subwindow found
         ChartIndicatorDelete(chart_id, rsi_subwin, rsi_name);    //--- Delete RSI indicator from chart
         Print("RSI indicator removed from chart");               //--- Log removal
      }
      IndicatorRelease(RSI_Handle);                               //--- Release RSI handle
   }
   ObjectsDeleteAll(0, "DivLine_");                               //--- Delete all divergence line objects
   ObjectsDeleteAll(0, "SwingHigh_");                             //--- Delete all swing high objects
   ObjectsDeleteAll(0, "SwingLow_");                              //--- Delete all swing low objects
   ObjectsDeleteAll(0, "RSI_SwingHigh_");                         //--- Delete all RSI swing high objects
   ObjectsDeleteAll(0, "RSI_SwingLow_");                          //--- Delete all RSI swing low objects
   Print("RSI Hidden Divergence EA deinitialized");               //--- Log deinitialization
}

Wir definieren die Ereignishandler von OnDeinit, das einen konstanten Integer-Parameter „reason“ akzeptiert, der die Ursache der Deinitialisierung angibt, um Aufräumarbeiten durchzuführen, wenn der Expert Advisor aus dem Chart entladen wird. Wenn „RSI_Handle“ nicht gleich INVALID_HANDLE ist, wird die aktuelle Chart-ID mit „ChartID“ abgerufen und der Name des RSI-Indikators durch Kombination von „RSI(“ mit der über die Funktion IntegerToString umgewandelten Periode konstruiert. Dann suchen wir das RSI-Subfenster mit ChartWindowFind unter Angabe der Chart-ID und des Namens; wenn es gefunden wird (rsi_subwin != -1), entfernen wir den Indikator aus dem Chart mit ChartIndicatorDelete unter Angabe der Chart-ID, des Subfenster-Index und des Namens und protokollieren die Entfernung. Danach geben wir die Ressourcen des Indikators frei, indem wir IndicatorRelease mit dem RSI-Handle aufrufen.

Als Nächstes löschen wir alle Chartobjekte, indem wir die Funktion ObjectsDeleteAll mehrmals aufrufen: zuerst mit dem Teilfenster 0 und dem Präfix „DivLine_“, um die Divergenzlinien zu löschen, dann „SwingHigh_“ für die Kennzeichnung von hohen Umkehrpunkten, „SwingLow_“ für die Kennzeichnung von tiefen Umkehrpunkten, „RSI_SwingHigh_“ für die Kennzeichnung RSI-Hochs und „RSI_SwingLow_“ für die RSI-Tiefs. Schließlich protokollieren wir den Abschluss der Deinitialisierung mit „Drucken“, um zu bestätigen, dass der RSI Hidden Divergence EA ordnungsgemäß beendet wurde. Da wir unsere Ziele erreicht haben, bleibt nur noch das Backtesting des Programms, das im nächsten Abschnitt behandelt wird.


Backtests

Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.

Backtest-Grafik:

GRAPH

Bericht des Backtest:

BERICHT


Schlussfolgerung

Abschließend haben wir das Handelssystem einer versteckten RSI-Divergenz in MQL5 entwickelt, das versteckte Aus- und Abwärts-Divergenzen durch hohe und tiefe Umkehrpunkte mit Stärkebestätigung identifiziert, sie mit sauberen Checks innerhalb von Balkenbereich und Toleranz validiert und Signale mit anpassbaren Steigungswinkeln auf Preis- und RSI-Linien für eine verbesserte Genauigkeit filtert. Das System führt Handelsgeschäfte mit einer festen Losgröße, optionalem Stop-Loss und Take-Profit in Pips sowie Trailing-Stops für dynamisches Risikomanagement aus und bietet visuelles Feedback über farbige Trendlinien und beschriftete Umkehrpunkte mit Winkelanzeigen auf Preis- und RSI-Charts.

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 versteckten RSI-Divergenz-Strategie sind Sie für den effektiven Handel mit Fortsetzungssignalen gerüstet und bereit für weitere Optimierungen auf Ihrem Handelsweg. Viel Spaß beim Handeln!

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

Risk-Based Trade Placement EA mit On-Chart UI (Part 1): Gestaltung der Nutzeroberfläche Risk-Based Trade Placement EA mit On-Chart UI (Part 1): Gestaltung der Nutzeroberfläche
Lernen Sie, wie man ein sauberes und professionelles On-Chart-Kontrollpanel in MQL5 für einen Risk-Based Trade Placement Expert Advisor erstellt. Diese Schritt-für-Schritt-Anleitung erklärt, wie man eine funktionale GUI entwirft, die es Händlern ermöglicht, Handelsparameter einzugeben, die Losgröße zu berechnen und die automatische Auftragserteilung vorzubereiten.
Entwicklung des Price Action Analysis Toolkit (Teil 48): Multi-Timeframe Harmony Index mit gewichtetem Bias Dashboard Entwicklung des Price Action Analysis Toolkit (Teil 48): Multi-Timeframe Harmony Index mit gewichtetem Bias Dashboard
In diesem Artikel wird der „Multi-Timeframe Harmony Index“ vorgestellt – ein fortschrittlicher Expert Advisor für MetaTrader 5, der einen gewichteten Bias aus mehreren Timeframes berechnet, die Messwerte mithilfe des EMA glättet und die Ergebnisse in einem übersichtlichen Dashboard anzeigt. Es umfasst anpassbare Warnungen und automatische Kauf-/Verkaufssignale bei Überschreiten von Schwellenwerten für starke Verzerrungen. Geeignet für Händler, die Multi-Timeframe-Analysen nutzen, um ihre Einstiege an der allgemeinen Marktstruktur auszurichten.
Entwicklung des Price Action Analysis Toolkit (Teil 49): Integration von Trend-, Momentum- und Volatilitätsindikatoren in ein MQL5-System Entwicklung des Price Action Analysis Toolkit (Teil 49): Integration von Trend-, Momentum- und Volatilitätsindikatoren in ein MQL5-System
Vereinfachen Sie Ihre MetaTrader 5 Charts mit dem Multi Indicator Handler EA. Dieses interaktive Dashboard fasst Trend-, Momentum- und Volatilitätsindikatoren in einem Echtzeit-Panel zusammen. Wechseln Sie im Handumdrehen zwischen den Profilen und konzentrieren Sie sich auf die Analyse, die Sie am meisten benötigen. Mit den Ein-Klick-Steuerelementen zum Ausblenden/Einblenden können Sie sich auf die Kursentwicklung konzentrieren. Lesen Sie weiter, um Schritt für Schritt zu erfahren, wie Sie es in MQL5 selbst erstellen und anpassen können.
Aufbau eines Smart Trade Managers in MQL5: Automatisieren Sie Break-Even, Trailing Stop und Teilweises Schließen Aufbau eines Smart Trade Managers in MQL5: Automatisieren Sie Break-Even, Trailing Stop und Teilweises Schließen
Lernen Sie, wie man einen Smart Trade Manager Expert Advisor in MQL5 erstellt, der das Handelsmanagement mit Break-Even-, Trailing-Stop- und Partial-Close-Funktionen automatisiert. Ein praktischer, schrittweiser Leitfaden für Händler, die durch Automatisierung Zeit sparen und die Konsistenz verbessern wollen.