Automating Trading Strategies in MQL5 (Part 38): Hidden RSI Divergence Trading with Slope Angle Filters
Introduction
In our previous article (Part 37), we developed a Regular RSI Divergence Convergence system in MetaQuotes Language 5 (MQL5) that detected regular bullish and bearish divergences between price swings and Relative Strength Index (RSI) values, executed trades on signals with optional risk controls, and provided on-chart visualizations for enhanced analysis. In Part 38, we develop a Hidden RSI Divergence Trading system with slope angle filters.
This system identifies hidden bullish and bearish divergences using swing points, applies clean checks with bar ranges and tolerance, filters signals via customizable slope angles on price and RSI lines, executes trades with risk management, and includes visual markers with angle displays on charts. We will cover the following topics:
By the end, you’ll have a functional MQL5 strategy for trading hidden RSI divergences, ready for customization—let’s dive in!
Understanding the Hidden RSI Divergence Strategy
The hidden RSI divergence strategy focuses on identifying trend continuation opportunities by detecting specific mismatches between price swings and the Relative Strength Index (RSI) oscillator, which highlights underlying momentum strength in ongoing trends. For hidden bullish divergence, the price establishes a higher low while the RSI forms a lower low, suggesting that bearish pullbacks are weakening and the uptrend may resume. For hidden bearish divergence, the price creates a lower high, but the RSI shows a higher high, indicating that bullish corrections are fading and the downtrend could persist.
We intend to enhance reliability by filtering divergences with slope angles on both price and RSI lines to confirm sufficient steepness or flatness, apply tolerance thresholds for clean patterns without breaches, and enter trades accordingly—buying on hidden bullish signals or selling on hidden bearish ones—with defined risk parameters like stops, profits, and trailing mechanisms. By leveraging these elements, we can pursue high-probability continuation setups in established trends. Have a look below at the different setups we could have.
Hidden Bullish Divergence Setup:

Hidden Bearish Divergence Setup:

Our plan is to detect swing highs and lows with confirmation strength, validate hidden divergences through clean checks within specified bar ranges and tolerance, apply optional slope angle filters on price and RSI for signal quality, execute automated trades with customizable lot sizing and risk controls, and provide visual aids like colored lines and labels with angle displays on both charts, building an effective system for hidden divergence trading. In brief, here is a visual representation of our objectives.

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 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
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. These are just the default settings. Feel free to customize them. 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, just like we did with the regular version.
For the "Price Divergence Filter" group, "Use_Price_Slope_Filter" defaults to false to optionally enable angle-based filtering, with "Price_Min_Slope_Degrees" at 10.0 and "Price_Max_Slope_Degrees" at 80.0 to define acceptable slope ranges in degrees (min disabled at 0, max at 90). Similarly, the "RSI Divergence Filter" has "Use_RSI_Slope_Filter" as true, with "RSI_Min_Slope_Degrees" at 1.0 and "RSI_Max_Slope_Degrees" at 89.0 for RSI line slopes. The rest of the parameters are identical to the previous regular version, except that we added the degrees filtering option.
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, but this time with a degree visualization.
//+------------------------------------------------------------------+ //| 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 the OnInit event handler, we begin by creating the RSI indicator handle with the iRSI function, 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. Upon initialization, 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 the ArraySize function 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.
//+------------------------------------------------------------------+ //| 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 }
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.
Next, we define the "CalculateVisualAngle" function to compute the visual slope angle in degrees between two points on the chart, aiding in divergence filtering. It takes "chart_id" for the chart identifier, "sub_window" to specify main or subwindow, along with "time1", "val1", "time2", and "val2" for the coordinates. We initialize pixel variables "x1", "y1", "x2", "y2" to zero, then convert chart time-price to XY pixels using ChartTimePriceToXY for both points, storing success in "ok1" and "ok2". If conversion fails or x-coordinates match, return 0.0; otherwise, calculate "dx" as "x2-x1" and "dy" as "y2-y1". For vertical lines where "dx" is zero, return -90.0 or 90.0 based on "dy" sign; else, compute the "angle" with MathArctan on "-dy / dx", convert to degrees by multiplying by 180.0 over M_PI, and return its absolute value via MathAbs for a positive slope measure. 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 hidden 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 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 the OnTick function, which executes on every new tick, we first use a static "last_time" variable to track the timestamp of the last processed bar, retrieving the current bar time with iTime passing _Symbol, _Period, and 0, exiting early if unchanged to prevent redundant calculations, then updating "last_time". We define a "data_size" of 200 bars for analysis and declare arrays for "high_data", "low_data", "rsi_data", and "time_data", filling them using CopyHigh, "CopyLow", "CopyTime", and CopyBuffer to obtain recent high prices, low prices, timestamps, and RSI values from "RSI_Handle".
We set these arrays as series with ArraySetAsSeries" for reverse indexing (recent first) and get the "chart_id" via "ChartID", along with locating the RSI subwindow index using ChartWindowFind based on the period-formatted name. To identify the latest swing highs, we initialize "last_high_bar" and "prev_high_bar" to -1, looping from 1 to "data_size - Swing_Strength" and invoking "CheckSwingHigh" to find the two most recent valid swing highs, setting them and breaking after the second.
If a new swing high is detected by comparing "time_data[last_high_bar]" to "Last_High_Time", we shift previous high variables to hold current last values, update "Last_High_Price", "Last_High_Time", and "Last_High_RSI" from the arrays, and assign "high_type" as "H" by default, or "HH" for higher high or "LH" for lower high if a previous exists. We then assess hidden bearish divergence: if "Prev_High_Price" is set, price shows a lower high, RSI a higher high, and bar difference ("prev_high_bar - last_high_bar") is within "Min_Bars_Between" and "Max_Bars_Between", we call "CleanDivergence" with previous and last RSI highs, their shifts, "rsi_data", and true for bearish.
If clean, we compute "price_angle" using "CalculateVisualAngle" on the main chart (subwindow 0) with high times and prices, and "rsi_angle" on the RSI subwindow with high times and RSI values. We verify slope filters: if not using price filter or "price_angle" is between "Price_Min_Slope_Degrees" and "Price_Max_Slope_Degrees", and similarly for RSI with "Use_RSI_Slope_Filter", "rsi_angle", "RSI_Min_Slope_Degrees", and "RSI_Max_Slope_Degrees", we set "hidden_bear_div" to true, draw divergence lines, and create a trend line named "DivLine_HiddenBear_" plus timestamp on the main chart with ObjectCreate connecting highs, configuring 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. If the "rsi_window" is valid, we similarly draw an RSI line named "DivLine_RSI_HiddenBear_" in the subwindow connecting RSI highs with matching settings.
For price swing labels if "Mark_Swings_On_Price" is true, we check for an existing text object named "SwingHigh_" plus timestamp with "ObjectFind"; if absent, create it via "ObjectCreate" at the high point, set text with "ObjectSetString" including "high_type" and " Hidden Bear Div " plus formatted "price_angle" if divergent, apply "Swing_High_Color", anchor "ANCHOR_LEFT_LOWER", and "Font_Size" using "ObjectSetInteger". If "Mark_Swings_On_RSI" is true and a subwindow is found, we do the same for an RSI label named "RSI_SwingHigh_" at the RSI high, with text including the "rsi_angle" if divergent, same color, anchor, and size. We conclude by redrawing the chart with the ChartRedraw function. Upon compilation, we get the following outcome.

Now that we can identify and visualize the hidden 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 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 } } }
We define the "OpenBuy" function to handle opening long positions upon detecting a hidden bullish divergence. We first retrieve the current ask price using SymbolInfoDouble, passing _Symbol and "SYMBOL_ASK". Then, if "SL_Pips" is greater than zero, we calculate the stop loss as "ask_price - SL_Pips * _Point" normalized to "_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" passing the symbol, ORDER_TYPE_BUY, "Lot_Size", zero 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 hidden bearish signals, mirroring the structure: get the bid price with "SymbolInfoDouble" passing "_Symbol" and "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 close existing trades before new ones, looping backward from the PositionsTotal function minus 1 to zero for safety. For each position, if PositionGetTicket with p returns a valid ticket greater than zero and matches our "_Symbol" via "PositionGetString" with "POSITION_SYMBOL" and "Magic_Number" with "PositionGetInteger" using "POSITION_MAGIC", we close it using "obj_Trade.PositionClose" on the ticket. We now just need to add the functions to close all positions and open the respective ones. Here is how we call them.
// Hidden bearish divergence detected - Sell signal CloseAll(); //--- Close all open positions OpenSell(); //--- Open sell position
Upon 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 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 }
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 } } } } } }
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 the PositionsTotal function minus 1 to zero to safely handle modifications without index shifts.
For each position, if PositionGetTicket passing 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.
//+------------------------------------------------------------------+ //| 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 }
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) { //--- 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 }
We define the OnDeinit event handler, which accepts a constant integer "reason" parameter indicating the cause of deinitialization, to perform cleanup operations when the Expert Advisor is unloaded from the chart. If "RSI_Handle" is not equal to INVALID_HANDLE, we retrieve the current chart ID using "ChartID" and construct the RSI indicator name by combining "RSI(" with the period converted via the IntegerToString function. We then locate the RSI subwindow with ChartWindowFind passing the chart ID and name; if found (rsi_subwin != -1), we remove the indicator from the chart using ChartIndicatorDelete with the chart ID, subwindow index, and name, logging the removal. Afterward, we release the indicator resources by calling IndicatorRelease with the RSI handle.
Next, we clear all chart objects by invoking the ObjectsDeleteAll function multiple times: first with subwindow 0 and prefix "DivLine_" to delete divergence lines, then "SwingHigh_" for swing high labels, "SwingLow_" for swing low labels, "RSI_SwingHigh_" for RSI high labels, and "RSI_SwingLow_" for RSI low labels. Finally, we log the deinitialization completion with "Print" to confirm the RSI Hidden Divergence EA has been properly shut down. 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 hidden RSI divergence trading system in MQL5 that identifies hidden bullish and bearish divergences through swing high and low points with strength confirmation, validates them using clean checks within bar ranges and tolerance, and filters signals with customizable slope angles on price and RSI lines for improved accuracy. The system executes trades with fixed lots, optional stop loss, and take profit in pips, plus trailing stops for dynamic risk management, and provides visual feedback via colored trend lines and labeled swing points with angle displays on both price and RSI charts.
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 hidden RSI divergence strategy, you’re equipped to trade continuation 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.
Optimizing Long-Term Trades: Engulfing Candles and Liquidity Strategies
Circle Search Algorithm (CSA)
Statistical Arbitrage Through Cointegrated Stocks (Part 7): Scoring System 2
MetaTrader 5 Machine Learning Blueprint (Part 5): Sequential Bootstrapping—Debiasing Labels, Improving Returns
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use