
Introduction to MQL5 (Part 11): A Beginner's Guide to Working with Built-in Indicators in MQL5 (II)
Introduction
Welcome back to our MQL5 series! We'll discuss a fascinating subject in this chapter that I believe you'll find very useful and fascinating. We looked at how to use MQL5's built-in indicators in the last chapter, paying particular attention to the RSI. Not only that, but we even worked on a practical project that showed you how to incorporate the RSI into your trading approach with ease. We're going one step further this time by including not one but three potent indications in our project (RSI, the Stochastic Oscillator, and the Moving Average). That's not all; you’ll also learn about the intriguing idea of hidden divergence detection through our project. We will specifically discuss how to spot hidden bullish and bearish divergences.
It's crucial to remember that the sole goal of this project is education. Building your confidence in utilizing MQL5 and learning how to recognize these patterns programmatically are its main objectives. Thus, let's get started on this practical project that will increase your understanding of MQL5 and enable you to advance your trading abilities.
In this article, you'll learn:
- How to examine RSI lows and highs in connection to the corresponding price movement to uncover hidden bullish and bearish divergences.
- Developing Expert Advisors using multiple indicators (RSI, MA, and Stochastic Oscillator).
- Identifying and visualizing hidden bullish and bearish divergences on the chart.
- Implementing risk management by calculating lot sizes based on dollar risk and stop-loss distance for inconsistent candle sizes when using built-in indicators.
- Automating trades with a predefined Risk-Reward Ratio (RRR) using divergence signals.
1. Setting up the Project
1.1. How the EA Works
The Moving Average and RSI indicators are used in this project to find hidden bullish and bearish divergences. While the RSI finds differences between RSI levels and corresponding prices, the MA assists in identifying the trend. The stochastic oscillator is only used to validate the trading signals in the end and not to identify divergence.
1.1.1. Hidden Bullish Divergence
At this point, the RSI forms a lower low (LL) while the price forms a higher low (HL). This divergence implies that the RSI is weak, suggesting possible upward momentum in the future, even though the matching price (C-Price) made a higher bottom.
1.1.2. Hidden Bearish Divergence
At this point, the RSI forms a higher high while the price forms a lower high. Suggesting possible downward momentum in the future.
1.1.3. Logic for Sell
Trend Filter:
- Ensure the current price is below the 200 EMA to confirm a bearish trend.
Mark the Last Significant RSI High:
- Identify the most recent RSI high formed after a bullish candle, then followed by 3 consecutive bearish candles.
- Place a horizontal line on the RSI chart at this level.
Mark Corresponding Price:
- On the price chart (switch to a line chart), place a horizontal line at the corresponding price (C-PRICE) level of the RSI high.
Hidden Bearish Divergence:
- Look for a new RSI high above the RSI horizontal line.
- Ensure the corresponding price high is below the price horizontal line on the chart.
Stochastic Confirmation:
- After all the conditions are met, wait for the Stochastic to cross above the overbought level (e.g., 80) and then cross below it again within 11 bars.
- If confirmation doesn’t occur within 11 bars, no trade is placed.
Set SL and TP:
- SL: Last swing high.
- TP: Defined by an input parameter for the Risk-Reward Ratio (RRR).
1.1.4. Logic for Buy
Trend Filter:
- Make sure the current price is above the 200 EMA.
Mark the Last Significant RSI Low:
- Identify the most recent RSI low formed after a bearish candle, thenfollowed by 3 consecutive bullish candles.
- Place a horizontal line on the RSI chart at this level.
Mark Corresponding Price:
- On the price chart (switch to a line chart), place a horizontal line at the corresponding price (C-PRICE) level of the RSI low.
Hidden Bullish Divergence:
- Look for a new RSI low below the RSI horizontal line.
- Ensure the corresponding price low is above the price (C-PRICE) horizontal line on the chart.
Stochastic Confirmation:
- After all the conditions are met, wait for the Stochastic to cross below the oversold level (e.g., 20) and then cross above it again within 11 bars.
- If confirmation doesn’t occur within 11 bars, no trade is placed.
Set SL and TP:
- SL: Last swing low.
- TP: Defined by an input parameter for the Risk-Reward Ratio (RRR).
2. Adding Indicator Properties and Retrieving Data
2.1. Setting up Indicator Properties
We'll set up the properties of the indicators that we will be utilizing in our project: the Stochastic Oscillator, RSI, and Moving Average. These properties offer freedom when designing the EA and specify how the indicators will behave.
Analogy
Consider yourself putting together a toolkit for a specific task. Similar to a measuring tape, the moving average makes sure you comprehend the task's overall dimensions (trend direction). By acting as a magnifying glass, the RSI assists you in spotting the smallest nuances or hidden divergences. Before proceeding to the following stage (final confirmation), the Stochastic Oscillator serves as a leveling tool to make sure everything is in perfect alignment.
Consider the computer to be your helper in this endeavor. You have to explain to it exactly what these tools are and how they should function before you can ask it to utilize them. A generic tool cannot be handed over and expected to know what to do. Rather, you must specify each tool's characteristics, such as the kind of measuring tape, the magnifying glass magnification level, or the level tool's sensitivity. By configuring these features, the computer is guaranteed to understand exactly which indicators to use and how to use them efficiently.
Examples://DECLEAR INDICATOR HANDLES int ma_handle; int rsi_handle; int stoch_handle; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //INDICATOR PROPERTIES ma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE); rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE); stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH); return(INIT_SUCCEEDED); }
Explanation:
Consider yourself in a workshop with three specialized pieces of equipment for your project: a tape measure (MA), a magnifying glass (RSI), and a spirit level (Stochastic Oscillator). Make sure every tool is ready to use and set up correctly before getting started. You wouldn't simply take them out of the toolbox and use them right now!
Declaring Indicator Handles (Preparing the Tools)
int ma_handle; int rsi_handle; int stoch_handle;
It's similar to naming the cabinets in your workshop where your tools will be kept. Each tool must have its own location so that you can locate it when you need it.
- ma_handle is for the tape measure (Moving Average).
- rsi_handle is for the magnifying glass (RSI).
- stoch_handle is for the spirit level (Stochastic Oscillator).
Setting up the Tools (Initializing Indicators)
iMA (Moving Average) Setupma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE);
This is like telling your assistant, “I need to set up the tape measure for a project.” You're specifying exactly how to use it:
- _Symbol: This lets the assistant know what you're measuring, such as the asset, stock, or currency pair you're dealing with. This describes the asset you're studying (for example, EUR/USD), much as you would identify what you're measuring with your tape measure.
- PERIOD_CURRENT: This refers to the time frame you're working with. Think of it as the length of the tape measure — are you measuring the past day, week, or month? PERIOD_CURRENT means you're working with the current chart's time frame.
- 200: This is the length of the tape measure (the number of candles the Moving Average will cover). So, you're asking your assistant to use a tape measure that looks at the past 200 periods to measure the overall trend.
- 0: This is the shift. It indicates how much you want to shift the measurement to the right or left. Here, 0 means no shift — you're measuring the current price and the past 200 periods directly.
- MODE_EMA: This specifies the type of tape measure you're using: Exponential Moving Average (EMA). EMA is more sensitive and reacts faster to price changes than a simple moving average. So, you're choosing a tape measure that adapts quickly to new data.
- PRICE_CLOSE: This is the reference point for your tape measure. In this case, you're measuring based on the closing price of each period. Just like a tape measure could be calibrated to measure specific distances, you're telling your assistant to measure the closing prices specifically.
iRSI (Relative Strength Index) Setup
rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);
Now, you’re setting up the magnifying glass (RSI). You tell your assistant exactly how to use it:
- _Symbol: This works the same way as before, specifying the asset you're analyzing.
- PERIOD_CURRENT: Just like the Moving Average, you're working with the time frame of the current chart.
- 14: This is the period for the RSI. Think of it like setting the magnifying power of your magnifying glass. You're telling your assistant to focus on 14 periods (or candles), helping you analyze the relative strength and detect divergence.
- PRICE_CLOSE: Just as with the Moving Average, you're focusing on the closing prices to calculate the RSI. This ensures you're looking at the endpoint of each period to get an accurate reading.
iStochastic (Stochastic Oscillator) Setup
stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH);
- _Symbol: Again, this specifies which asset you’re analyzing, just like the previous indicators.
- PERIOD_CURRENT: You're working with the current chart's time frame.
- 5, 3, and 3: These are the parameters for the Stochastic Oscillator:
- 5 is the %K period: This is like setting how sensitive the spirit level (Stochastic Oscillator) is to price movements. It checks the last 5 periods to see where the price is relative to its range.
- 3 is the %D period: This is the smoothed version of %K, often used to get a clearer signal.
- 3 is the Slowing period: This further smooths the indicator to make sure it’s not too reactive to small changes, helping to avoid false signals.
- MODE_SMA: You're asking the spirit level to use a Simple Moving Average (SMA) method for smoothing the %K and %D values, which is a basic but reliable way of averaging data.
- STO_LOWHIGH: This tells the assistant to use the Low/High method for calculating the Stochastic Oscillator. It means you're focusing on the price range (lowest low and highest high) during each period, rather than using closing prices.
By telling the computer these parameters, you're making sure each of your indicators (tools) is properly calibrated to perform its job effectively and accurately in the project.
3. Retrieving Indicators and Candlestick Data
3.1. Retrieving Indicators Data
The data extraction from the indicators that we established in the previous section will be the main emphasis of this section. We can utilize the indicator data to inform our Expert Advisor's trading recommendations by retrieving it. We'll also examine how to extract information from the Stochastic Oscillator, RSI, and Moving Average (MA). These factors are crucial because they give our EA the cues it needs to place trades. Our EA has to get the relevant data from these indicators to ascertain whether the conditions for trading are met, just as a builder needs precise measurements from their equipment to make sure a project is moving forward well. We will examine how to effectively call and store these variables in the following phases so that the EA may use real-time market data to inform its judgments.
Example:
//DECLEAR INDICATOR HANDLES int ma_handle; int rsi_handle; int stoch_handle; //DECLEAR DOUBLE VARIABLES THAT STORES INDICATORS DATA double ma_buffer[]; double rsi_buffer[]; double stoch_buffer_k[], stoch_buffer_d[]; //DECLEAR VARIABLES TO STORE CANDLE DATA double open[]; double close[]; double high[]; double low[]; datetime time[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //INDICATOR PROPERTIES ma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE); rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE); stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH); //START FROM THE LATEST CANDLE ON THE CHART ArraySetAsSeries(ma_buffer,true); ArraySetAsSeries(rsi_buffer,true); ArraySetAsSeries(stoch_buffer_k,true); ArraySetAsSeries(stoch_buffer_d,true); //START FROM THE LATEST CANDLE ON THE CHART ArraySetAsSeries(open,true); ArraySetAsSeries(close,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(time,true); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //COPY INDICATOR'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART CopyBuffer(ma_handle,0,1,30,ma_buffer); CopyBuffer(stoch_handle,0,1,30,stoch_buffer_k); CopyBuffer(stoch_handle,1,1,30,stoch_buffer_d); CopyBuffer(rsi_handle,0,1,30,rsi_buffer); //COPY CANDLE'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART CopyOpen(_Symbol,PERIOD_CURRENT,1,30,open); CopyClose(_Symbol,PERIOD_CURRENT,1,30,close); CopyHigh(_Symbol,PERIOD_CURRENT,1,30,high); CopyLow(_Symbol,PERIOD_CURRENT,1,30,low); CopyTime(_Symbol,PERIOD_CURRENT,1,30,time); }
Explanation:
Declare Double Variables that Store Indicator Data
Arrays must be declared before the indication data can be stored. Since the numerical values from the indicators — which include the Stochastic Oscillator, RSI, and Moving Average — will be stored in these arrays, they are of type double. The ma_buffer[] will hold values from the Moving Average (MA), while the rsi_buffer[] retains values from the RSI. The stoch_buffer_k[] and stoch_buffer_d[] arrays specifically store the stochastic oscillator K and D lines. These arrays are initially empty and will quickly fill with the indicator data we get from the graphic.
Starting from the Latest Candle on the Chart
In this step, we use the function ArraySetAsSeries() to arrange the arrays so that the most recent data will be in the first index of each array. By passing true as the argument, we are telling the program to start populating the arrays from the most recent values. This is important because, by default, arrays are filled from the oldest data (the first element), but in this case, we need to ensure that the most recent indicator values are accessible first. This arrangement makes it easier for us to work with the latest data when making trading decisions.
Copying Indicator Data into Arrays
Once the arrays are set up, we use the CopyBuffer() function to retrieve the actual indicator data from the chart. The CopyBuffer() function copies the data from the indicator’s buffer into our previously declared arrays. Each call to CopyBuffer() is specific to one indicator:
- For the Moving Average, we copy the data from ma_handle and place it in ma_buffer[].
- We retrieve both the %K and %D values for the Stochastic Oscillator, storing them in stoch_buffer_k[] and stoch_buffer_d[] respectively.
- We copy the RSI's data into rsi_buffer[].
The function is instructed to transfer data from the last 30 bars by the fourth input, 30, and to begin with the second-most recent bar by the third parameter, this enables us to extract from the indicators the historical data required for our Expert Advisor's analysis and decision-making.
In summary, these steps ensure that we can collect, store, and access the data from our indicators. By declaring the arrays, setting the order of data correctly, and using CopyBuffer() to retrieve the data, we have everything in place to begin analyzing the indicator values and making trading decisions based on those values.
3.2. Candlestick Data
We must now collect the matching candlestick data to match the indicator values after successfully retrieving the data from the indicators. Because it gives us the context in which to apply our indicators, candlestick data is crucial for trading decision-making. The candlesticks show us the real market price activity, just as the indicators tell us about trends and momentum. We can find possible trade signals, including the development of bullish or bearish divergences or other patterns that might point to a change in market mood, by integrating indicator and candlestick data.
Examples:
double open[]; double close[]; double high[]; double low[]; datetime time[];
We declare arrays (open[], close[], high[], low[], and time[]) to store essential candle information such as opening, closing, highest, and lowest prices, as well as their corresponding timestamps. These variables help us analyze price movements and correlate them with the indicator data retrieved earlier.
ArraySetAsSeries(open,true); ArraySetAsSeries(close,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(time,true);To ensure the most recent candle is at the beginning of each array, we use the ArraySetAsSeries function. This rearranges the data, so the latest candle is at index 0, with older candles following in sequence. This alignment simplifies the analysis and ensures consistency with the indicator data structure.
CopyOpen(_Symbol,PERIOD_CURRENT,1,30,open); CopyClose(_Symbol,PERIOD_CURRENT,1,30,close); CopyHigh(_Symbol,PERIOD_CURRENT,1,30,high); CopyLow(_Symbol,PERIOD_CURRENT,1,30,low); CopyTime(_Symbol,PERIOD_CURRENT,1,30,time);
Using functions like CopyOpen, CopyClose, CopyHigh, CopyLow, and CopyTime, we populate the arrays with actual market data. We retrieve data for the last 30 candles starting from the second candle on the chart, aligning the price data with the corresponding indicator values for accurate decision-making.
4. Identifying the Latest RSI Highs and Lows with Corresponding Data
As criteria for comparing indicator behavior with price action, it is crucial to locate the most recent RSI highs and lows. The market may continue to increase when there is a hidden bullish divergence, which occurs when the price makes a higher low and the RSI makes a lower low. However, there is a hidden bearish divergence that points to a possible downward continuation when the price reaches a lower high and the RSI reaches a higher high.
To determine whether the high was below the MA at that time or if the low was above it, we will also obtain the matching Moving Average number. This extra validation guarantees that the trend is in line with the anticipated movement and aids in validating the divergence signals.
4.1. Finding Hidden Bullish Divergence
Identifying the latest RSI low is a critical step in detecting hidden bullish divergences. The RSI low represents a point where momentum weakens during a price retracement, and analyzing it alongside the corresponding price and Moving Average (MA) values provides valuable insights. A hidden bullish divergence occurs when the RSI forms a lower low while the price creates a higher low, indicating potential upward momentum despite the retracement.
To detect the RSI low, we will define a specific logic: the low must be formed after at least one bearish movement followed by three consecutive bullish movements. This condition ensures the low reflects a meaningful shift in momentum. Once the RSI low is identified, we will retrieve its position, note the corresponding price on the chart at that point, and extract the Moving Average value to determine if the price low was above the MA. This validation helps confirm the alignment of the divergence signal with the prevailing trend, enhancing its reliability.
After identifying the RSI low, we will also define logic to search for a lower low that may form after the RSI low. This lower low must have an RSI value below the identified RSI low, with the price at that point remaining above the previous price low. This ensures the divergence remains intact and supports the hidden bullish divergence criteria. By integrating these steps, we establish a comprehensive and structured approach to reliably identifying hidden bullish divergence opportunities.
Example://DECLEAR INDICATOR HANDLES int ma_handle; int rsi_handle; int stoch_handle; //DECLEAR DOUBLE VARIABLES THAT STORES INDICATORS DATA double ma_buffer[]; double rsi_buffer[]; double stoch_buffer_k[], stoch_buffer_d[]; //DECLEAR VARIABLES TO STORE CANDLE DATA double open[]; double close[]; double high[]; double low[]; datetime time[]; //DECLEAR VARIBLE ROR RSI LOW AND CORESPONDING CANDLE double rsi_low_value; // Stores the RSI value at the identified low point. double corresponding_low_value; // Stores the price (closing value) corresponding to the RSI low. double corresponding_low_ma; // Stores the Moving Average value at the same point. datetime rsi_low_time; // Stores the timestamp of the RSI low. //DECLEAR VARIABLE FOR THE MINIMUM CORRESPONDING PRICE VALUE int minimum_value_low; //DECLEAR VARIBLE ROR NEW RSI LOW AND CORESPONDING CANDLE double new_rsi_low_value; datetime new_rsi_low_time; double new_corresponding_low_value; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //INDICATOR PROPERTIES ma_handle = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_EMA,PRICE_CLOSE); rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE); stoch_handle = iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH); //START FROM THE LATEST CANDLE ON THE CHART ArraySetAsSeries(ma_buffer,true); ArraySetAsSeries(rsi_buffer,true); ArraySetAsSeries(stoch_buffer_k,true); ArraySetAsSeries(stoch_buffer_d,true); //START FROM THE LATEST CANDLE ON THE CHART ArraySetAsSeries(open,true); ArraySetAsSeries(close,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(time,true); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //COPY INDICATOR'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART CopyBuffer(ma_handle,0,1,30,ma_buffer); CopyBuffer(stoch_handle,0,1,30,stoch_buffer_k); CopyBuffer(stoch_handle,1,1,30,stoch_buffer_d); CopyBuffer(rsi_handle,0,1,30,rsi_buffer); //COPY CANDLE'S DATA FROM THE SECOND BAR ON THE CHART TO THE LAST 31st BAR ON THE CHART CopyOpen(_Symbol,PERIOD_CURRENT,1,30,open); CopyClose(_Symbol,PERIOD_CURRENT,1,30,close); CopyHigh(_Symbol,PERIOD_CURRENT,1,30,high); CopyLow(_Symbol,PERIOD_CURRENT,1,30,low); CopyTime(_Symbol,PERIOD_CURRENT,1,30,time); //LOOP THROUGH THE LAST 30 BARS ON THE CHART for(int i = 0; i < 30; i++) { //PREVENT ARRAY OUT OF RANGE ERROR if((i+1 < 30) && (i+2 < 30) && (i < 30) && (i+3 < 30) && (i+4 < 30)) { //LOGIC TO IDENTIFY THE LATEST RSI LOW if(rsi_buffer[i+4] > rsi_buffer[i+3] && rsi_buffer[i+2] > rsi_buffer[i+3] && rsi_buffer[i+1] > rsi_buffer[i+2] && rsi_buffer[i] > rsi_buffer[i+1]) { //GETTING LATEST RSI LOW, CORRESPONDING CANDLE LOW, CORRESPONDING MA VALUE, and RSI TIME rsi_low_value = rsi_buffer[i+3]; corresponding_low_value = close[i+3]; corresponding_low_ma = ma_buffer[i+3]; rsi_low_time = time[i+3]; break; } } } //TOTAL NUMBERS OF BARS FROM THE LAST SIGNIFICANT RSI LOW int total_bars_2 = 0; total_bars_2 = Bars(_Symbol,PERIOD_CURRENT,rsi_low_time, time[0]); //MINIMUM CLOSE PRICE FROM THE LAST SIGNIFICANT RSI LOW minimum_value_low = ArrayMinimum(close,0,total_bars_2); if(corresponding_low_value > corresponding_low_ma && close[0] > ma_buffer[0] && close[minimum_value_low] >= corresponding_low_value) { //CREATE LINES TO MARK RSI AND CORRESPONDING CANDLE LOW ObjectCreate(ChartID(),"RSI LOW VALUE",OBJ_TREND,1,rsi_low_time,rsi_low_value,TimeCurrent(),rsi_low_value); ObjectCreate(ChartID(),"C-CANDLE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,TimeCurrent(),corresponding_low_value); //SETTING OBJECTS COLOUR ObjectSetInteger(ChartID(),"RSI LOW VALUE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"C-CANDLE",OBJPROP_COLOR,clrBlack); //CREATE TWO LINES TO CONNECT RSI LOW AND THE CORRESPONDING PRICE ON THE CHART ObjectCreate(ChartID(),"C-CANDLE LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,rsi_low_time,0); ObjectCreate(ChartID(),"RSI LOW LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,rsi_low_time,100); //SETTING OBJECTS COLOUR ObjectSetInteger(ChartID(),"C-CANDLE LINE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"RSI LOW LINE",OBJPROP_COLOR,clrBlack); //CREATE TEXTS TO MART RSI LOW AND CORRESPONDING PRICE (C-PRICE) ObjectCreate(ChartID(),"C-CANDLE TEXT",OBJ_TEXT,0,TimeCurrent(),corresponding_low_value); ObjectSetString(ChartID(),"C-CANDLE TEXT",OBJPROP_TEXT,"C-PRICE"); ObjectCreate(ChartID(),"RSI LOW TEXT",OBJ_TEXT,1,TimeCurrent(),rsi_low_value); ObjectSetString(ChartID(),"RSI LOW TEXT",OBJPROP_TEXT,"RSI LOW"); //SETTING TEXT COLOUR ObjectSetInteger(ChartID(),"C-CANDLE TEXT",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"RSI LOW TEXT",OBJPROP_COLOR,clrBlack); //19 LOGIC TO GET THE NEW RSI LOW if(rsi_buffer[0] > rsi_buffer[1] && rsi_buffer[1] < rsi_buffer[2] && rsi_buffer[1] < rsi_low_value && close[1] > corresponding_low_value) { new_rsi_low_value = rsi_buffer[1]; new_rsi_low_time = time[1]; new_corresponding_low_value = close[1]; } //CONDITION FOR HIDDEN BULLISH DIVERGENCE if(rsi_low_value > new_rsi_low_value && corresponding_low_value < new_corresponding_low_value) { ObjectCreate(ChartID(),"RSI LOW TREND LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,new_rsi_low_time,new_rsi_low_value); ObjectCreate(ChartID(),"L",OBJ_TEXT,1,rsi_low_time,rsi_low_value); ObjectSetString(ChartID(),"L",OBJPROP_TEXT,"L"); ObjectSetInteger(ChartID(),"L",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"RSI LOW TREND LINE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"L",OBJPROP_COLOR,clrBlack); ObjectCreate(ChartID(),"LL",OBJ_TEXT,1,new_rsi_low_time,new_rsi_low_value); ObjectSetString(ChartID(),"LL",OBJPROP_TEXT,"LL"); ObjectSetInteger(ChartID(),"LL",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"LL",OBJPROP_COLOR,clrBlack); //for candle ObjectCreate(ChartID(),"C-CANDLE TREND LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,new_rsi_low_time,new_corresponding_low_value); ObjectCreate(ChartID(),"CL",OBJ_TEXT,0,rsi_low_time,corresponding_low_value); ObjectSetString(ChartID(),"CL",OBJPROP_TEXT,"L"); ObjectSetInteger(ChartID(),"CL",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"C-CANDLE TREND LINE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"CL",OBJPROP_COLOR,clrBlack); ObjectCreate(ChartID(),"CLL",OBJ_TEXT,0,new_rsi_low_time,new_corresponding_low_value); ObjectSetString(ChartID(),"CLL",OBJPROP_TEXT,"HL"); ObjectSetInteger(ChartID(),"CLL",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"CLL",OBJPROP_COLOR,clrBlack); } } //LOGIC TO DELETE THE OBJECTS WHEN ITS NO LONGER RELEVEANT else if((close[0] < ma_buffer[0]) || (close[minimum_value_low] < corresponding_low_value)) { ObjectDelete(ChartID(),"RSI LOW VALUE"); ObjectDelete(ChartID(),"C-CANDLE"); ObjectDelete(ChartID(),"C-CANDLE LINE"); ObjectDelete(ChartID(),"RSI LOW LINE"); ObjectDelete(ChartID(),"C-CANDLE TEXT"); ObjectDelete(ChartID(),"RSI LOW TEXT"); ObjectDelete(ChartID(),"RSI LOW TREND LINE"); ObjectDelete(ChartID(),"L"); ObjectDelete(ChartID(),"LL"); ObjectDelete(ChartID(),"C-CANDLE TREND LINE"); ObjectDelete(ChartID(),"CL"); ObjectDelete(ChartID(),"RSI LOW TEXT"); ObjectDelete(ChartID(),"CLL"); } }
Explanation:
Potential divergences can be found by using the rsi_low_value, which records the lowest RSI value inside a given range. Since line charts are based on closing prices, compatibility with them is ensured by the corresponding_low_value, which is the closing price of the candle where the RSI low occurs. When converting to a line chart, this alignment makes the RSI lows and associated price points look logical. To ascertain whether the price is above or below the Moving Average (MA), which frequently validates trends, the corresponding_low_ma holds the MA value at the same location. Finally, the rsi_low_time function allows for the construction of objects such as trend lines or labels associated with particular candles by logging the precise time of the RSI low.
The next step is to identify the last significant RSI low. This involves checking for specific patterns in RSI values where there is at least one bearish movement followed by three consecutive bullish movements.
- The code scans the last 30 bars for a significant RSI low.
- Key Condition:
- The RSI value at i+3 must first drop (bearish movement from i+4 to i+3).
- After that, it must show three consecutive increases (i+3 → i+2 → i+1 → i).
- Once this pattern is detected, the RSI low, corresponding closing price, Moving Average value, and timestamp are saved.
This logic ensures that the low is part of meaningful RSI behavior, where a decline in RSI is followed by recovery, making it a significant turning point.
Why Use close[i+3]?
If you use a line chart, the line is plotted using closing prices. By associating RSI lows with the closing price, the markers you create (e.g., trend lines) will align correctly with the line chart. This ensures clarity and visual consistency.
if(corresponding_low_value > corresponding_low_ma && close[0] > ma_buffer[0] && close[minimum_value_low] >= corresponding_low_value)
This condition checks several factors to confirm whether the current situation aligns with the criteria for detecting a hidden bullish divergence:
- corresponding_low_value > corresponding_low_ma: This checks if the price at the identified RSI low is above the Moving Average (MA). It suggests that the price is above its moving average, which indicates that the trend is still relatively strong or bullish.
- close[0] > ma_buffer[0]: This checks if the most recent closing price (current candle) is above the moving average value, reinforcing that the price is still above the MA and in an uptrend.
- close[minimum_value_low] >= corresponding_low_value: This confirms that the price at the most recent significant low (the one found earlier) is still at or higher than the previous significant low. This helps to ensure that the price is not falling below the previous low, indicating higher lows in price and supporting the bullish interpretation.
When all these conditions are true, it suggests that the price is above its moving average and has formed a higher low, which is a key characteristic of a potential hidden bullish divergence, where the price action does not confirm the deeper bearish momentum suggested by the RSI.
//CREATE LINES TO MARK RSI AND CORRESPONDING PRICE ObjectCreate(ChartID(),"RSI LOW VALUE",OBJ_TREND,1,rsi_low_time,rsi_low_value,TimeCurrent(),rsi_low_value); ObjectCreate(ChartID(),"C-CANDLE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,TimeCurrent(),corresponding_low_value); //SETTING OBJECTS COLOUR ObjectSetInteger(ChartID(),"RSI LOW VALUE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"C-CANDLE",OBJPROP_COLOR,clrBlack); //CREATE TWO LINES TO CONNECT RSI LOW AND THE CORRESPONDING PRICE ON THE CHART ObjectCreate(ChartID(),"C-CANDLE LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,rsi_low_time,0); ObjectCreate(ChartID(),"RSI LOW LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,rsi_low_time,100); //SETTING OBJECTS COLOUR ObjectSetInteger(ChartID(),"C-CANDLE LINE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"RSI LOW LINE",OBJPROP_COLOR,clrBlack); //CREATE TEXTS TO MART RSI LOW AND CORRESPONDING PRICE (C-PRICE) ObjectCreate(ChartID(),"C-CANDLE TEXT",OBJ_TEXT,0,TimeCurrent(),corresponding_low_value); ObjectSetString(ChartID(),"C-CANDLE TEXT",OBJPROP_TEXT,"C-PRICE"); ObjectCreate(ChartID(),"RSI LOW TEXT",OBJ_TEXT,1,TimeCurrent(),rsi_low_value); ObjectSetString(ChartID(),"RSI LOW TEXT",OBJPROP_TEXT,"RSI LOW"); //SETTING TEXT COLOUR ObjectSetInteger(ChartID(),"C-CANDLE TEXT",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"RSI LOW TEXT",OBJPROP_COLOR,clrBlack);
This code creates visual elements on the chart to highlight the RSI low and its corresponding price, which are key points for identifying potential divergence patterns. First, trend lines are drawn to mark both the RSI low value and the corresponding price (C-PRICE) at the same point. These lines, labeled "RSI LOW VALUE" and "C-CANDLE" are drawn in black to maintain visual clarity. The purpose of these trend lines is to visually connect the RSI low with the corresponding price, which helps you easily identify the relationship between the price action and the RSI behavior.
Next, additional trend lines are created to connect the RSI low to the corresponding price on the chart. The "C-CANDLE LINE" connects the price at the RSI low to the current time, while the "RSI LOW LINE" extends the RSI low vertically on the chart. These lines serve as visual markers that help analyze the movement of both the RSI and the price, especially when looking for potential bullish or bearish divergence. All of these trend lines are set to the color black to maintain consistency and ensure that they are clearly visible against the chart's background.
Lastly, the code creates text labels to annotate the chart with the values of the RSI low and the corresponding price (candle low). The labels "RSI LOW" and "C-PRICE" are placed at the respective levels of the RSI and price, making it easy to identify these key points directly on the chart. These text labels are also set to black, ensuring they stand out and are legible. By creating these objects, the code helps traders quickly visualize important price levels and RSI points, aiding in technical analysis and the identification of hidden divergences.
OUTPUT:
Once the first significant RSI low is identified, the next step is to find a new lower RSI low. The logic here is simpler than for the first low.
Logic for the New RSI Low:
For the new RSI low:
- There must be only one bearish movement, where RSI decreases for one bar.
- This must be followed by one bullish movement, where RSI starts increasing immediately after the low.
if(rsi_buffer[0] > rsi_buffer[1] && rsi_buffer[1] < rsi_buffer[2] && rsi_buffer[1] < rsi_low_value && close[1] > corresponding_low_value) { new_rsi_low_value = rsi_buffer[1]; new_rsi_low_time = time[1]; new_corresponding_low_value = close[1]; }
The first part of the code declares variables to store information about the new RSI low and its corresponding price level. The new_rsi_low_value variable holds the value of the new RSI low, which is necessary for comparison and analysis of divergence. The new_rsi_low_time stores the time when the new RSI low occurs, and this timestamp is important for marking this point on the chart. Finally, new_corresponding_low_value stores the price associated with the new RSI low, typically the closing price, which helps to connect the RSI movements with actual market price action.
The requirement for determining the new RSI low is defined in the second section of the code. The reasoning guarantees that the new low satisfies two requirements: a bearish movement (where the RSI falls) and a bullish movement (where the RSI begins to rise). Furthermore, the matching price must produce a higher low, suggesting possible hidden bullish divergence, while the new RSI low must be lower than the prior major RSI low. To emphasize the divergence on the chart later on, the algorithm assigns the new values for the RSI low, its timestamp, and the associated price if these criteria are satisfied.
if(rsi_low_value > new_rsi_low_value && corresponding_low_value < new_corresponding_low_value)
This condition looks for a hidden bullish divergence between the price chart and the RSI. When the price makes a higher low and the RSI makes a lower low, it is known as hidden bullish divergence.
//FOR RSI ObjectCreate(ChartID(),"RSI LOW TREND LINE",OBJ_TREND,1,rsi_low_time,rsi_low_value,new_rsi_low_time,new_rsi_low_value); ObjectCreate(ChartID(),"L",OBJ_TEXT,1,rsi_low_time,rsi_low_value); ObjectSetString(ChartID(),"L",OBJPROP_TEXT,"L"); ObjectSetInteger(ChartID(),"L",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"RSI LOW TREND LINE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"L",OBJPROP_COLOR,clrBlack); ObjectCreate(ChartID(),"LL",OBJ_TEXT,1,new_rsi_low_time,new_rsi_low_value); ObjectSetString(ChartID(),"LL",OBJPROP_TEXT,"LL"); ObjectSetInteger(ChartID(),"LL",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"LL",OBJPROP_COLOR,clrBlack); //FOR CORRESPONDING PRICE ObjectCreate(ChartID(),"C-CANDLE TREND LINE",OBJ_TREND,0,rsi_low_time,corresponding_low_value,new_rsi_low_time,new_corresponding_low_value); ObjectCreate(ChartID(),"CL",OBJ_TEXT,0,rsi_low_time,corresponding_low_value); ObjectSetString(ChartID(),"CL",OBJPROP_TEXT,"L"); ObjectSetInteger(ChartID(),"CL",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"C-CANDLE TREND LINE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"CL",OBJPROP_COLOR,clrBlack); ObjectCreate(ChartID(),"CLL",OBJ_TEXT,0,new_rsi_low_time,new_corresponding_low_value); ObjectSetString(ChartID(),"CLL",OBJPROP_TEXT,"HL"); ObjectSetInteger(ChartID(),"CLL",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"CLL",OBJPROP_COLOR,clrBlack);
Once a hidden bullish divergence is identified, this block of code is intended to mark important chart aspects graphically. In the first set of actions, the RSI is the main emphasis. The first important RSI low and the subsequent lower RSI low are represented by trend lines and text labels. The code initially establishes a trend line between the new lower RSI low (new_rsi_low_value) and the original RSI low (rsi_low_value). Traders can more easily spot the RSI's downward trend thanks to the trend line. In these instances, text labels "L" and "LL" are also added to identify them as notable lows; "L" denotes the initial low, and "LL" denotes the lower low.
The next set of actions is focused on the corresponding price levels. Similar to the RSI, trend lines and text labels are created to connect the first price low (corresponding_low_value) to the new price low (new_corresponding_low_value). The corresponding price is expected to form a higher low, which is critical for the hidden bullish divergence setup. The code creates a trend line between these two price points and labels them with "L" and "HL," where "L" marks the first significant price low and "HL" denotes the higher low formed by the price. These visual elements help you confirm that the price movement is showing the expected higher low while the RSI shows a lower low, a classic indicator of hidden bullish divergence.
The ObjectDelete() function is responsible for cleaning up the visual objects on the chart when the conditions for hidden bullish divergence are no longer valid. Specifically, it checks if the current closing price is below the moving average (ma_buffer[0]) or if the minimum closing price since the last significant RSI low is lower than the corresponding price low (corresponding_low_value). If either of these conditions is met, the previously drawn trend lines, text labels, and other objects marking the RSI and price lows are deleted. This ensures that the chart remains clear of outdated or irrelevant visual markers, allowing traders to focus on the most current and valid market conditions.
OUTPUT:
4.2. Finding Hidden Bearish Divergence
A hidden bearish divergence occurs when the RSI forms a higher high while the price creates a lower high, signaling potential downward momentum despite temporary price recovery. To identify this pattern, the latest RSI high is detected using a specific logic: it must occur after at least one bullish movement followed by three consecutive bearish movements, indicating a meaningful peak in momentum. Once the RSI high is identified, its corresponding price and Moving Average (MA) value are retrieved to confirm alignment with the prevailing bearish trend. A valid hidden bearish divergence requires the price high to remain below the MA, reinforcing the downward bias.
Unlike hidden bullish divergence, where the RSI forms a lower low and the price creates a higher low (signaling potential upward momentum), hidden bearish divergence works in the opposite direction. After identifying the RSI high, the logic also includes checking for a new RSI high that exceeds the first, with the corresponding price forming a lower high. This divergence highlights weakening bullish momentum, in contrast to hidden bullish divergence, which indicates weakening bearish momentum. These distinctions ensure a structured approach for detecting and differentiating both types of divergences effectively.
Example:
//DECLEAR VARIBLE ROR RSI HIGH AND CORESPONDING PRICE double rsi_high_value; double corresponding_high_value; double corresponding_high_ma; datetime rsi_high_time; //DECLEAR VARIABLE FOR THE MAXIMUM CORRESPONDING PRICE VALUE int maximum_value_high; // DECLEAR VARIBLE ROR NEW RSI HIGH AND CORESPONDING PRICE double new_rsi_high_value; datetime new_rsi_high_time; double new_corresponding_high_value;
//LOOP THROUGH THE LAST 30 BARS ON THE CHART for(int i = 0; i < 30; i++) { //PREVENT ARRAY OUT OF RANGE ERROR if((i+1 < 30) && (i+2 < 30) && (i < 30) && (i+3 < 30) && (i+4 < 30)) { //LOGIC TO IDENTIFY THE LATEST RSI HIGH if(rsi_buffer[i+4] < rsi_buffer[i+3] && rsi_buffer[i+2] < rsi_buffer[i+3] && rsi_buffer[i+1] < rsi_buffer[i+2] && rsi_buffer[i] < rsi_buffer[i+1]) { //GETTING LATEST RSI HIGH, CORRESPONDING PRICE, CORRESPONDING MA VALUE, and RSI TIME rsi_high_value = rsi_buffer[i+3]; corresponding_high_value = close[i+3]; corresponding_high_ma = ma_buffer[i+3]; rsi_high_time = time[i+3]; break; } } } //TOTAL NUMBERS OF BARS FROM THE LAST SIGNIFICANT RSI HIGH int total_bars_3 = 0; total_bars_3 = Bars(_Symbol,PERIOD_CURRENT,time[0],rsi_high_time); //MAXIMUM CLOSE PRICE FROM THE LAST SIGNIFICANT RSI LOW maximum_value_high = ArrayMaximum(close,0,total_bars_3); if(corresponding_high_value < corresponding_high_ma && close[0] < ma_buffer[0] && close[maximum_value_high] <= corresponding_high_value) { //CREATE LINES TO MARK RSI AND CORRESPONDING PRICE ObjectCreate(ChartID(),"RSI HIGH VALUE",OBJ_TREND,1,rsi_high_time,rsi_high_value,TimeCurrent(),rsi_high_value); ObjectCreate(ChartID(),"C-CANDLE HIGH",OBJ_TREND,0,rsi_high_time,corresponding_high_value,TimeCurrent(),corresponding_high_value); //SETTING OBJECTS COLOUR ObjectSetInteger(ChartID(),"RSI HIGH VALUE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"C-CANDLE HIGH",OBJPROP_COLOR,clrBlack); //CREATE TWO LINES TO CONNECT RSI HIGH AND THE CORRESPONDING PRICE ON THE CHART ObjectCreate(ChartID(),"C-CANDLE LINE HIGH",OBJ_TREND,0,rsi_high_time,corresponding_high_value,rsi_high_time,0); ObjectCreate(ChartID(),"RSI HIGH LINE",OBJ_TREND,1,rsi_high_time,rsi_high_value,rsi_high_time,100); //SETTING OBJECTS COLOUR ObjectSetInteger(ChartID(),"C-CANDLE LINE HIGH",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"RSI HIGH LINE",OBJPROP_COLOR,clrBlack); //CREATE TEXTS TO MART RSI HIGH AND CORRESPONDING PRICE (C-PRICE) ObjectCreate(ChartID(),"C-CANDLE TEXT HIGH",OBJ_TEXT,0,TimeCurrent(),corresponding_high_value); ObjectSetString(ChartID(),"C-CANDLE TEXT HIGH",OBJPROP_TEXT,"C-PRICE"); ObjectCreate(ChartID(),"RSI HIGH TEXT",OBJ_TEXT,1,TimeCurrent(),rsi_high_value); ObjectSetString(ChartID(),"RSI HIGH TEXT",OBJPROP_TEXT,"RSI HIGH"); //SETTING TEXT COLOUR ObjectSetInteger(ChartID(),"C-CANDLE TEXT HIGH",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"RSI HIGH TEXT",OBJPROP_COLOR,clrBlack); //LOGIC TO GET THE NEW RSI HIGH if(rsi_buffer[0] < rsi_buffer[1] && rsi_buffer[1] > rsi_buffer[2] && rsi_buffer[1] > rsi_high_value && close[1] < corresponding_high_value) { new_rsi_high_value = rsi_buffer[1]; new_rsi_high_time = time[1]; new_corresponding_high_value = close[1]; } if(rsi_high_value < new_rsi_high_value && corresponding_high_value > new_corresponding_high_value) { //for rsi ObjectCreate(ChartID(),"RSI HIGH TREND LINE",OBJ_TREND,1,rsi_high_time,rsi_high_value,new_rsi_high_time,new_rsi_high_value); ObjectCreate(ChartID(),"H",OBJ_TEXT,1,rsi_high_time,rsi_high_value); ObjectSetString(ChartID(),"H",OBJPROP_TEXT,"H"); ObjectSetInteger(ChartID(),"H",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"RSI HIGH TREND LINE",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"H",OBJPROP_COLOR,clrBlack); ObjectCreate(ChartID(),"HH",OBJ_TEXT,1,new_rsi_high_time,new_rsi_high_value); ObjectSetString(ChartID(),"HH",OBJPROP_TEXT,"HH"); ObjectSetInteger(ChartID(),"HH",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"HH",OBJPROP_COLOR,clrBlack); //for candle ObjectCreate(ChartID(),"C-CANDLE TREND LINE HIGH",OBJ_TREND,0,rsi_high_time,corresponding_high_value,new_rsi_high_time,new_corresponding_high_value); ObjectCreate(ChartID(),"CH",OBJ_TEXT,0,rsi_high_time,corresponding_high_value); ObjectSetString(ChartID(),"CH",OBJPROP_TEXT,"H"); ObjectSetInteger(ChartID(),"CH",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"C-CANDLE TREND LINE HIGH",OBJPROP_COLOR,clrBlack); ObjectSetInteger(ChartID(),"CH",OBJPROP_COLOR,clrBlack); ObjectCreate(ChartID(),"CLH",OBJ_TEXT,0,new_rsi_high_time,new_corresponding_high_value); ObjectSetString(ChartID(),"CLH",OBJPROP_TEXT,"LH"); ObjectSetInteger(ChartID(),"CLH",OBJPROP_FONTSIZE,15); ObjectSetInteger(ChartID(),"CLH",OBJPROP_COLOR,clrBlack); } } //LOGIC TO DELETE THE OBJECTS WHEN ITS NO LONGER RELEVEANT else if((close[0] > ma_buffer[0]) || (close[maximum_value_high] > corresponding_high_value)) { ObjectDelete(ChartID(),"RSI HIGH VALUE"); ObjectDelete(ChartID(),"C-CANDLE HIGH"); ObjectDelete(ChartID(),"C-CANDLE LINE HIGH"); ObjectDelete(ChartID(),"RSI HIGH LINE"); ObjectDelete(ChartID(),"C-CANDLE TEXT HIGH"); ObjectDelete(ChartID(),"RSI HIGH TEXT"); ObjectDelete(ChartID(),"RSI HIGH TREND LINE"); ObjectDelete(ChartID(),"H"); ObjectDelete(ChartID(),"HH"); ObjectDelete(ChartID(),"C-CANDLE TREND LINE HIGH"); ObjectDelete(ChartID(),"CH"); ObjectDelete(ChartID(),"CLH"); }
Explanation:
This code segment is designed to identify hidden bearish divergence by detecting RSI highs and their corresponding price highs, following a logic similar to 4.1. Finding Hidden Bullish Divergence. Not much emphasis will be added here, as most of the logic has already been explained in 4.1. Finding Hidden Bullish Divergence; the only difference is that now the focus is on the opposite scenario. While the underlying approach remains consistent, this implementation targets bearish momentum by focusing on RSI highs instead of lows.
The first part of the code declares variables such as rsi_high_value, corresponding_high_value, and corresponding_high_ma to store the RSI high, its corresponding price, and the Moving Average (MA) value, respectively. These are used to identify the latest RSI high by looping through the last 30 bars and applying the condition of one bullish movement followed by three consecutive bearish movements. Once an RSI high is found, the corresponding price and MA value are retrieved, and the timestamp (rsi_high_time) is recorded. A validation step ensures the corresponding price high is below the MA, reinforcing the bearish trend.
Next, we detect a new RSI high, where the RSI forms a higher high and the price forms a lower high (LH). This satisfies the criteria for hidden bearish divergence, signaling weakening upward momentum in the price action. If this condition is met, trend lines and labels are created to mark the RSI highs visually (H and HH) and their corresponding price highs (H and LH) on the chart. These visual markers help traders quickly identify potential hidden bearish divergence points.
The code also includes logic to delete objects when the conditions for hidden bearish divergence are invalidated, such as when the current price closes above the MA or the maximum price since the identified RSI high exceeds the corresponding high value. This ensures the chart remains clean and reflects only valid divergence signals.
5. Incorporating the Stochastic Oscillator and Executing Trades
The Stochastic Oscillator is presented as the last confirmation tool now that we understand how to use the Moving Average and RSI to find hidden bullish and bearish divergences. By ensuring that the divergence corresponds with shifts in market momentum, the oscillator serves to increase the precision of trade entry. With stringent timing and risk management guidelines, the confirmation rationale for both divergence kinds depends on the oscillator's performance in relation to overbought and oversold levels.
5.1. Incorporating the Stochastic Oscillator
Following confirmation of the hidden bullish divergence by the RSI and price movement, we will monitor for the Stochastic Oscillator to fall below the oversold level (20) and then climb above it once more. This crossover must occur 11 bars after the divergence is detected to validate the signal. If confirmed, a buy trade will be started, with the stop-loss (SL) being the latest swing low. The take-profit (TP) will be established by a predefined risk-to-reward ratio (RRR) to guarantee disciplined trade management.
Example:
if(total_bars < 11) { if(stoch_buffer_k[0] < 20 && stoch_buffer_k[1] > 20 && currentBarTime != lastTradeBarTime && totalPositions < 1) { // } }
Explanation:
This code confirms a hidden bullish divergence setup by validating it with the Stochastic Oscillator. First, it calculates the number of bars since the RSI low (new_rsi_low_time) using the Bars function and ensures the trade setup remains valid within an 11-bar window "(if(total_bars < 11))". Within this period, it checks if the Stochastic Oscillator's %K line has crossed above the oversold level of 20 (stoch_buffer_k[1] < 20 and stoch_buffer_k[0] > 20). This crossover confirms a potential upward momentum, strengthening the bullish signal. If both conditions are met, the code proceeds to execute the trade logic, ensuring all criteria for a hidden bullish divergence are fulfilled.
In the same way, we will wait for the Stochastic Oscillator to rise above the overbought level (80) and then cross back below to confirm hidden bearish divergence. Additionally, this bearish crossover needs to happen within 11 bars of the divergence being detected. If verified, a sell transaction will be made, with the TP calculated using the specified RRR and the SL set at the most recent swing high. By combining these procedures with the original divergence detection method, it is ensured that trades are only made when there is obvious risk management and good momentum alignment.
Example:
int total_bars = Bars(_Symbol,PERIOD_CURRENT,new_rsi_high_time,TimeCurrent()); if(total_bars < 11) { if(stoch_buffer_k[0] < 80 && stoch_buffer_k[1] > 80) { // } }
Explanation:
The number of bars between the current time (TimeCurrent()) and the time the new RSI high (new_rsi_high_time) was discovered is determined by the variable total_bars. It guarantees the setup is still active and hasn't expired if there are fewer than eleven bars. The code then determines whether the %K line of the stochastic oscillator (stoch_buffer_k) has passed below the overbought threshold (80) in the current bar (stoch_buffer_k[0] < 80) and above it in the previous bar (stoch_buffer_k[1] > 80). The trade setup is verified, and the code starts running the required logic to enter the transaction if both requirements are met.
5.2. Trade Execution
5.2.1. Swing Low and Swing High
This section will focus on all the essential components required for executing trades after confirming a valid divergence and Stochastic Oscillator signal. It ensures that trades are executed systematically and with proper risk management.
To define the stop-loss (SL), we must first ascertain the last swing high or low. The last swing low will be the SL for a hidden bullish divergence, and the last swing high for a hidden bearish divergence. This guarantees that, depending on previous price behavior, trades have a precisely defined risk threshold.
Example:
double last_high; double last_low; //LOGIC FOR LAST HIGH for(int i = 0; i < 30; i++) { if(i < 30 && i+1 < 30) { if(close[i] < open[i] && close[i+1] > open[i+1]) { last_high = MathMax(high[i],high[i+1]); break; } } } //LOGIC FOR LAST LOW for(int i = 0; i < 30; i++) { if(i < 30 && i+1 < 30) { if(close[i] > open[i] && close[i+1] < open[i+1]) { last_low = MathMin(low[i],low[i+1]); break; } } } }
Explanation:
To determine the most recent swing high and low, this code examines the previous 30 bars. It determines the highest price (last_high) by detecting a bearish candle followed by a bullish one, and the lowest price (last_low) by detecting a bullish candle followed by a bearish one. When determining stop-loss levels, these figures are essential.
5.2.2. Managing Trade Execution and Preventing Over-trading
Only one open position will be permitted at a time by the system to discourage excessive trading and lower needless risk. The code will make sure there aren't any open positions before making a new transaction. This guarantees better controlled trading behavior by stopping the system from completing transactions on every signal. Effective trade execution requires the inclusion of the #include <Trade/Trade.mqh> library, which grants access to the CTrade class for trade management.
Example:
#include <Trade/Trade.mqh> CTrade trade; input int MagicNumber = 9097; //MAGIC NUMBER //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //SET MAGIC NUMBER trade.SetExpertMagicNumber(MagicNumber); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Getting total positions int totalPositions = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(ChartID())) { totalPositions++; } } }
Explanation:
The code starts by including the Trade.mqh library, which provides access to trading functions in MetaTrader. The CTradeobject, trade, is then instantiated to manage trade operations. The MagicNumber is defined as an input, set to 9097 in this case, which uniquely identifies trades opened by this specific Expert Advisor (EA) to distinguish them from others on the same account. In the OnInit()function, the magic number is applied to the trade object using SetExpertMagicNumber(MagicNumber). This ensures that the EA recognizes its trades when performing operations like opening, modifying, or closing positions.
In the OnTick() function, the code checks the total number of open positions for the symbol on the current chart by iterating through all positions using PositionsTotal(). It retrieves each position's ticket number with PositionGetTicket(i) and verifies whether the position's magic number matches the EA's magic number using PositionGetInteger(POSITION_MAGIC). Additionally, the code checks if the position belongs to the current chart symbol by using PositionGetString(POSITION_SYMBOL). If both conditions are true, it increments the totalPositions variable, keeping track of the number of open trades related to the specific EA and symbol, which ensures that only one position is open at a time.
5.2.3. Risk Management
The trading system guarantees a methodical approach to risk management and profitability maximization by integrating these two essential elements. This strategy supports long-term market success and helps safeguard the trading account. The implementation of a strong risk management system by enabling traders to indicate the precise amount of money they want to risk on each deal will be the main goal of this section. To make sure that possible gains exceed possible risks, it will also describe how to calculate the risk-to-reward ratio (RRR) for every trade.
Example:
//INPUTS input double risk_amount = 20; // $ PER TRADE input double rrr = 4; //RRR input int MagicNumber = 9097; //MAGIC NUMBER double take_profit; double points_risk; double lot_size; //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //BULLISH HIDDEN DIVERGENCE if(stoch_buffer_k[0] > 20 && stoch_buffer_k[1] < 20 && currentBarTime != lastTradeBarTime && totalPositions < 1) { take_profit = ((ask_price - last_low) * rrr) + ask_price; points_risk = ask_price - low[0]; lot_size = CalculateLotSize(_Symbol, risk_amount, points_risk); trade.Buy(lot_size,_Symbol,ask_price,last_low,take_profit); lastTradeBarTime = currentBarTime; } //BEARISH HIDDEN DIVERGENCE if(stoch_buffer_k[0] < 80 && stoch_buffer_k[1] > 80 && currentBarTime != lastTradeBarTime && totalPositions < 1) { take_profit = MathAbs(((last_high - ask_price) * rrr) - ask_price); points_risk = high[0] - ask_price; lot_size = CalculateLotSize(_Symbol, risk_amount, points_risk); trade.Sell(lot_size,_Symbol,ask_price,last_high,take_profit); lastTradeBarTime = currentBarTime; } } //+-----------------------------------------------------------------------+ //| Function to calculate the lot size based on risk amount and stop loss | //+-----------------------------------------------------------------------+ double CalculateLotSize(string symbol, double riskAmount, double stopLossPips) { // Get symbol information double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); // Calculate pip value per lot double pipValuePerLot = tickValue / point; // Calculate the stop loss value in currency double stopLossValue = stopLossPips * pipValuePerLot; // Calculate the lot size double lotSize = riskAmount / stopLossValue; // Round the lot size to the nearest acceptable lot step double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lotSize = MathFloor(lotSize / lotStep) * lotStep; // Ensure the lot size is within the allowed range double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); return lotSize; }
Explanation:
Based on a given risk amount (in dollars) and the stop-loss distance (in pip), the CalculateLotSize function calculates the right lot size for a trade. The lot size is determined by dividing the risk amount by the stop-loss value, converting the stop-loss distance into a monetary value, and calculating the pip value per lot using symbol-specific data. The outcome is modified to meet the broker's requirements for the minimum and maximum lot sizes, as well as lot steps. For this function to be reusable across the code and to make risk management uniform and effective, it should be declared in the global area.
For a buy trade, the Stochastic Oscillator's %K line must rise above 20 after dipping below it. Trades execute only if no positions are open (totalPositions < 1) and the current bar differs from the last traded bar. The stop-loss is set at the last swing low, and the take-profit is calculated using the risk-reward ratio (rrr) and the distance between the ask price and the swing low. Lot size is determined using the CalculateLotSize function, based on the risk amount and stop-loss distance. After execution, lastTradeBarTime is updated to prevent duplicate trades on the same bar.
The Oscillator's %K line must first be above the 80 level (overbought) before crossing below it to indicate a sell trade. In a similar vein, the transaction will only be executed if there are no open positions and the bar is new. The risk-reward ratio and the difference between the ask price and the latest swing high (last_high) are used to determine the take-profit (take_profit). At the final swing high, the stop-loss is established. The CalculateLotSize function is used once more to determine the lot size. To avoid duplicate trades, the sell function updates lastTradeBarTime and places the sell order.
By following transaction limits, appropriate risk management, and confirmation signals, this code guarantees disciplined trading and increases the trading strategy's dependability.
Conclusion
This article provided a step-by-step guide to identifying and trading hidden bullish and bearish divergences using MQL5, integrating indicators like the Moving Average, RSI, and Stochastic Oscillator. It emphasized risk management, trade execution, and automation to help traders spot these patterns effectively in the market. This educational content aims to teach the fundamentals of divergence detection and systematic trading. A well-detailed source code, complete with comprehensive comments, will be attached to serve as a practical reference for implementing the concepts discussed.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Thank you
Hello Israel, thank you for putting so much time into the clear projects, especially your analogies appeal to me, you keep your promise to "do everything in small steps". Thank you I'm going to start the project now (11), and I'm quite excited, I'll let you hear from me John
You're welcome, Thank you for your kind words.