preview
Automating Trading Strategies in MQL5 (Part 37): Regular RSI Divergence Convergence with Visual Indicators

Automating Trading Strategies in MQL5 (Part 37): Regular RSI Divergence Convergence with Visual Indicators

MetaTrader 5Trading |
10 503 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article (Part 36), we developed a Supply and Demand Trading System in MetaQuotes Language 5 (MQL5) that identified supply and demand zones through consolidation ranges, validated them with impulsive moves, and traded retests with trend confirmation and customizable risk parameters. In Part 37, we develop a Regular RSI Divergence Convergence System with visual indicators. This system detects regular bullish and bearish divergences between price swings and Relative Strength Index (RSI) values, executes trades on signals with optional risk controls, and provides on-chart visualizations for enhanced analysis. We will cover the following topics:

  1. Understanding the Regular RSI Divergence Convergence Strategy
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you’ll have a functional MQL5 strategy for trading regular RSI divergences, ready for customization—let’s dive in!


Understanding the Regular RSI Divergence Convergence Strategy

The regular RSI divergence convergence strategy focuses on spotting potential trend reversals by identifying mismatches between price action and the Relative Strength Index (RSI) oscillator, which measures momentum. For bullish divergence, the price forms a lower low while the RSI creates a higher low, signaling weakening bearish momentum and a likely bullish reversal. For bearish divergence, the price makes a higher high, but the RSI shows a lower high, indicating fading bullish momentum and a potential bearish shift.

We use confirmed swing points over a set number of bars to detect these patterns. To ensure a clean divergence, we apply a tolerance that avoids intermediate crossings. Entry is triggered when a bullish divergence is identified—take a long position at the close of the confirmation bar—or when a bearish divergence is spotted—enter a short position at the close of the confirmation bar. Positions are managed with risk controls, including predefined stop and profit targets, as well as dynamic trailing stops. By incorporating these elements, we capitalize on reversal setups. Have a look below at the different setups we could have.

Bullish Divergence Setup:

REGULAR BULLISH DIVERGENCE

Bearish Divergence Setup:

REGULAR BEARISH DIVERGENCE

Our plan is to scan for swing highs and lows using a strength confirmation, validate divergences within specified bar ranges and tolerance thresholds, execute automated trades with customizable lot sizing and risk parameters, and add visual aids like colored lines and labels for easy monitoring, building a system for divergence-based trading. In a nutshell, we want to achieve the following:

STRATEGY BLUEPRINT


Implementation in MQL5

To create the program in MQL5, open the MetaEditor, go to the Navigator, locate the Experts folder, click on the "New" tab, and follow the prompts to create the file. Once it is made, in the coding environment, we will need to declare some input parameters and global variables that we will use throughout the program.

//+------------------------------------------------------------------+
//|                        RSI Regular 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 "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 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

We start by including the Trade library with "#include <Trade\Trade.mqh>" to enable built-in functions for managing positions and orders. Next, we define various input parameters grouped by category for user customization. Under "RSI Settings", we set "RSI_Period" to 14 for the RSI calculation length and "RSI_Applied" to PRICE_CLOSE to base it on closing prices. In "Swing Settings", "Swing_Strength" is set to 5 to determine the bars needed for confirming swing highs and lows, while "Min_Bars_Between" and "Max_Bars_Between" limit the divergence detection to between 5 and 50 bars, and "Tolerance" at 0.1 allows a small buffer for clean divergence checks.

For "Trade Settings", we include "Lot_Size" at 0.01 for fixed position sizing, "Magic_Number" as 123456789 to identify our trades, and "SL_Pips" and "TP_Pips" both at 300.0 to optionally set stop loss and take profit distances (disabled if zero). The "Trailing Stop Settings" group has "Enable_Trailing_Stop" as true to activate dynamic stops, with "Trailing_Stop_Pips" at 30.0 for the trailing distance and "Min_Profit_To_Trail_Pips" at 50.0 as the profit threshold to begin trailing. In "Visualization", we specify colors like "Bull_Color" as clrGreen for bullish divergences, "Bear_Color" as clrRed for bearish ones, "Swing_High_Color" as clrRed, and "Swing_Low_Color" as clrGreen for labels, along with "Line_Width" at 2, "Line_Style" as STYLE_SOLID for divergence lines, and "Font_Size" at 8 for text labels.

Finally, we declare global variables: "RSI_Handle" initialized to "INVALID_HANDLE" to store the RSI indicator reference, and "obj_Trade" as a CTrade instance for handling trade operations. We will now need to define the global variables for swing points and initialize them.

//+------------------------------------------------------------------+
//| 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

We continue by declaring a set of global variables under the "Swing Variables" section to store details about the latest and prior swing points for both highs and lows. These include "Last_High_Price" and "Last_High_Time" for the most recent swing high's price and timestamp, along with "Prev_High_Price" and "Prev_High_Time" for the one before it. Similarly, for swing lows, we have "Last_Low_Price", "Last_Low_Time", "Prev_Low_Price", and "Prev_Low_Time". To link these with indicator data, we add "Last_High_RSI" and "Prev_High_RSI" for RSI values at those high points, as well as "Last_Low_RSI" and "Prev_Low_RSI" for the lows. All are initialized to zero to start fresh, enabling us to update and compare them dynamically during runtime for divergence detection. With these, we are all set. We just need to initialize the program, specifically the RSI indicator, and make sure we can reference its window so we can draw on it later.

//+------------------------------------------------------------------+
//| 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 Divergence EA initialized");         //--- Log initialization success
   return(INIT_SUCCEEDED);                         //--- Return initialization success
}

In the OnInit event handler, we begin by creating the RSI indicator handle with iRSI, passing the current symbol, timeframe, RSI period, and applied price type to set up the oscillator. We then check if "RSI_Handle" is "INVALID_HANDLE"; if so, we log an error message using "Print" and return "INIT_FAILED" to halt initialization. Next, we retrieve the current chart ID with ChartID and construct the RSI indicator name as a string combining "RSI(" with the period converted via the IntegerToString function.

We attempt to locate the RSI subwindow using ChartWindowFind with the chart ID and name; if it's not found ("rsi_subwin" == -1), we add the indicator to subwindow 1 via ChartIndicatorAdd, logging an error if that fails. After that, we configure the trade object by calling "obj_Trade.SetExpertMagicNumber(Magic_Number)" to associate our unique identifier with trades. Finally, we print a success message and return "INIT_SUCCEEDED" to confirm proper setup. When we initialize the program, we get the following outcome.

INITIALIZATION

Now that we can initialize the program and add the indicator to its subwindow that we can reference, we will need to check and draw the swing points on the chart so that we can use them to identify the divergences or convergences. Let us define some helper functions for that.

//+------------------------------------------------------------------+
//| 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
}

We define the "CheckSwingHigh" function, which takes an integer bar index and a reference to an array of high prices to determine if a swing high exists at that bar. It first checks if the bar is out of bounds based on "Swing_Strength" using ArraySize to avoid index errors, returning false if so. We then retrieve the current high price and loop from 1 to "Swing_Strength", verifying that no left or right adjacent bars have highs greater than or equal to the current one; if any do, it returns false, otherwise true to confirm a swing high.

Similarly, we create the "CheckSwingLow" function with the same structure but for low prices, ensuring the bar is in range, getting the current low, and checking in the loop that no adjacent bars have lows less than or equal to the current one, returning true only if it's a valid swing low. We can also define a function for detecting a clean RSI divergence.

//+------------------------------------------------------------------+
//| 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
}

Here, we implement the "CleanDivergence" function to verify that the divergence line between two RSI points remains uncrossed by intermediate RSI values, ensuring a "clean" pattern without violations. It accepts parameters like "rsi1" and "rsi2" for the RSI values at the swings, "shift1" and "shift2" for their bar shifts (with "shift1" expected to be greater), a reference to the "rsi_data" array, and a "bearish" boolean to distinguish divergence type.

We first validate the shifts, returning false if invalid, then loop through bars between "shift2 + 1" and "shift1 - 1", calculating an "interp_factor" as the normalized position and an "interp_rsi" as the linearly interpolated value between "rsi1" and "rsi2". For bearish cases, we check if any "rsi_data[b]" exceeds "interp_rsi + Tolerance", returning false on breach; for bullish, we ensure no "rsi_data[b]" falls below "interp_rsi - Tolerance". If all checks pass, we return true to confirm the divergence is clean and reliable for signaling. We all all the functions to help us identify the divergences, and thus we can implement that on the OnTick event handler. We will start with bearish divergence.

//+------------------------------------------------------------------+
//| 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
   // 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 higher_high = Last_High_Price > Prev_High_Price; //--- Check for higher high
      bool lower_rsi_high = Last_High_RSI < Prev_High_RSI; //--- Check for lower RSI high
      int bars_diff = prev_high_bar - last_high_bar; //--- Calculate bars between highs
      bool bear_div = false;                     //--- Initialize bearish divergence flag
      if (Prev_High_Price > 0.0 && higher_high && lower_rsi_high && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check bearish divergence conditions
         if (CleanDivergence(Prev_High_RSI, Last_High_RSI, prev_high_bar, last_high_bar, rsi_data, true)) { //--- Check clean divergence
            bear_div = true;                    //--- Set bearish divergence flag
            // Draw divergence lines
            string line_name = "DivLine_Bear_" + 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
            int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Find RSI subwindow
            if (rsi_window != -1) {              //--- Check if RSI subwindow found
               string rsi_line = "DivLine_RSI_Bear_" + 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
      string swing_name = "SwingHigh_" + TimeToString(Last_High_Time); //--- Set swing high label name
      if (ObjectFind(chart_id, swing_name) < 0) { //--- Check if label exists
         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_type + (bear_div ? " Bear Div" : "")); //--- 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
      }
      ChartRedraw(chart_id);                    //--- Redraw chart
   }
}

In the OnTick event handler, which runs on every new tick, we use a static "last_time" variable to track the timestamp of the last processed bar, retrieving the current bar time with iTime and exiting early if it's unchanged to avoid redundant processing on the same bar, then updating "last_time". We set a "data_size" of 200 bars for analysis, which is an arbitrary value we thought, you can increase or decrease as per your liking, and declare arrays for "high_data", "low_data", "rsi_data", and "time_data", populating them via CopyHigh, "CopyLow", "CopyTime", and CopyBuffer to fetch recent high prices, low prices, timestamps, and RSI values from the handle. We configure these arrays as series with ArraySetAsSeries for time-series indexing and obtain the "chart_id" using "ChartID" for later object drawing.

To detect the latest swing highs, we initialize "last_high_bar" and "prev_high_bar" to -1, then loop backward from recent bars (1 to "data_size - Swing_Strength"), calling "CheckSwingHigh" to find the two most recent valid swing highs, assigning them accordingly, and breaking after the second. If a new swing high is confirmed by comparing "time_data[last_high_bar]" against "Last_High_Time", we update the previous high variables with current last values, set the new "Last_High_Price", "Last_High_Time", "Last_High_RSI" from the arrays, and determine the "high_type" as "H" default, or "HH" for higher high or "LH" for lower high if a previous exists.

We then evaluate bearish divergence conditions: if "Prev_High_Price" exists, price shows a higher high, RSI a lower high, and bar difference ("prev_high_bar - last_high_bar") falls within "Min_Bars_Between" and "Max_Bars_Between", we invoke "CleanDivergence" with previous and last RSI highs, their shifts, the "rsi_data" array, and true for bearish mode. If clean, we set the "bear_div" flag to true and draw visual elements: create a trend line object named "DivLine_Bear_" plus timestamp on the main chart with ObjectCreate connecting previous and last high points, setting properties like "OBJPROP_COLOR" to "Bear_Color", "OBJPROP_WIDTH" to "Line_Width", "OBJPROP_STYLE" to "Line_Style", disabling ray and setting foreground via the ObjectSetInteger function.

We locate the RSI subwindow with "ChartWindowFind" using the period-based name; if found, create a similar RSI divergence line named "DivLine_RSI_Bear_" in that subwindow, connecting RSI values with matching properties. Finally, for the swing label, we check if a text object named "SwingHigh_" plus timestamp exists using "ObjectFind"; if not, create it with "ObjectCreate" at the high point, set text via "ObjectSetString" to include "high_type" and " Bear Div" if applicable, apply "Swing_High_Color", anchor "ANCHOR_LEFT_LOWER", and "Font_Size" with "ObjectSetInteger", then refresh the chart using the ChartRedraw function. Upon compilation, we get the following outcome.

BEARISH DIVERGENCE

Now that we can identify and visualize the bearish divergence, we need to trade it by opening a sell trade. However, before we open a sell trade, we want to close all the open positions to avoid overtrading. You can choose to keep them if you want. We will need some helper functions for that.

//+------------------------------------------------------------------+
//| 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 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 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
      }
   }
}

Since we will need to open both buy and sell positions, we create all the helper functions for that. First, we define the "OpenBuy" function to handle opening long positions upon detecting a bullish divergence. We have not yet defined a logic to identify buy signals; we are just leaping forward since we will need it either way. We first retrieve the current ask price using the SymbolInfoDouble function. Then, if "SL_Pips" is greater than zero, we calculate the stop loss as "ask_price - SL_Pips * _Point" normalized to the symbol's digits with NormalizeDouble; otherwise, set it to zero. Similarly, for take profit, if "TP_Pips" is positive, we compute "ask_price + TP_Pips * _Point" normalized, or zero if disabled. We attempt to open the buy position via "obj_Trade.PositionOpen" with parameters like the symbol, ORDER_TYPE_BUY, "Lot_Size", no slippage, and the calculated sl and tp; if successful, log a message with "Print", otherwise print the error description from "obj_Trade.ResultRetcodeDescription".

Next, we create the "OpenSell" function for short positions on bearish signals, mirroring the structure: get the bid price with "SymbolInfoDouble(_Symbol, SYMBOL_BID)", set sl as "bid_price + SL_Pips * _Point" if enabled, tp as "bid_price - TP_Pips * _Point" if set, and use "obj_Trade.PositionOpen" with "ORDER_TYPE_SELL". Log success or failure similarly.

Finally, we implement the "CloseAll" function to shut down existing trades before new ones, looping backward from "PositionsTotal() - 1" to zero for safety. For each position, if "PositionGetTicket(p)" is valid and matches our "_Symbol" via "PositionGetString(POSITION_SYMBOL)" and "Magic_Number" with "PositionGetInteger(POSITION_MAGIC)", we close it using "obj_Trade.PositionClose" on the ticket. We now need to add these functions when we have a signal by first closing the positions and then opening the respective signal positions. For the bearish divergence signal, we add the following functions.

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

Opon compilation, we get the following outcome.

SHORT POSITION

We can see we can trade the bearish divergence signal. We now need to have a similar logic for the bullish divergence as well. Here is the logic we adapted to achieve that.

// 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 lower_low = Last_Low_Price < Prev_Low_Price; //--- Check for lower low
   bool higher_rsi_low = Last_Low_RSI > Prev_Low_RSI; //--- Check for higher RSI low
   int bars_diff = prev_low_bar - last_low_bar; //--- Calculate bars between lows
   bool bull_div = false;                 //--- Initialize bullish divergence flag
   if (Prev_Low_Price > 0.0 && lower_low && higher_rsi_low && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check bullish divergence conditions
      if (CleanDivergence(Prev_Low_RSI, Last_Low_RSI, prev_low_bar, last_low_bar, rsi_data, false)) { //--- Check clean divergence
         bull_div = true;                 //--- Set bullish divergence flag
         // Bullish divergence detected - Buy signal
         CloseAll();                      //--- Close all open positions
         OpenBuy();                       //--- Open buy position
         // Draw divergence lines
         string line_name = "DivLine_Bull_" + 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
         int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Find RSI subwindow
         if (rsi_window != -1) {          //--- Check if RSI subwindow found
            string rsi_line = "DivLine_RSI_Bull_" + 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
   string swing_name = "SwingLow_" + TimeToString(Last_Low_Time); //--- Set swing low label name
   if (ObjectFind(chart_id, swing_name) < 0) { //--- Check if label exists
      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_type + (bull_div ? " Bull Div" : "")); //--- 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
   }
   ChartRedraw(chart_id);                 //--- Redraw chart
}

Here, we just used the same logic as we did with the bearish divergence signal, only with inversed conditions. We added comments to make it self-explanatory. Upon compilation, we have the following outcome.

BULLISH DIVERGENCE POSITION

From the image, we can see that we are able to trade the bullish divergences as well. What we now need to do is optimize the profits by adding a trailing stop. We will define a function for that as well. It helps keep the code modular.

//+------------------------------------------------------------------+
//| 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
               }
            }
         }
      }
   }
}

Here, we implement the "ApplyTrailingStop" function to dynamically adjust stop losses on open positions once they reach a profitable threshold, helping lock in gains as the market moves favorably. We start by assigning the symbol's point value to "point" using "_Point" for pip calculations. Then, we loop backward through all positions from the result of "PositionsTotal" minus 1 to zero to safely handle closures or modifications without index shifts.

For each position, if the PositionGetTicket function with parameter i returns a valid ticket greater than zero, we verify it belongs to our symbol by calling PositionGetString with "POSITION_SYMBOL" and checking if it equals _Symbol, and matches our identifier via "PositionGetInteger" with "POSITION_MAGIC" against "Magic_Number". We retrieve the current "sl" with "PositionGetDouble" passing "POSITION_SL", "tp" using "PositionGetDouble" with "POSITION_TP", "openPrice" from "PositionGetDouble" with "POSITION_PRICE_OPEN", and the "ticket" via "PositionGetInteger" with "POSITION_TICKET".

If it's a buy position checked by PositionGetInteger with "POSITION_TYPE" equaling POSITION_TYPE_BUY, we calculate a "newSL" as the current bid obtained from "SymbolInfoDouble" passing "_Symbol" and "SYMBOL_BID", minus "Trailing_Stop_Pips * point", normalized to "_Digits" with "NormalizeDouble". We then test if this "newSL" is greater than the existing "sl" and the profit, calculated as the bid from "SymbolInfoDouble" minus "openPrice", exceeds "Min_Profit_To_Trail_Pips * point"; if so, we update the position using "obj_Trade.PositionModify" with the ticket, new SL, and unchanged TP.

For sell positions where "POSITION_TYPE" equals POSITION_TYPE_SELL from "PositionGetInteger", we compute "newSL" as the ask from "SymbolInfoDouble" passing "_Symbol" and "SYMBOL_ASK", plus "Trailing_Stop_Pips * point", normalized similarly. We check if "newSL" is less than the current "sl" and profit, derived from "openPrice" minus the ask via "SymbolInfoDouble", surpasses the minimum threshold, then modify accordingly with "obj_Trade.PositionModify". This ensures trailing only activates on qualifying trades without affecting others. We can now call the function in our tick event handler to do the heavy lifting.

if (Enable_Trailing_Stop && PositionsTotal() > 0) {               //--- Check if trailing stop enabled
   ApplyTrailingStop();                   //--- Apply trailing stop to positions
}

We just call the trailing stop function above on every tick if we have positions open and we get the following outcome.

TRAILING STOP

After applying the trailing stop, that is all. We are done. We now need to make sure we delete our chart objects when we remove the expert from the chart to avoid clutter.

//+------------------------------------------------------------------+
//| Expert Deinitialization Function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (RSI_Handle != INVALID_HANDLE) IndicatorRelease(RSI_Handle); //--- Release RSI handle if valid
   ObjectsDeleteAll(0, "DivLine_");                //--- Delete all divergence line objects
   ObjectsDeleteAll(0, "SwingHigh_");              //--- Delete all swing high objects
   ObjectsDeleteAll(0, "SwingLow_");               //--- Delete all swing low objects
   Print("RSI Divergence EA deinitialized");       //--- Log deinitialization
}

In the OnDeinit event handler, if "RSI_Handle" does not equal INVALID_HANDLE, we free the indicator's memory by calling IndicatorRelease with "RSI_Handle" as the argument. Next, we clear all relevant chart objects: using ObjectsDeleteAll with subwindow 0 and the prefix "DivLine_" to remove divergence lines, then with "SwingHigh_" for swing high labels, and "SwingLow_" for swing low labels. Lastly, we output a confirmation message via Print to log that the RSI Divergence EA has been successfully deinitialized. Since we have achieved our objectives, the thing that remains is backtesting the program, and that is handled in the next section.


Backtesting

After thorough backtesting, we have the following results.

Backtest graph:

GRAPH

Backtest report:

REPORT


Conclusion

In conclusion, we’ve developed a regular RSI divergence convergence system in MQL5 that detects bullish and bearish divergences through swing high and low points with strength confirmation, validates them using clean checks within bar ranges and tolerance, and executes trades with fixed lots, optional stop loss, and take profit in pips, plus trailing stops for dynamic risk management.

Disclaimer: This article is for educational purposes only. Trading carries significant financial risks, and market volatility may result in losses. Thorough backtesting and careful risk management are crucial before deploying this program in live markets.

With this regular RSI divergence strategy, you’re equipped to trade divergence signals effectively, ready for further optimization in your trading journey. Happy trading!

Machine Learning Blueprint (Part 4): The Hidden Flaw in Your Financial ML Pipeline — Label Concurrency Machine Learning Blueprint (Part 4): The Hidden Flaw in Your Financial ML Pipeline — Label Concurrency
Discover how to fix a critical flaw in financial machine learning that causes overfit models and poor live performance—label concurrency. When using the triple-barrier method, your training labels overlap in time, violating the core IID assumption of most ML algorithms. This article provides a hands-on solution through sample weighting. You will learn how to quantify temporal overlap between trading signals, calculate sample weights that reflect each observation's unique information, and implement these weights in scikit-learn to build more robust classifiers. Learning these essential techniques will make your trading models more robust, reliable and profitable.
Building a Smart Trade Manager in MQL5: Automate Break-Even, Trailing Stop, and Partial Close Building a Smart Trade Manager in MQL5: Automate Break-Even, Trailing Stop, and Partial Close
Learn how to build a Smart Trade Manager Expert Advisor in MQL5 that automates trade management with break-even, trailing stop, and partial close features. A practical, step-by-step guide for traders who want to save time and improve consistency through automation.
Black-Scholes Greeks: Gamma and Delta Black-Scholes Greeks: Gamma and Delta
Gamma and Delta measure how an option’s value reacts to changes in the underlying asset’s price. Delta represents the rate of change of the option’s price relative to the underlying, while Gamma measures how Delta itself changes as price moves. Together, they describe an option’s directional sensitivity and convexity—critical for dynamic hedging and volatility-based trading strategies.
Price Action Analysis Toolkit Development (Part 47): Tracking Forex Sessions and Breakouts in MetaTrader 5 Price Action Analysis Toolkit Development (Part 47): Tracking Forex Sessions and Breakouts in MetaTrader 5
Global market sessions shape the rhythm of the trading day, and understanding their overlap is vital to timing entries and exits. In this article, we’ll build an interactive trading sessions  EA that brings those global hours to life directly on your chart. The EA automatically plots color‑coded rectangles for the Asia, Tokyo, London, and New York sessions, updating in real time as each market opens or closes. It features on‑chart toggle buttons, a dynamic information panel, and a scrolling ticker headline that streams live status and breakout messages. Tested on different brokers, this EA combines precision with style—helping traders see volatility transitions, identify cross‑session breakouts, and stay visually connected to the global market’s pulse.