Automating Trading Strategies in MQL5 (Part 37): Regular RSI Divergence Convergence with Visual Indicators
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:
- Understanding the Regular RSI Divergence Convergence Strategy
- Implementation in MQL5
- Backtesting
- 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:

Bearish Divergence Setup:

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:

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.

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.

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.

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.

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.

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:

Backtest 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!
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Machine Learning Blueprint (Part 4): The Hidden Flaw in Your Financial ML Pipeline — Label Concurrency
Building a Smart Trade Manager in MQL5: Automate Break-Even, Trailing Stop, and Partial Close
Black-Scholes Greeks: Gamma and Delta
Price Action Analysis Toolkit Development (Part 47): Tracking Forex Sessions and Breakouts in MetaTrader 5
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use