Viewing the resource: Automating Order Blocks: Building an MQL5 Expert Advisor for Forex Trading

Automating Order Blocks: Building an MQL5 Expert Advisor for Forex Trading

Allan Munene Mutiiria 2025-10-15 15:34:09 468 Views
This article explores the development of an Expert Advisor (EA) in MQL5 that identifies order blocks...

Introduction


Order blocks are price zones where institutional traders place large orders, driving significant forex market moves. Manually trading these zones is challenging due to time and error risks. This article details an MQL5 (Metaquotes Language 5) Expert Advisor (EA) for MetaTrader 5 that automates order block detection and trading. It identifies consolidation ranges, confirms breakouts, and executes trades with risk management. We’ll provide a clear blueprint, implementation steps, testing process, and conclusions to help traders of all levels use this tool effectively. If you prefer a tutorial, we did a YouTube video for you!

Understanding Order Blocks

Before diving into our EA’s blueprint, we need to understand the two main types of order blocks, as they form the foundation of our trading strategy:
  • Bullish Order Blocks (Demand Zones): These occur in a downtrend, marking areas where buyers step in with significant force, causing a sharp upward price move. We identify them as the last bearish candle (or range) before a strong bullish impulse. When price revisits these zones, it often bounces upward, offering buy opportunities.
  • Bearish Order Blocks (Supply Zones): Found in an uptrend, these are zones where sellers dominate, triggering a sharp downward move. We spot them as the last bullish candle (or range) before a bearish impulse. Price returning to these zones typically faces rejection, signaling sell opportunities.
We focus on these blocks because they reflect institutional intent, creating high-probability trade setups. Our EA will use these definitions to detect and act on valid order blocks.

Roadmap: Blueprint for Our Order Blocks Expert Advisor


We aim to create a robust MQL5 Expert Advisor (EA) for MetaTrader 5 that automates the identification and trading of order blocks in the forex market. Our goal is to streamline the process, making it accessible for traders with varying experience levels while ensuring precision and risk control. Here’s the blueprint of what we will accomplish:
  1. Define Order Block Logic: We will establish rules to detect consolidation ranges where price stabilizes before a breakout. We’ll identify bullish and bearish order blocks based on impulsive price moves following these ranges, ensuring we capture institutional activity accurately.
  2. Visualize Order Blocks: We’ll configure the EA to draw rectangles on the chart to mark order block zones, using distinct colors for bullish (lime), bearish (red), and invalid (blue) blocks. This helps us visually confirm the EA’s logic in real-time.
  3. Automate Trade Entries: We will program the EA to trigger buy or sell trades when price revisits a valid order block and shows confirmation signals, like candlestick patterns crossing the block’s boundaries. This ensures we align with high-probability setups.
  4. Implement Risk Management: We’ll incorporate strict checks for lot size, margin, stop-loss, and take-profit levels to protect our capital. We do this because uncontrolled risk can wipe out accounts, and our EA must prioritize consistency.
  5. Validate and Track Blocks: We’ll ensure the EA tracks active order blocks, removes outdated ones, and avoids duplicate signals. This keeps our strategy efficient and prevents overtrading.
  6. Test and Optimize: We will backtest the EA on historical data and forward-test it in a demo environment to evaluate performance. This step is crucial because it helps us refine settings like range candles and risk-reward ratios for better results.
By following this roadmap, we’ll build an EA that automates order block trading with precision, saving us time while leveraging institutional price zones for profitable trades. In a nutshell, this is what we will achieve. Let's go now, boys! Joking 😂. Boys and girls! Ooh, whatever!

Implementation of the Order Blocks Expert Advisor

We begin building our MQL5 Expert Advisor (EA) by setting up the configuration parameters that define how the EA operates, visualizes order blocks, and manages trades. These settings will allow us to customize the EA’s behavior, ensuring flexibility and clarity in its operation.
//+------------------------------------------------------------------+
//|                                              ORDER BLOCKS EA.mq5 |
//|      Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                           https://youtube.com/@ForexAlgo-Trader? |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://youtube.com/@ForexAlgo-Trader?"
#property description "======= IMPORTANT =======\n"
#property description "1. This is a FREE EA."
#property description "2. To get the source code of the EA, follow the Copyright link."
#property description "3. Incase of anything, contact developer via the link provided."
                      "Hope you will enjoy the EA Logic.\n"
#property description "*** HAPPY TRADING! ***"
#property version   "2.00"

sinput group "✔🔰 EA GENERAL SETTINGS 💱💲"
input double inpLot = 0.01; // Lotsize
input ulong magic_no = 1234567; // Magic Number
input int sl_tp_pts = 300; // Stop Loss/Take Profit Points
input double r2r_ratio = 7; // Risk : Reward Ratio
sinput string ob_up_name = "Bullish Order Block"; // OB Up Name
sinput string ob_down_name = "Bearish Order Block"; // OB Down Name
sinput short ob_up_unicode = 0x2BED; // OB Up Unicode
sinput short ob_down_unicode = 0x2BEF; // OB Down Unicode
sinput string range_name = "Range"; // Range name

input int range_candles = 7; // Range Candles
input double max_deviation = 50; // Maximum Deviation Points
input int starting_index = 1; // Starting Index
input int wait_bars = 3; // Validation Wait Bars

sinput color def_clr_up = clrLime; // Bullish OB Color
sinput color def_clr_down = clrRed; // Bearish OB Color
sinput color def_invalid = clrBlue; // Invalid OB Color
sinput color def_text = clrBlack; // Text Color
sinput bool prt = false; // Print Statements
sinput int fontSize = 15; // Font Size
We start with the "sinput group" directive labeled "✔🔰 EA GENERAL SETTINGS 💱💲", which organizes our input parameters into a user-friendly section in the MetaTrader 5 interface. This helps us easily adjust settings when deploying the EA.

First, we define the "inpLot" input as a "double" with a default value of 0.01, representing the lot size for trades. This controls the volume of each position, allowing us to scale our risk. Next, we set the "magic_no" input as a "ulong" with a default of 1234567. This unique identifier ensures our EA’s trades are distinguishable from others, preventing conflicts in trade management, incase you have many EAs on your charts.

For risk management, we include "sl_tp_pts" as an "int" set to 300, defining the stop-loss and take-profit distance in points. We also define "r2r_ratio" as a "double" with a value of 7, establishing the risk-to-reward ratio for trades. This means for every unit of risk, we aim for seven units of profit, ensuring a favorable reward structure. To label order blocks on the chart, we use "ob_up_name" and "ob_down_name" as "string" inputs, set to "Bullish Order Block" and "Bearish Order Block", respectively. These names appear on the chart for clarity. We also assign "ob_up_unicode" and "ob_down_unicode" as "short" inputs with values 0x2BED and 0x2BEF, adding unique symbols to visually distinguish bullish and bearish blocks. The "range_name" input, set to "Range", labels consolidation zones that don’t qualify as valid order blocks.

For order block detection, we define "range_candles" as an "int" set to 7, specifying the number of candles to analyze for consolidation ranges. The "max_deviation" input, a "double" set to 50 points, sets the maximum price difference allowed between highs and lows to confirm a consolidation. The "starting_index" input, an "int" set to 1, determines the starting candle for analysis, while "wait_bars", an "int" set to 3, defines the number of bars to wait after a breakout to validate an order block. For visualization, we set "def_clr_up" as a "color" input to "clrLime" for bullish order blocks, "def_clr_down" to "clrRed" for bearish blocks, and "def_invalid" to "clrBlue" for invalid ranges. The "def_text" input, set to "clrBlack", defines the color of text labels on the chart. We include a "prt" input as a "bool" set to false, controlling whether the EA logs debug messages to the MetaTrader 5 journal. Finally, "fontSize", an "int" set to 15, sets the text size for chart labels, ensuring readability.

These parameters form the foundation of our EA, allowing us to customize trading, visualization, and order block detection while maintaining strict risk control. With these settings in place, we’re ready to build the logic for detecting and trading order blocks. On compilation, we get the following outcome.

With our input parameters set, we will now lay the groundwork for the core functionality of our MQL5 Expert Advisor (EA) by including necessary libraries, defining data structures, and initializing variables. These elements will enable us to manage trades, track price levels, and store order block information efficiently.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

struct PriceIndex{
   double price;
   int index;
};

PriceIndex highestHigh = {0,0};
PriceIndex lowestLow = {0,0};
bool breakoutDetected = false;

double impulseLow = 0.0;
double impulseHigh = 0.0;
int breakoutBarIndex = -1;
datetime breakoutTime = 0;

string totalOBs_names[];
datetime totalOBs_dates[];
bool totalOBs_is_signals[];

bool is_OB_UP = false;
bool is_OB_DOWN = false;

#define OB_Prefix "OB REC "
#define CLR_UP def_clr_up
#define CLR_DOWN def_clr_down
We start by including the "Trade.mqh" library with the "#include <Trade/Trade.mqh>" directive. This library provides the "CTrade" class, which simplifies trade operations like opening and closing positions. We instantiate a CTrade object named "obj_Trade" to handle all trading actions, ensuring we can execute buy and sell orders programmatically with proper risk management.

Next, we define a custom structure called "PriceIndex" to store price and index data for key price levels. This structure has two members: "price" (a "double") to hold the price value and "index" (an "int") to track the candle index where this price occurs. We use this structure to create two variables: "highestHigh" and "lowestLow", both initialized with "price" and "index" set to 0. These track the highest high and lowest low within a consolidation range, helping us identify potential breakout points. We declare a "bool" variable "breakoutDetected" initialized to false. This flag signals when a price breakout occurs from the consolidation range, triggering order block detection. We also define two "double" variables, "impulseHigh" and "impulseLow", both set to 0.0, to store the high and low prices of the impulsive move following a breakout. The "breakoutBarIndex" variable, an "int" set to -1, tracks the candle index where a breakout occurs, while "breakoutTime", a "datetime" variable initialized to 0, records the time of the breakout for validation timing.

To manage order blocks, we create three dynamic arrays: "totalOBs_names" (a "string" array), "totalOBs_dates" (a "datetime" array), and "totalOBs_is_signals" (a "bool" array). These store the names, end times, and signal status of order blocks drawn on the chart, allowing us to track active blocks and prevent duplicate trade signals. We also define two "bool" variables, "is_OB_UP" and "is_OB_DOWN", both initialized to false. These flags indicate whether a bullish or bearish order block is detected after a breakout, guiding our EA’s trading decisions. Finally, we use preprocessor directives to define constants: "OB_Prefix" as "OB REC " for naming chart objects, "CLR_UP" as "def_clr_up" (lime) for bullish order blocks, and "CLR_DOWN" as "def_clr_down" (red) for bearish order blocks. These ensure consistent naming and coloring conventions.

This setup provides the structural foundation for our EA, enabling it to track price levels, detect breakouts, and manage order block data efficiently. With these components in place, we’re ready to implement the logic for detecting and visualizing order blocks. We are now all set man.

Alright, let’s get this EA rolling! We’re now diving into the OnInit function, the kickoff party for our MQL5 Expert Advisor. This is where we set things up before the trading action begins, like prepping the stage for a rockstar performance.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   obj_Trade.SetExpertMagicNumber(magic_no);
   if (prt){
      Print("Success. "+__FILE__+" Initialized...");
   }
//---
   return(INIT_SUCCEEDED);
}
The "OnInit" function is called when we first load the EA onto a MetaTrader 5 chart. It’s like the EA stretching its legs and saying, “Let’s do this!” We start by calling the "SetExpertMagicNumber" method on our "obj_Trade" object, passing in the "magic_no" input we defined earlier (that 1234567 number). This assigns a unique ID to all trades the EA opens, so it doesn’t get confused with other trades on the chart—like giving each trade its own VIP pass.

Next, we check if the "prt" input (our debug print switch) is true. If it is, we use the Print function to log a cheeky message: "Success. " + FILE + " Initialized...". This is the EA’s way of high-fiving us, confirming it’s ready to hunt for order blocks. The "FILE" macro tosses in the EA’s filename, so we know exactly who’s talking. No big deal, just a little pat on the back to keep us motivated.

Finally, we return INIT_SUCCEEDED to tell MetaTrader 5 that our setup went smoothly. It’s like the EA winking at the platform, saying, “I’m good to go, let’s make some pips!” This simple initialization ensures our trading object is ready and our debug logging is set, paving the way for the real action in detecting and trading order blocks. This is what we get, of course if you have enabled statements' printing.

Alright, rock and roll boys, 😂!

We’re deep in the zone now, crafting the brain of our MQL5 Expert Advisor to hunt down those order blocks like a forex ninja! We will now build the smarts to spot consolidation ranges, catch breakouts, and lock in those impulsive moves that make order blocks pop. Let’s dive in and see how we make the market spill its secrets, keeping it sharp and fun so you’re itching to see what’s next.

bool IsConsolidationEqualHighsAndLows(int rangeCandles, double maxDeviation, int startingIndex){
   
   for (int i=startingIndex; i<startingIndex+rangeCandles-1; i++){
      if (MathAbs(high(i) - high(i+1)) > maxDeviation*Point()){
         return false;
      }
      if (MathAbs(low(i) - low(i+1)) > maxDeviation*Point()){
         return false;
      }
   }
   return true;
}

void GetHighestHigh(int rangeCandles, int startingIndex, PriceIndex &highestHighRef){
   highestHighRef.price = high(startingIndex);
   highestHighRef.index = startingIndex;
   
   for (int i=startingIndex+1; i<startingIndex+rangeCandles; i++){
      if (high(i) > highestHighRef.price){
         highestHighRef.price = high(i);
         highestHighRef.index = i;
      }
   }
}

void GetLowestLow(int rangeCandles, int startingIndex, PriceIndex &lowestLowRef){
   lowestLowRef.price = low(startingIndex);
   lowestLowRef.index = startingIndex;
   
   for (int i=startingIndex+1; i<startingIndex+rangeCandles; i++){
      if (low(i) < lowestLowRef.price){
         lowestLowRef.price = low(i);
         lowestLowRef.index = i;
      }
   }
}

void ExtendRangeIfWithinLimits(){
   double currentHigh = high(1);
   double currentLow = low(1);
   
   if (currentHigh <= highestHigh.price && currentLow >= lowestLow.price){
      if (prt){
         Print("Range extended: Including candle with high = ",currentHigh," and low = ",currentLow);
      }
   }
   else {
      if (prt){
         Print("No extension posiible. The current bar is outside the range.");
      }
   }
}

bool CheckRangeBreak(PriceIndex &highestHighRef, PriceIndex &lowestLowRef){
   double closingPrice = close(1);
   
   if (closingPrice > highestHighRef.price){
      if (prt){
         Print("Range break upwards detected. Closing price ",closingPrice," is above the highest high: ",highestHighRef.price);
      }
      return true;
   }
   else if (closingPrice < lowestLowRef.price){
      if (prt){
         Print("Range break downwards detected. Closing price ",closingPrice," is below the lowest low: ",lowestLowRef.price);
      }
      return true;
   }
   return false;
}

void DetectImpulsiveMovement(double breakoutHigh,double breakoutLow,int impulseBars,double impulseThreshold){
   double range = breakoutHigh - breakoutLow;
   double impulseThresholdPrice = range *impulseThreshold;
   
   for (int i=1; i<=impulseBars; i++){
      double closePrice = close(i);
      if (closePrice >= breakoutHigh+impulseThresholdPrice){
         is_OB_UP = true;
         if (prt){
            Print("Impulsive upward movement detected: Close Price = ",closePrice,
                  ", Threshold = ",breakoutHigh+impulseThresholdPrice
            );
         }
         return;
      }
      else if (closePrice <= breakoutLow-impulseThresholdPrice){
         is_OB_DOWN = true;
         if (prt){
            Print("Impulsive downward movement detected: Close Price = ",closePrice,
                  ", Threshold = ",breakoutHigh-impulseThresholdPrice
            );
         }
         return;
      }
   }
   is_OB_UP = false;
   is_OB_DOWN = false;
   if (prt){Print("No impulsive movement detected after breakout.");}
}


double high(int index){return iHigh(_Symbol,_Period,index);}
double low(int index){return iLow(_Symbol,_Period,index);}
double open(int index){return iOpen(_Symbol,_Period,index);}
double close(int index){return iClose(_Symbol,_Period,index);}
datetime time(int index){return iTime(_Symbol,_Period,index);}
We kick things off by crafting the "IsConsolidationEqualHighsAndLows" function to sniff out when the market’s just chilling, like it’s binge-watching a show before a big plot twist. It takes "rangeCandles" (an "int" for how many candles we’re checking), "maxDeviation" (a "double" for the max price wiggle we allow), and "startingIndex" (an "int" for where we start). We loop from "startingIndex" to "startingIndex + rangeCandles - 1", comparing highs and lows of adjacent candles using MathAbs on prices from our "high" and "low" helpers (which lean on "iHigh" and "iLow" for chart data). If the difference is more than "maxDeviation * Point()", we bail with a false—market’s too jumpy for a consolidation party. If the candles stay tight, we return true, signaling a cozy range ready for action.

Then, we craft the "GetHighestHigh" function to grab the tallest price in our range. It uses "rangeCandles", "startingIndex", and our "PriceIndex" structure "highestHighRef". We set "highestHighRef.price" to the high of the starting candle with "high(startingIndex)" and "highestHighRef.index" to "startingIndex". Looping through the range, we update "highestHighRef" if a higher price shows up via "high(i)". It’s like picking the MVP candle that’s soaring above the rest.

We mirror this with the "GetLowestLow" function to find the lowest price, updating "lowestLowRef" in our "PriceIndex" structure using "low(i)". This spots the underdog candle, just as crucial for nailing our range. Next up, we craft the "ExtendRangeIfWithinLimits" function to check if the latest candle (index 1) fits our chill zone. We grab "currentHigh" and "currentLow" with "high(1)" and "low(1)". If "currentHigh" is at or below "highestHigh.price" and "currentLow" is at or above "lowestLow.price", we stretch the range and log it with "Print" if "prt" is true. If the candle’s too wild, we log that it’s a rebel and doesn’t fit. This keeps our range loose enough to grow, like inviting one more friend to the crew.

We also craft the "CheckRangeBreak" function to catch when the market says, “Peace out, I’m breaking free!” It uses "highestHighRef" and "lowestLowRef" and checks the latest candle’s close with "close(1)" (via iClose). If "closingPrice" shoots above "highestHighRef.price", we log an upward breakout with "Print" (if "prt" is true) and return true. If it drops below "lowestLowRef.price", we log a downward break and return true. No breakout? We return false, keeping the market in check.

Lastly, we craft the "DetectImpulsiveMovement" function to see if a breakout turns into a full-on market sprint, confirming our order blocks. It takes "breakoutHigh", "breakoutLow", "impulseBars" (an "int" for candles to scan), and "impulseThreshold" (a "double" for move size). We calculate the range as "breakoutHigh - breakoutLow" and set "impulseThresholdPrice" as "range * impulseThreshold". Looping through "impulseBars", we check "close(i)" (using "iClose"). If the close blasts above "breakoutHigh + impulseThresholdPrice", we flip "is_OB_UP" to true, log the fireworks, and bounce. If it crashes below "breakoutLow - impulseThresholdPrice", we set "is_OB_DOWN" to true and log it. No big move? Both flags stay false, and we log the snooze with "Print". This is how we catch the market’s wildest moments.

We cap it with helper functions: "high", "low", "open", "close", and "time", which pull price and time data using iHigh, "iLow", "iOpen", "iClose", and "iTime" for any candle index. These are our trusty sidekicks, keeping the code clean and mean. With these functions, we’re arming our EA to spot consolidations, nab breakouts, and confirm order blocks like a boss. Stick around—this forex ride’s about to get even wilder as we start drawing and trading these blocks! We will do this on the tick event handler.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   static bool isNewBar = false;
   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   
   if (prevBars == currBars){isNewBar = false;}
   else if (prevBars != currBars){
      isNewBar = true;
      prevBars = currBars;
   }
   
   if (!isNewBar)
      return;
   
   int rangeCandles = range_candles;
   double maxDeviation = max_deviation;
   int startingIndex = starting_index;
   int waitBars = wait_bars;
   
   if (!breakoutDetected){
      if (highestHigh.price == 0 && lowestLow.price == 0){
         if (IsConsolidationEqualHighsAndLows(rangeCandles,maxDeviation,startingIndex)){
            GetHighestHigh(rangeCandles,startingIndex,highestHigh);
            GetLowestLow(rangeCandles,startingIndex,lowestLow);
            
            if (prt){
               Print("Consolidation range established: Highest High = ",highestHigh.price,
                     " at index ,",highestHigh.index,
                     " and Lowest Low = ",lowestLow.price,
                     " at index ",lowestLow.index
               );
            }
         }
      }
      else{
         ExtendRangeIfWithinLimits();
      }
   }
   
   if (highestHigh.price > 0 && lowestLow.price > 0){
      breakoutDetected = CheckRangeBreak(highestHigh,lowestLow);
   }
   
   if (breakoutDetected){
      if (prt){
         Print("Breakout detected. Resetting for the next range.");
      }
      
      breakoutBarIndex = 1;
      breakoutTime = TimeCurrent();
      impulseHigh = highestHigh.price;
      impulseLow = lowestLow.price;
      
      breakoutDetected = false;
      highestHigh.price = 0;
      highestHigh.index = 0;
      
      lowestLow.price = 0;
      lowestLow.index = 0;
   }
}
We’re cranking up the heat now, diving into the OnTick event handler—the heartbeat of our MQL5 Expert Advisor! This is where we make things happen every time the market twitches, sniffing out consolidation ranges and breakouts like a forex bloodhound. Let’s roll through this code with some zing, keeping you hooked for what’s coming next.

We craft the detection to run on every price tick, but we’re smart about it—only acting when a new candle forms to avoid spamming our CPU. We declare a "static bool" variable "isNewBar" and set it to false initially. To track new candles, we use the iBars function to get "currBars" (the current number of bars for the symbol and timeframe) and compare it to a "static int" variable "prevBars", initialized to "currBars". If "prevBars" equals "currBars", no new candle, so we set "isNewBar" to false. If they differ, a new candle’s born, so we set "isNewBar" to true and update "prevBars" to "currBars". If "isNewBar" is false, we bail with a "return" to save processing power—nobody’s got time for pointless ticks!

When a new candle hits, we grab our input parameters: "rangeCandles" from "range_candles", "maxDeviation" from "max_deviation", "startingIndex" from "starting_index", and "waitBars" from "wait_bars". These are our tools for spotting consolidation and breakouts, like a chef prepping ingredients for a killer dish

If "breakoutDetected" is false, we check if our "highestHigh.price" and "lowestLow.price" in our "PriceIndex" structures are zero (meaning no range is set). If so, we call "IsConsolidationEqualHighsAndLows" with "rangeCandles", "maxDeviation", and "startingIndex" to see if the market’s chilling in a tight range. If it returns true, we’ve found a consolidation zone! We then call "GetHighestHigh" and "GetLowestLow" to update "highestHigh" and "lowestLow" with the range’s top and bottom prices and their indices. If "prt" is true, we use "Print" to log a victory message, shouting out the "highestHigh.price", "highestHigh.index", "lowestLow.price", and "lowestLow.index" to confirm our range is locked in.If "highestHigh.price" and "lowestLow.price" aren’t zero, we’ve already got a range, so we call "ExtendRangeIfWithinLimits" to see if the latest candle fits. This keeps our consolidation zone fresh, like adding a new track to a playlist.

If both "highestHigh.price" and "lowestLow.price" are set, we check for a breakout by calling "CheckRangeBreak" with "highestHigh" and "lowestLow". If it returns true, we’ve got action! We set "breakoutDetected" to true, and if "prt" is true, we log a “Breakout detected” message with "Print" to celebrate. We then set "breakoutBarIndex" to 1 (marking the breakout candle), grab the current time with "TimeCurrent" for "breakoutTime", and store "highestHigh.price" in "impulseHigh" and "lowestLow.price" in "impulseLow" to prep for order block detection. Finally, we reset "breakoutDetected" to false, clear "highestHigh" and "lowestLow" (setting "price" and "index" to 0), and get ready to hunt for the next range—like hitting the reset button for a new round. This function is our EA’s pulse, constantly checking for consolidation and breakouts to set the stage for order block magic. Hang tight, because we’re about to start drawing those blocks and maybe even throwing some trades into the mix! Before that, though, it is always a good programming practice to compile and check that you got what you want at every milestone. In our case, this is what we get, and if you are following us, you should have something that depicts it.

Paper, rock, scissors! We win 😂!

It is imperative that you enable that statement thing because it is important. You get to know what we are doing. Bomboclat. Now that we can define the ranges, let us visualize them on the chart. It looks really weird that we have signals but can't see them. So technically it is the EA that knows and not us. Let's define a helper function to draw the ranges, real quick now, jokes aside 👈.

void CreateRec(string objName,datetime time1,double price1,
               datetime time2,double price2,color clr,string txt
){
   if (ObjectFind(0,objName) < 0){
      ObjectCreate(0,objName,OBJ_RECTANGLE,0,time1,price1,time2,price2);
      if (prt){
         Print("SUCCESS CREATING OBJECT >",objName,"< WITH"," T1: ",time1,", P1: ",price1,
               ", T2: ",time2,", P2: ",price2);
      }
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);
      ObjectSetInteger(0,objName,OBJPROP_FILL,true);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_BACK,false);
      
      string description_txt = txt;
      string textObjName = objName+description_txt;
      
      datetime midTime = time1 +(time2-time1)/2;
      double midPrice = (price1+price2)/2;
      
      if (ObjectFind(0,textObjName) < 0){
         ObjectCreate(0,textObjName,OBJ_TEXT,0,midTime,midPrice);
         ObjectSetString(0,textObjName,OBJPROP_TEXT,description_txt);
         ObjectSetInteger(0,textObjName,OBJPROP_COLOR,def_text);
         ObjectSetInteger(0,textObjName,OBJPROP_FONTSIZE,fontSize);
         ObjectSetInteger(0,textObjName,OBJPROP_ANCHOR,ANCHOR_CENTER);
         if (prt){
            Print("SUCCESS CREATING LABEL >",textObjName,"< WITH TEXT: ",description_txt);
         }
      }
      
      ChartRedraw(0);
   }
}

We’re turning up the style now, painting our order blocks on the chart like forex Picassos! Here, we craft the "CreateRec" function to draw those all-important rectangles and labels that make our bullish and bearish order blocks pop on the MetaTrader 5 chart. This is where our EA shows off, making it super easy to see where the action’s at. Let’s dive in and keep you pumped for what’s next!

We define the "CreateRec" function to draw a rectangle and a label for each order block or range. It takes seven parameters: "objName" (a "string" for the object’s name), "time1" and "time2" (both "datetime" for the start and end times of the rectangle), "price1" and "price2" (both "double" for the high and low prices), "clr" (a "color" for the rectangle’s fill), and "txt" (a "string" for the label text). We did a lesson on functions and how we can pass parameters to them, 2 ways really, in our YouTube channel, and you can check that, for now, all brains in here. This function is our artist’s brush, sketching out the zones we’re trading.

First, we check if the rectangle doesn’t already exist using ObjectFind with "objName". If "ObjectFind" returns less than 0 (the function returns a neagtive number in case of an error, you can read the documentation), it’s not on the chart, so we create it with ObjectCreate, passing "objName", OBJ_RECTANGLE (the object type), and the time-price coordinates "time1", "price1", "time2", and "price2". If "prt" is true, we use "Print" to cheer “SUCCESS CREATING OBJECT” with the details of "objName", "time1", "price1", "time2", and "price2"—like a high-five for nailing the rectangle.

Return Value (ObjectFind)
If successful, the function returns the number of the subwindow (0 means the main window of the chart), in which the object is found. If the object is not found, the function returns a negative number. To read more about the error, call GetLastError().

We then set the rectangle’s properties with ObjectSetInteger and ObjectSetDouble. We assign "time1" and "price1" to the first corner (index 0) and "time2" and "price2" to the second corner (index 1) using "OBJPROP_TIME" and "OBJPROP_PRICE". We make the rectangle filled with "OBJPROP_FILL" set to true, color it with "clr" (like lime for bullish or red for bearish) using "OBJPROP_COLOR", and ensure it’s in the foreground with "OBJPROP_BACK" set to false. This makes our order block zones bold and beautiful.

Next, we create a label for the rectangle. We store the "txt" parameter in "description_txt" (a "string") and create a unique label name, "textObjName", by combining "objName" and "description_txt". We calculate "midTime" (a "datetime") as the midpoint between "time1" and "time2" and "midPrice" (a "double") as the average of "price1" and "price2" to place the label smack in the center of the rectangle.

If the label doesn’t exist (checked with "ObjectFind" on "textObjName"), we create it using "ObjectCreate" with "OBJ_TEXT" as the object type, placing it at "midTime" and "midPrice". We set the label’s text to "description_txt" with "ObjectSetString" and "OBJPROP_TEXT", color it with "def_text" (our black text color) using "OBJPROP_COLOR", size it with "fontSize" (set to 15) via OBJPROP_FONTSIZE, and center it with "OBJPROP_ANCHOR" set to "ANCHOR_CENTER". If "prt" is true, we log a “SUCCESS CREATING LABEL” message with "Print", showing off "textObjName" and "description_txt". This label tells us whether it’s a “Bullish Order Block” or “Bearish Order Block” right on the chart.

Finally, we call ChartRedraw to refresh the chart, ensuring our rectangle and label show up instantly, like unveiling a masterpiece.

With "CreateRec", we’re making our order blocks impossible to miss, turning our chart into a clear map of institutional zones. We can now detect the zones and visualize them.

if (breakoutBarIndex >= 0 && TimeCurrent() > breakoutTime+waitBars*PeriodSeconds()){
   DetectImpulsiveMovement(impulseHigh,impulseLow,waitBars,1);
   
   bool is_OB_Valid = is_OB_UP || is_OB_DOWN;
   
   datetime time1 = iTime(_Symbol,_Period,rangeCandles+waitBars+1);
   double price1 = impulseHigh;
   
   int visisbleBars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   datetime time2 = is_OB_Valid ? time1+(visisbleBars/1)*PeriodSeconds() : time(waitBars+1);
   double price2 = impulseLow;
   
   string obName = OB_Prefix+"("+TimeToString(time1)+")";
   color obClr = clrBlack;
   
   if (is_OB_Valid){obClr = is_OB_UP ? CLR_UP : CLR_DOWN;}
   else if (!is_OB_Valid){obClr = def_invalid;}
   
   string obText = "";
   
   if (is_OB_Valid){obText = is_OB_UP ? ob_up_name+ShortToString(ob_up_unicode) : ob_down_name+ShortToString(ob_down_unicode);}
   else if (!is_OB_Valid){obText = range_name;}
   
   if (!is_OB_Valid){
      if (ObjectFind(0,obName) < 0){
         CreateRec(obName,time1,price1,time2,price2,obClr,obText);
      }
   }
   else if (is_OB_Valid){
      if (ObjectFind(0,obName) < 0){
         CreateRec(obName,time1,price1,time2,price2,obClr,obText);
         
         if (prt){Print("Old Arraysize = ",ArraySize(totalOBs_names));}
         ArrayResize(totalOBs_names,ArraySize(totalOBs_names)+1);
         if (prt){Print("New Arraysize = ",ArraySize(totalOBs_names));}
         totalOBs_names[ArraySize(totalOBs_names)-1] = obName;
         if (prt){ArrayPrint(totalOBs_names);}
         
         if (prt){Print("Old Arraysize = ",ArraySize(totalOBs_dates));}
         ArrayResize(totalOBs_dates,ArraySize(totalOBs_dates)+1);
         if (prt){Print("New Arraysize = ",ArraySize(totalOBs_dates));}
         totalOBs_dates[ArraySize(totalOBs_dates)-1] = time2;
         if (prt){ArrayPrint(totalOBs_dates);}

         if (prt){Print("Old Arraysize = ",ArraySize(totalOBs_is_signals));}
         ArrayResize(totalOBs_is_signals,ArraySize(totalOBs_is_signals)+1);
         if (prt){Print("New Arraysize = ",ArraySize(totalOBs_is_signals));}
         totalOBs_is_signals[ArraySize(totalOBs_is_signals)-1] = false;
         if (prt){ArrayPrint(totalOBs_is_signals);}
      }
   }
   
   breakoutBarIndex = -1;
   breakoutTime = 0;
   impulseHigh = 0;
   impulseLow = 0;
   is_OB_UP = false;
   is_OB_DOWN = false;
}

We’re now at the part where our MQL5 Expert Advisor gets serious, turning breakouts into full-fledged order blocks with some slick chart visuals! This is where we confirm impulsive moves, draw order blocks, and keep track of them like a forex librarian. Let’s dive in with some energy to keep you hooked!

We start by checking if a breakout has happened and enough time has passed to validate it. We use "breakoutBarIndex >= 0" to ensure a breakout candle was flagged and compare "TimeCurrent" (the current chart time) to "breakoutTime + waitBars * PeriodSeconds()" to confirm we’ve waited "waitBars" candles (set by our input). This waiting period is like letting the market catch its breath before we call it an order block.

If the condition holds, we call "DetectImpulsiveMovement" with "impulseHigh", "impulseLow", "waitBars", and a hardcoded "impulseThreshold" of 1 to check if the breakout led to a big move. This sets our "is_OB_UP" or "is_OB_DOWN" flags if the move is strong enough, signaling a bullish or bearish order block.

We create a "bool" variable "is_OB_Valid" set to true if either "is_OB_UP" or "is_OB_DOWN" is true, confirming we’ve got a legit order block. Next, we set up the rectangle’s coordinates: "time1" (a "datetime") is grabbed using "iTime" for the candle at index "rangeCandles + waitBars + 1", marking the start of our block. We set "price1" (a "double") to "impulseHigh" for the top of the zone. For the rectangle’s end, we calculate "visisbleBars" (an "int") using ChartGetInteger with "CHART_VISIBLE_BARS" to get the number of visible chart bars. If "is_OB_Valid" is true, we set "time2" (a "datetime") to "time1 + (visisbleBars/1) * PeriodSeconds()" to stretch the rectangle across the chart; otherwise, we use "time(waitBars + 1)" for a shorter invalid range. We set "price2" (a "double") to "impulseLow" for the bottom of the zone.

We create a unique name for the rectangle with "obName" (a "string") using "OB_Prefix" and TimeToString(time1) to include the timestamp, like naming a new pet. We initialize "obClr" (a "color") to "clrBlack". If "is_OB_Valid" is true, we set "obClr" to "CLR_UP" (lime) for bullish blocks if "is_OB_UP" is true, or "CLR_DOWN" (red) for bearish if "is_OB_DOWN" is true. If "is_OB_Valid" is false, we use "def_invalid" (blue) for a non-order-block range. For the label, we set "obText" (a "string") to "ob_up_name + ShortToString(ob_up_unicode)" for bullish blocks, "ob_down_name + ShortToString(ob_down_unicode)" for bearish, or "range_name" for invalid ranges, adding those cool Unicode symbols for extra flair.

If the block is invalid ("is_OB_Valid" is false) and doesn’t exist yet (checked with "ObjectFind" on "obName"), we call "CreateRec" with "obName", "time1", "price1", "time2", "price2", "obClr", and "obText" to draw a blue rectangle labeled “Range”. If it’s a valid order block, we do the same to draw a lime or red rectangle with the right label. But we also store this block’s info: we resize "totalOBs_names" (a "string" array) using "ArrayResize" to add "obName", logging sizes with "Print" and "ArrayPrint" if "prt" is true. We do the same for "totalOBs_dates" (a "datetime" array) to store "time2" and "totalOBs_is_signals" (a "bool" array) to add false (no trade signal yet). These arrays keep our blocks organized, like a forex filing cabinet.

Finally, we reset our variables: "breakoutBarIndex" to -1, "breakoutTime" to 0, "impulseHigh" and "impulseLow" to 0, and "is_OB_UP" and "is_OB_DOWN" to false. This clears the slate for the next breakout, keeping our EA ready for action. With this, we’re drawing order blocks like a pro, making our chart a treasure map of trading zones. Stick around—this is about to get even spicier when we start firing off trades! See what we got!

We can see that we get to visualize the order blocks. By the way, if you don't want the unverified consolidation ranges, you can skip their visualization. We now need to open trades based on confirmed signals. Before that, we will define functions to validate entry conditions real quick.
//+------------------------------------------------------------------+
//|      1. CHECK TRADING VOLUME                                     |
//+------------------------------------------------------------------+

double Check1_ValidateVolume_Lots(double lots){
   double symbolVol_Min = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   double symbolVol_Max = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   double symbolVol_STEP = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
      
   double accepted_Lots;
   double CurrentLots = lots;
   accepted_Lots = MathMax(MathMin(CurrentLots,symbolVol_Max),symbolVol_Min);
   
   int lotDigits = 0;
   if (symbolVol_Min == 1) lotDigits = 0;
   if (symbolVol_Min == 0.1) lotDigits = 1;
   if (symbolVol_Min == 0.01) lotDigits = 2;
   if (symbolVol_Min == 0.001) lotDigits = 3;

   double normalized_lots = NormalizeDouble(accepted_Lots,lotDigits);
   //Print("MIN LOTS = ",symbolVol_Min,", NORMALIZED LOTS = ",normalized_lots);
   
   return (normalized_lots);
}



//+------------------------------------------------------------------+
//|      2. CHECK MONEY/MARGIN TO OPEN POSITION                      |
//+------------------------------------------------------------------+

bool Check2_Margin(ENUM_ORDER_TYPE Order_Type,double lot_Vol){
   double margin;
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   double openPrice = (Order_Type == ORDER_TYPE_BUY) ? Ask : Bid;
   
   bool result = OrderCalcMargin(Order_Type,_Symbol,lot_Vol,openPrice,margin);
   if (result == false){
      ResetLastError();
      Print("ERROR: Something Unexpected Happened While Calculating Margin\n",
            "RESULT = ",result,", ERROR = ",_LastError);
      return (false);
   }
   if (margin > AccountInfoDouble(ACCOUNT_MARGIN_FREE)){
      Print("WARNING! NOT ENOUGH MARGIN TO OPEN THE POSITION. NEEDED = ",margin);
      return (false);
   }
   return (true);
}



//+------------------------------------------------------------------+
//|      3. CHECK VOLUME LIMIT                                       |
//+------------------------------------------------------------------+

bool Check3_VolumeLimit(double lots_Vol_Limit){
   double volumeLimit = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
   double symb_Vol_Max40 = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   double allowed_Vol_Lim = (volumeLimit == 0) ? symb_Vol_Max40 : volumeLimit;
   if (getAllVolume()+lots_Vol_Limit > allowed_Vol_Lim){
      Print("WARNING! VOLUME LIMIT REACHED: LIMIT = ",allowed_Vol_Lim);
      return (false);
   }
   return (true);
}

double getAllVolume(){
   ulong ticket=0;
   double Volume=0;
   
   for (int i=PositionsTotal()-1 ;i>=0 ;i--){
      ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket)){
         if (PositionGetString(POSITION_SYMBOL)==_Symbol){
            Volume += PositionGetDouble(POSITION_VOLUME);
         }
      }
   }
   
   for (int i=OrdersTotal()-1 ;i>=0 ;i--){
      ticket = OrderGetTicket(i);
      if (OrderSelect(ticket)){
         if (OrderGetString(ORDER_SYMBOL)==_Symbol){
            Volume += OrderGetDouble(ORDER_VOLUME_CURRENT);
         }
      }
   }
   return (Volume);
}



//+------------------------------------------------------------------+
//|      4. CHECK TRADE LEVELS                                       |
//+------------------------------------------------------------------+

bool Check4_TradeLevels(ENUM_POSITION_TYPE pos_Type,double sl=0,double tp=0,ulong tkt=0){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   int stopLevel = (int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   int freezeLevel = (int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   int spread = (int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
   
   double stopLevel_Pts = stopLevel*_Point;
   double freezeLevel_Pts = freezeLevel*_Point;
   
   if (pos_Type == POSITION_TYPE_BUY){
      // STOP LEVELS CHECK
      if (tp > 0 && tp - Bid < stopLevel_Pts){
         Print("WARNING! BUY TP ",tp,", Bid ",Bid," (TP-Bid = ",NormalizeDouble((tp-Bid)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         return (false);
      }
      if (sl > 0 && Bid - sl < stopLevel_Pts){
         Print("WARNING! BUY SL ",sl,", Bid ",Bid," (Bid-SL = ",NormalizeDouble((Bid-sl)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         return (false);
      }
      // FREEZE LEVELS CHECK
      if (tp > 0 && tp - Bid < freezeLevel_Pts){
         Print("WARNING! BUY TP ",tp,", Bid ",Bid," (TP-Bid = ",NormalizeDouble((tp-Bid)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         return (false);
      }
      if (sl > 0 && Bid - sl < freezeLevel_Pts){
         Print("WARNING! BUY SL ",sl,", Bid ",Bid," (Bid-SL = ",NormalizeDouble((Bid-sl)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         return (false);
      }
   }
   if (pos_Type == POSITION_TYPE_SELL){
      // STOP LEVELS CHECK
      if (tp > 0 && Ask - tp < stopLevel_Pts){
         Print("WARNING! SELL TP ",tp,", Ask ",Ask," (Ask-TP = ",NormalizeDouble((Ask-tp)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         return (false);
      }
      if (sl > 0 && sl - Ask < stopLevel_Pts){
         Print("WARNING! SELL SL ",sl,", Ask ",Ask," (SL-Ask = ",NormalizeDouble((sl-Ask)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         return (false);
      }
      
      // FREEZE LEVELS CHECK
      if (tp > 0 && Ask - tp < freezeLevel_Pts){
         Print("WARNING! SELL TP ",tp,", Ask ",Ask," (Ask-TP = ",NormalizeDouble((Ask-tp)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         return (false);
      }
      if (sl > 0 && sl - Ask < freezeLevel_Pts){
         Print("WARNING! SELL SL ",sl,", Ask ",Ask," (SL-Ask = ",NormalizeDouble((sl-Ask)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         return (false);
      }
   }
   
   if (tkt > 0){
      bool result = PositionSelectByTicket(tkt);
      if (result == false){
         Print("ERROR Selecting The Position (CHECK) With Ticket # ",tkt);
         return (false);
      }
      double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT);
      double pos_SL = PositionGetDouble(POSITION_SL);
      double pos_TP = PositionGetDouble(POSITION_TP);
      
      bool slChanged = MathAbs(pos_SL - sl) > point;
      bool tpChanged = MathAbs(pos_TP - tp) > point;

      //bool slChanged = pos_SL != sl;
      //bool tpChanged = pos_TP != tp;
      
      if (!slChanged && !tpChanged){
         Print("ERROR. Pos # ",tkt," Already has Levels of SL: ",pos_SL,
               ", TP: ",pos_TP," NEW[SL = ",sl," | TP = ",tp,"]. NO POINT IN MODIFYING!!!");
         return (false);
      }
   }
   
   return (true);
}

//+------------------------------------------------------------------+
//|      5. CHECK & CORRECT TRADE LEVELS                             |
//+------------------------------------------------------------------+

double Check5_TradeLevels_Rectify(ENUM_POSITION_TYPE pos_Type,double sl=0,double tp=0){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   int stopLevel = (int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   int freezeLevel = (int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   int spread = (int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
   
   double stopLevel_Pts = stopLevel*_Point;
   double freezeLevel_Pts = freezeLevel*_Point;
   
   double accepted_price = 0.0;
   
   if (pos_Type == POSITION_TYPE_BUY){
      // STOP LEVELS CHECK
      if (tp > 0 && tp - Bid < stopLevel_Pts){
         accepted_price = Bid+stopLevel_Pts;
         Print("WARNING! BUY TP ",tp,", Bid ",Bid," (TP-Bid = ",NormalizeDouble((tp-Bid)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
      if (sl > 0 && Bid - sl < stopLevel_Pts){
         accepted_price = Bid-stopLevel_Pts;
         Print("WARNING! BUY SL ",sl,", Bid ",Bid," (Bid-SL = ",NormalizeDouble((Bid-sl)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
      // FREEZE LEVELS CHECK
      if (tp > 0 && tp - Bid < freezeLevel_Pts){
         accepted_price = Bid+freezeLevel_Pts;
         Print("WARNING! BUY TP ",tp,", Bid ",Bid," (TP-Bid = ",NormalizeDouble((tp-Bid)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
      if (sl > 0 && Bid - sl < freezeLevel_Pts){
         accepted_price = Bid-freezeLevel_Pts;
         Print("WARNING! BUY SL ",sl,", Bid ",Bid," (Bid-SL = ",NormalizeDouble((Bid-sl)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
   }
   if (pos_Type == POSITION_TYPE_SELL){
      // STOP LEVELS CHECK
      if (tp > 0 && Ask - tp < stopLevel_Pts){
         accepted_price = Ask-stopLevel_Pts;
         Print("WARNING! SELL TP ",tp,", Ask ",Ask," (Ask-TP = ",NormalizeDouble((Ask-tp)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
      if (sl > 0 && sl - Ask < stopLevel_Pts){
         accepted_price = Ask+stopLevel_Pts;
         Print("WARNING! SELL SL ",sl,", Ask ",Ask," (SL-Ask = ",NormalizeDouble((sl-Ask)/_Point,_Digits),") WITHIN STOP LEVEL OF ",stopLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
      
      // FREEZE LEVELS CHECK
      if (tp > 0 && Ask - tp < freezeLevel_Pts){
         accepted_price = Ask-freezeLevel_Pts;
         Print("WARNING! SELL TP ",tp,", Ask ",Ask," (Ask-TP = ",NormalizeDouble((Ask-tp)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
      if (sl > 0 && sl - Ask < freezeLevel_Pts){
         accepted_price = Ask+freezeLevel_Pts;
         Print("WARNING! SELL SL ",sl,", Ask ",Ask," (SL-Ask = ",NormalizeDouble((sl-Ask)/_Point,_Digits),") WITHIN FREEZE LEVEL OF ",freezeLevel);
         Print("PRICE MODIFIED TO: ",accepted_price);
         return (accepted_price);
      }
   }
   return (accepted_price);
}
We will skip the explanation of the functions and dive straight to their use since they are self explanatory.
for (int j=ArraySize(totalOBs_names)-1; j>=0; j--){
   string obNAME = totalOBs_names[j];
   bool obExist = false;
   
   double obHigh = ObjectGetDouble(0,obNAME,OBJPROP_PRICE,0);
   double obLow = ObjectGetDouble(0,obNAME,OBJPROP_PRICE,1);
   datetime objTime1 = (datetime)ObjectGetInteger(0,obNAME,OBJPROP_TIME,0);
   datetime objTime2 = (datetime)ObjectGetInteger(0,obNAME,OBJPROP_TIME,1);
   color obColor = (color)ObjectGetInteger(0,obNAME,OBJPROP_COLOR);
   
   if (time(1) < objTime2){
      obExist = true;
   }
   
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   if (obColor == CLR_UP && Ask > obHigh && close(1) > obHigh && open(1) < obHigh && !totalOBs_is_signals[j]){
      if (prt){Print("BUY SIGNAL for (",obNAME,") Now @ ",Ask);}
      double sl = Bid-(sl_tp_pts*r2r_ratio)*_Point;
      double tp = Bid+sl_tp_pts*_Point;
      
      double trade_lots = Check1_ValidateVolume_Lots(inpLot);
      
      if (Check2_Margin(ORDER_TYPE_BUY,trade_lots) &&
          Check3_VolumeLimit(trade_lots) &&
          Check4_TradeLevels(POSITION_TYPE_BUY,sl,tp)
      ){
         obj_Trade.Buy(trade_lots,_Symbol,Ask,sl,tp);
         totalOBs_is_signals[j] = true;
         if (prt){ArrayPrint(totalOBs_names,_Digits," [< >] ");}
         if (prt){ArrayPrint(totalOBs_is_signals,_Digits," [< >] ");}
      }
      
   }
   else if (obColor == CLR_DOWN && Bid < obLow && close(1) < obLow && open(1) > obLow && !totalOBs_is_signals[j]){
      if (prt){Print("SELL SIGNAL for (",obNAME,") Now @ ",Bid);}
      double sl = Ask+(sl_tp_pts*r2r_ratio)*_Point;
      double tp = Ask-sl_tp_pts*_Point;
      
      double trade_lots = Check1_ValidateVolume_Lots(inpLot);

      if (Check2_Margin(ORDER_TYPE_SELL,trade_lots) &&
          Check3_VolumeLimit(trade_lots) &&
          Check4_TradeLevels(POSITION_TYPE_SELL,sl,tp)
      ){
         obj_Trade.Sell(trade_lots,_Symbol,Bid,sl,tp);
         totalOBs_is_signals[j] = true;
         if (prt){ArrayPrint(totalOBs_names,_Digits," [< >] ");}
         if (prt){ArrayPrint(totalOBs_is_signals,_Digits," [< >] ");}
      }

   }
   
   
   if (obExist == false){
      bool removeName = ArrayRemove(totalOBs_names,0,1);
      bool removeTime = ArrayRemove(totalOBs_dates,0,1);
      bool remove_isSignal = ArrayRemove(totalOBs_is_signals,0,1);
      
      if (removeName && removeTime && remove_isSignal){
         if (prt){
            Print("Success removing the OB DATA from the arrays. New data is as below:");
            Print("Total sizes => OBs: ",ArraySize(totalOBs_names),", TIMEs: ",ArraySize(totalOBs_dates),", SIGNALs: ",ArraySize(totalOBs_is_signals));
            ArrayPrint(totalOBs_names);
            ArrayPrint(totalOBs_dates);
            ArrayPrint(totalOBs_is_signals);
         }
      }
      
   }
   
}
We’re at the edge of our seats now, diving into the part of our MQL5 Expert Advisor where we take our beautifully drawn order blocks and turn them into cold, hard trades! Here, still inside our "OnTick" function, is where we loop through our order blocks, check for trade signals, fire off buy or sell orders, and clean up old blocks like a forex janitor. Let’s jump in with some spark to keep you glued to the action!

We start by looping through our "totalOBs_names" array (our list of order block names) in reverse, from ArraySize(totalOBs_names)-1" down to 0, using a loop variable "j" (an "int"). For each block, we grab its name into "obNAME" (a "string") from "totalOBs_names[j]" and set a "bool" variable "obExist" to false to track if the block is still relevant.

We fetch the block’s details using MetaTrader 5’s object functions: "obHigh" and "obLow" (both "double") are pulled with ObjectGetDouble using "OBJPROP_PRICE" for the rectangle’s top (index 0) and bottom (index 1). We get "objTime1" and "objTime2" (both "datetime") with "ObjectGetInteger" using "OBJPROP_TIME" for the rectangle’s start and end times. The block’s color, "obColor" (a "color"), comes from "ObjectGetInteger" with "OBJPROP_COLOR". If the latest candle’s time, grabbed with "time(1)" (using "iTime"), is before "objTime2", we set "obExist" to true, meaning the block is still active on the chart.

Next, we grab the current market prices: "Ask" and "Bid" (both "double") using SymbolInfoDouble with "SYMBOL_ASK" and "SYMBOL_BID", normalized to the symbol’s digits with "NormalizeDouble". Now, we hunt for trade signals. For a bullish order block ("obColor" equals "CLR_UP"), we check if "Ask" is above "obHigh", the latest candle’s close (via "close(1)" using "iClose") is above "obHigh", the open (via "open(1)" using "iOpen") is below "obHigh", and "totalOBs_is_signals[j]" is false (no trade yet). This confirms price has crossed into the bullish block, signaling a buy. If true, we log a “BUY SIGNAL” with "Print" if "prt" is true. We calculate "sl" (stop-loss) as "Bid - (sl_tp_pts * r2r_ratio) * _Point" and "tp" (take-profit) as "Bid + sl_tp_pts * _Point", using our inputs "sl_tp_pts" and "r2r_ratio". We validate the lot size with "Check1_ValidateVolume_Lots" using "inpLot", storing it in "trade_lots" (a "double"). If "Check2_Margin" (for "ORDER_TYPE_BUY"), "Check3_VolumeLimit", and "Check4_TradeLevels" (for "POSITION_TYPE_BUY") all pass, we call "obj_Trade.Buy" with "trade_lots", the symbol, "Ask", "sl", and "tp" to open a buy trade. We set "totalOBs_is_signals[j]" to true to prevent duplicate trades and log arrays with "ArrayPrint" if "prt" is true.

For a bearish order block ("obColor" equals "CLR_DOWN"), we check if "Bid" is below "obLow", the close (via "close(1)") is below "obLow", the open (via "open(1)") is above "obLow", and "totalOBs_is_signals[j]" is false. This signals a sell as price enters the bearish block. We log a “SELL SIGNAL” with "Print" if "prt" is true, set "sl" as "Ask + (sl_tp_pts * r2r_ratio) * _Point" and "tp" as "Ask - sl_tp_pts * _Point". After validating "trade_lots" with "Check1_ValidateVolume_Lots" and passing "Check2_Margin" (for "ORDER_TYPE_SELL"), "Check3_VolumeLimit", and "Check4_TradeLevels" (for "POSITION_TYPE_SELL"), we call "obj_Trade.Sell" to open a sell trade. We flip "totalOBs_is_signals[j]" to true and log arrays if "prt" is true.
Finally, we tidy up. If "obExist" is false (the block’s expired), we use ArrayRemove to delete the entry at index 0 from "totalOBs_names", "totalOBs_dates", and "totalOBs_is_signals". If all removals succeed, we log a success message with "Print" if "prt" is true, showing the new array sizes and contents with the ArrayPrint function. This keeps our block list fresh, like clearing out old snacks from the fridge. See what we get.

Bearish Order Block:
Bullish Order Block:
From the image, we can see that we can identify, create, and visualize the respective order blocks, hence achieving our objectives.

Backtesting


We compiled a sample test in a Graphics Interchange Format as below.

Graph:
Results:

Conclusion


We’ve crossed the finish line, having built a solid MQL5 Expert Advisor that automates order block trading with clarity and precision. With functions like "IsConsolidationEqualHighsAndLows" to detect consolidation ranges, "CreateRec" to paint order blocks on the chart, and "obj_Trade" to execute trades, our EA systematically targets institutional price zones. Testing shows it identifies bullish and bearish order blocks, manages trades with strict risk controls, and cuts down manual effort while keeping our trading steady. For traders at any level, this EA offers a structured way to tap into smart money movements. As we wrap up, this EA stands as a strong foundation for automated forex trading, ready for you to take it further with your own tweaks and strategies. If you prefer a YouTube video tutorial, we gat you buddy!

Disclaimer: This MQL5 Expert Advisor is for educational purposes only and does not guarantee profits; forex trading carries significant risk, and we are not liable for losses. Test thoroughly on a demo account before risking real capital.
This sets the backbone of our order blocks EA logic, open to endless advancements—add new rules, filters, or custom tweaks to make it your own. Good luck and happy trading!

Disclaimer: The ideas and strategies presented in this resource are solely those of the author and are intended for informational and educational purposes only. They do not constitute financial advice, and past performance is not indicative of future results. All materials, including but not limited to text, images, files, and any downloadable content, are protected by copyright and intellectual property laws and are the exclusive property of Forex Algo-Trader or its licensors. Reproduction, distribution, modification, or commercial use of these materials without prior written consent from Forex Algo-Trader is strictly prohibited and may result in legal action. Users are advised to exercise extreme caution, perform thorough independent research, and consult with qualified financial professionals before implementing any trading strategies or decisions based on this resource, as trading in financial markets involves significant risk of loss.

Recent Comments

Go to discussion to Comment or View other Comments

No comments yet. Be the first to comment!