Viewing the resource: Building a Supply and Demand Zones Expert Advisor in MQL5 for Automated Forex Trading

Building a Supply and Demand Zones Expert Advisor in MQL5 for Automated Forex Trading

Allan Munene Mutiiria 2025-10-16 12:43:26 192 Views
This article explores the creation of an MQL5 Expert Advisor (EA) for MetaTrader 5 that identifies s...

Introduction


Supply and demand zones are key price areas in forex markets where imbalances between buyers and sellers create strong reversal points, often driven by institutional activity. Manually spotting and trading these zones can be time-intensive and prone to human error, especially in fast-moving markets. This article breaks down an MQL5 Expert Advisor (EA) for MetaTrader 5 that automates the detection, validation, and trading of supply and demand zones. It scans for consolidation ranges, verifies impulsive breakouts, draws zones on charts with customizable visuals, and triggers trades with built-in risk tools like stop-loss, take-profit, and trailing stops. We'll outline a straightforward blueprint, step-by-step implementation, testing insights, and key takeaways to empower traders of any experience level. If you prefer a hands-on guide, check out our YouTube channel for tutorials!


Understanding Supply and Demand Zones

Before exploring our Expert Advisor's blueprint, let's clarify the core concepts behind supply and demand zones, as they underpin our automated trading strategy:

  • Demand Zones: These form in downtrends as areas where buying pressure overwhelms selling, leading to sharp upward reversals. We identify them as consolidation ranges followed by an impulsive bullish breakout, marking spots where institutions accumulate positions. When price revisits these zones, it often bounces higher, creating reliable buy setups.
  • Supply Zones: Emerging in uptrends, these are regions where selling dominates, causing abrupt downward moves. Detected as tight ranges before a bearish impulse, they signal institutional distribution. Price returning to these zones typically encounters resistance, offering sell opportunities.

Our EA targets these zones to capture institutional footprints, using customizable filters like impulse validation and trend confirmation for high-probability trades while minimizing false signals.


Roadmap: Blueprint for Our Supply and Demand Zones Expert Advisor

Our objective is to develop a flexible MQL5 Expert Advisor (EA) for MetaTrader 5 that automates supply and demand zone trading in forex, making it user-friendly for beginners and pros alike while emphasizing accuracy and capital protection. Here's the high-level blueprint of our approach:

  1. Detect Consolidation Ranges: We'll define logic to identify tight price ranges (e.g., using a set number of bars and max spread) that precede impulsive breakouts, distinguishing demand (bullish) and supply (bearish) zones based on the direction of the move.
  2. Validate Zones: Incorporate optional impulse checks over a configurable number of bars to confirm strong breakouts, along with filters for zone size, overlaps, duplicates, and trend alignment to reduce noise.
  3. Visualize and Manage Zones: The EA will draw rectangles on the chart with customizable colors for demand/supply, tested, and broken zones, adding labels for clarity. It will track zone status (e.g., tested, broken, expired) and optionally delete invalid ones to keep the chart clean.
  4. Automate Trade Execution: Trigger buy/sell trades when price retests valid zones, with confirmation signals like candle overlaps and optional trend filters. Include lot sizing, stop-loss, take-profit, and trailing stop mechanics for risk control.
  5. Handle Retests and Limits: Support modes for no/limited/unlimited retesting of zones, max trades per zone, and handling of broken zones to adapt to different strategies.
  6. Test and Refine: Backtest on historical data and demo forward-test to assess performance, tweaking parameters like consolidation bars, impulse multipliers, and distances for optimal results.

This blueprint ensures our EA captures supply/demand dynamics efficiently, automating high-probability setups while prioritizing risk management. See what we will be achieving below and let's dive in—traders of all stripes, this one's for you! 😂


Implementation of the Supply and Demand Zones Expert Advisor


We kick off the development of our MQL5 Expert Advisor (EA) by defining the input parameters and core structures that control its behavior, visualization, and trading logic. These customizable settings make the EA adaptable, allowing users to fine-tune zone detection, risk management, and features like trailing stops for optimal performance.

//+------------------------------------------------------------------+  
//|                                    Supply and Demand Zone EA.mq5 |  
//|                           Copyright 2025, Allan Munene Mutiiria. |  
//|                                   https://t.me/Forex_Algo_Trader |  
//+------------------------------------------------------------------+  
#property copyright "Copyright 2025, Allan Munene Mutiiria."  
#property link "https://t.me/Forex_Algo_Trader"  
#property version "1.00"  
#include <Trade/Trade.mqh>  
CTrade obj_Trade;  
enum TradeTestedZonesMode{  
   NoRetrade,  
   LimitedRetrade,  
   UnlimitedRetrade  
};  
enum BrokenZonesMode{  
   AllowBroken,  
   NoBroken  
};  
enum ZonesSizeMode{  
   NoRestriction,  
   EnforceLimits  
};  
enum TrendConfirmationMode{  
   NoConfirmation,  
   ConfirmTrend  
};  
input double tradeLotSize = 0.01;  
input bool enableTrading = true;  
input bool enableTrailingStop = true;  
input double trailingStopPoints = 30;  
input double minProfitToTrail = 50;  
input int uniqueMagicNumber = 12345;  
input int consolidationBars = 5;  
input double maxConsolidationSpread = 1000000000000000000;  
input double stoplossDistance = 200;  
input double takeprofitDistance = 400;  
input double minMoveAwayPoints = 50;  
input bool deleteBrokenZonesFromChart = false;  
input bool deleteExpiredZonesFromChart = false;  
input int zoneExtensionBars = 150;  
input bool enableImpulseValidation = true;  
input int impulseCheckBars = 3;  
input double impulseMultiplier = 1.0;  
input TradeTestedZonesMode tradeTestedMode = NoRetrade;  
input int maxTradesPerZone = 2;  
input BrokenZonesMode brokenZoneMode = AllowBroken;  
input color demandZoneColor = clrBlue;  
input color supplyZoneColor = clrRed;  
input color testedDemandZoneColor = clrBlueViolet;  
input color testedSupplyZoneColor = clrOrange;  
input color brokenZoneColor = clrDarkGray;  
input color labelTextColor = clrBlack;  
input ZonesSizeMode zoneSizeRestriction = NoRestriction;  
input double minZonePoints = 50;  
input double maxZonePoints = 300;  
input TrendConfirmationMode trendConfirmation = NoConfirmation;  
input int trendLookbackbars = 10;  
input double minTrendPoints = 1;  
struct SDZone {  
  double high;  
  double low;  
  datetime startTime;  
  datetime endTime;  
  datetime breakoutTime;  
  bool isDemand;  
  bool tested;  
  bool broken;  
  bool readyForTest;  
  int tradeCount;  
  string name;  
};  
SDZone zones[];  
SDZone potentialZones[];  
int maxZones = 50;

We begin by including the "Trade.mqh" library with "#include <Trade/Trade.mqh>" to access the CTrade class for handling trades. We instantiate "obj_Trade" as a CTrade object to manage buy/sell operations with ease. Next, we define several enums for flexible modes: "TradeTestedZonesMode" (NoRetrade, LimitedRetrade, UnlimitedRetrade) to control retesting; "BrokenZonesMode" (AllowBroken, NoBroken) for handling broken zones; "ZonesSizeMode" (NoRestriction, EnforceLimits) for size filters; and "TrendConfirmationMode" (NoConfirmation, ConfirmTrend) for trend checks.

The input parameters start with trading basics: "tradeLotSize" (double, default 0.01) for position volume; "enableTrading" (bool, true) to toggle automation; "enableTrailingStop" (bool, true) for dynamic stops; "trailingStopPoints" (double, 30) and "minProfitToTrail" (double, 50) for trailing logic; "uniqueMagicNumber" (int, 12345) to identify EA trades. For zone detection: "consolidationBars" (int, 5) sets the lookback; "maxConsolidationSpread" (double, a large value) limits range width; "stoplossDistance" (double, 200) and "takeprofitDistance" (double, 400) in points for risk levels; "minMoveAwayPoints" (double, 50) ensures price moves away before retests. Visualization and management include "deleteBrokenZonesFromChart" and "deleteExpiredZonesFromChart" (both bool, false); "zoneExtensionBars" (int, 150) for zone lifespan; "enableImpulseValidation" (bool, true) with "impulseCheckBars" (int, 3) and "impulseMultiplier" (double, 1.0) for breakout strength. Additional filters: "tradeTestedMode" (enum, NoRetrade); "maxTradesPerZone" (int, 2); "brokenZoneMode" (enum, AllowBroken); colors like "demandZoneColor" (clrBlue) for visuals; "zoneSizeRestriction" (enum, NoRestriction) with "minZonePoints" (50) and "maxZonePoints" (300); "trendConfirmation" (enum, NoConfirmation) with "trendLookbackbars" (10) and "minTrendPoints" (1). We define the "SDZone" struct to store zone data: prices ("high", "low"), times ("startTime", "endTime", "breakoutTime"), flags ("isDemand", "tested", "broken", "readyForTest"), "tradeCount" (int), and "name" (string). We create dynamic arrays "zones[]" and "potentialZones[]" for active and candidate zones, with "maxZones" (50) to cap storage. These foundations enable precise zone handling and trading—compilation should yield a clean setup, ready for the core functions. Let's keep building! 😂

See? That is all done! Alright, let's crank up the excitement a notch! With our parameters locked and loaded like a forex superhero's utility belt, we're now stepping into the EA's startup routine—the place where it wakes up, stretches, and gets ready to conquer the charts. Think of this as the EA's morning coffee ritual: simple, essential, and setting the tone for a productive day of zone hunting.

//+------------------------------------------------------------------+  
//|                                   Expert initialization function |  
//+------------------------------------------------------------------+  
int OnInit() {  
//---  
   obj_Trade.SetExpertMagicNumber(uniqueMagicNumber);  
//---  
   return(INIT_SUCCEEDED);  
}

The OnInit function is like the EA's grand entrance when you slap it onto a MetaTrader 5 chart. It fires up first thing, giving us a chance to prep our trading tools. Here, we call "SetExpertMagicNumber" on our "obj_Trade" object, feeding it the "uniqueMagicNumber" from our inputs (that 12345 default, like a secret agent code). This tags all our trades so the EA knows which ones are its babies, avoiding mix-ups if you've got other bots partying on the same chart. We wrap it up by returning INIT_SUCCEEDED, basically the EA flashing a thumbs-up to MetaTrader, saying, "All systems go—let's spot some zones!" No drama, just a quick setup to keep things smooth. Now, every good party needs a cleanup crew, right? Enter the OnDeinit function, the EA's polite exit strategy when you remove it from the chart or shut down the terminal.

//+------------------------------------------------------------------+  
//|                                 Expert deinitialization function |  
//+------------------------------------------------------------------+  
void OnDeinit(const int reason) {  
//---  
   ObjectsDeleteAll(0,"SDZone_");  
   ChartRedraw();  
}

This one's straightforward but oh-so-important, like tidying up after a wild night to avoid a messy hangover. We use ObjectsDeleteAll to zap every chart object starting with "SDZone_" (our zone rectangles and labels—poof, gone!. Don't worry yet; we will be defining our zones later using that prefix). Then, ChartRedraw refreshes the chart, leaving it sparkling clean. The "reason" parameter? It's just MetaTrader whispering why the EA's bowing out (like a restart or removal), but we don't sweat it here—we're all about that fresh start vibe.

With init and deinit in the bag, our EA's got its basics covered, like a newbie trader learning to tie their shoelaces before running the marathon. But hold onto your charts, folks—the real fun kicks in next as we dive into the heartbeat of the operation: the tick handler and zone detection. Trust me, it's where the magic (and maybe a few pips) starts flying!

Alright, let's shift gears a bit and zoom in on the engine that keeps our EA humming—the part where we debug, detect, and tie it all into the market's rhythm. Imagine this as the EA's daily routine: checking the mail (for new zones), jotting notes (debugging), and only getting excited when something fresh pops up. We'll keep it light and straightforward, so you can follow along like we're chatting over coffee about how to spot those hidden market gems. To start, we've got "PrintZones," a super-simple debugger that's like the EA's personal journal, logging zone details so we can peek inside without guessing.

//+------------------------------------------------------------------+  
//|                                        Print zones for debugging |  
//+------------------------------------------------------------------+  
void PrintZones (SDZone &arr[]){  
   Print("Current zones count: ",ArraySize(arr));  
   for (int i=0; i<ArraySize(arr); i++){  
      Print("Zone ",i,": ",arr[i].name," endTime: ",TimeToString(arr[i].endTime));  
   }  
}

This function grabs a reference to our "SDZone" array (say, "zones[]") and "Print"s the count using ArraySize—quick tally! Then it loops through, logging each zone's index, name, and end time (made human-readable with TimeToString). It's our go-to for verifying zones are behaving, like a quick "show me the list" command. We will be calling it after creations to stay in the know, but it stays quiet in production unless you're in sleuth mode.

Now, for the fun part: "DetectZones," where the EA turns into a price whisperer, sniffing out consolidations that might bloom into supply or demand zones. It's like the EA saying, "Hold up, this quiet spot looks promising—let's investigate!"

//+------------------------------------------------------------------+  
//|                                   Detect Supply and Demand zones |  
//+------------------------------------------------------------------+  
void DetectZones (){  
   int startIndex = consolidationBars + 1;  
   if (iBars(_Symbol,_Period) < startIndex + 1) return;  
   bool isConsolidated = true;  
   double highPrice = iHigh(_Symbol,_Period,startIndex);  
   double lowPrice = iLow(_Symbol,_Period,startIndex);  
   for (int i = startIndex - 1; i>=2; i--){  
      highPrice = MathMax(highPrice, iHigh(_Symbol,_Period,i));  
      lowPrice = MathMin(lowPrice, iLow(_Symbol,_Period,i));  
      if (highPrice - lowPrice > maxConsolidationSpread * _Point){  
         isConsolidated = false;  
         break;  
      }  
   }  
   if (isConsolidated){  
      double closePrice = iClose(_Symbol,_Period,1);  
      double breakoutLow = iLow(_Symbol,_Period,1);  
      double breakoutHigh = iHigh(_Symbol,_Period,1);  
      bool isDemandZone = closePrice > highPrice && breakoutLow >= lowPrice;  
      bool isSupplyZone = closePrice < lowPrice && breakoutHigh <= highPrice;  
     
      if (isDemandZone || isSupplyZone){  
         double zoneSize = (highPrice - lowPrice) / _Point;  
         if (zoneSizeRestriction == EnforceLimits && (zoneSize < minZonePoints || zoneSize > maxZonePoints)) return;  
         datetime lastClosedBarTime = iTime(_Symbol,_Period,1);  
         bool overLaps = false;  
         for (int j=0; j<ArraySize(zones); j++){  
            if (lastClosedBarTime < zones[j].endTime){  
               double maxLow = MathMax(lowPrice,zones[j].low);  
               double minHigh = MathMin(highPrice,zones[j].high);  
               if (maxLow <= minHigh){  
                  overLaps = true;  
                  break;  
               }  
            }  
         }  
        
         bool duplicate = false;  
         for (int j=0; j<ArraySize(zones); j++){  
            if (lastClosedBarTime < zones[j].endTime){  
               if (MathAbs(zones[j].high - highPrice) < _Point && MathAbs(zones[j].low - lowPrice) < _Point){  
                  duplicate = true;  
                  break;  
               }  
            }  
         }  
        
         if (overLaps || duplicate) return;  
        
         if (enableImpulseValidation){  
            bool pot_overlaps = false;  
            for (int j=0; j<ArraySize(potentialZones); j++){  
               if (lastClosedBarTime < potentialZones[j].endTime){  
                  double maxLow = MathMax(lowPrice, potentialZones[j].low);  
                  double minHigh = MathMin(highPrice, potentialZones[j].high);  
                  if (maxLow <= minHigh){  
                     pot_overlaps = true;  
                     break;  
                  }  
               }  
            }  
           
            bool pot_duplicate = false;  
            for (int j=0; j<ArraySize(potentialZones); j++){  
               if (lastClosedBarTime < potentialZones[j].endTime){  
                  if (MathAbs(potentialZones[j].high - highPrice) < _Point && MathAbs(potentialZones[j].low - lowPrice) < _Point){  
                     pot_duplicate = true;  
                     break;  
                  }  
               }  
            }  
            if (pot_overlaps || pot_duplicate) return;  
           
            int potCount = ArraySize(potentialZones);  
            ArrayResize(potentialZones, potCount + 1);  
           
            potentialZones[potCount].high = highPrice;  
            potentialZones[potCount].low = lowPrice;  
            potentialZones[potCount].startTime = iTime(_Symbol,_Period,startIndex);  
            potentialZones[potCount].endTime = TimeCurrent() + PeriodSeconds(_Period) * zoneExtensionBars;  
            potentialZones[potCount].breakoutTime = iTime(_Symbol,_Period,1);  
            potentialZones[potCount].isDemand = isDemandZone;  
            potentialZones[potCount].tested = false;  
            potentialZones[potCount].broken = false;  
            potentialZones[potCount].readyForTest = false;  
            potentialZones[potCount].tradeCount = 0;  
            potentialZones[potCount].name = "PotentialZone_"+TimeToString(potentialZones[potCount].startTime,TIME_DATE|TIME_SECONDS);  
            Print("Potential zone created: ",(isDemandZone ? "Demand" : "Supply")," at ",lowPrice, " - ", highPrice, " endTime: ",TimeToString(potentialZones[potCount].endTime));  
         }  
        
         else {  
            int zoneCount = ArraySize(zones);  
            if (zoneCount >= maxZones){  
               ArrayRemove(zones, 0, 1);  
               zoneCount--;  
            }  
            ArrayResize(zones, zoneCount + 1);  
           
            zones[zoneCount].high = highPrice;  
            zones[zoneCount].low = lowPrice;  
            zones[zoneCount].startTime = iTime(_Symbol,_Period,startIndex);  
            zones[zoneCount].endTime = TimeCurrent() + PeriodSeconds(_Period) * zoneExtensionBars;  
            zones[zoneCount].breakoutTime = iTime(_Symbol,_Period,1);  
            zones[zoneCount].isDemand = isDemandZone;  
            zones[zoneCount].tested = false;  
            zones[zoneCount].broken = false;  
            zones[zoneCount].readyForTest = false;  
            zones[zoneCount].tradeCount = 0;  
            zones[zoneCount].name = "SDZone_"+TimeToString(zones[zoneCount].startTime,TIME_DATE|TIME_SECONDS);  
            Print("Zone created: ",(isDemandZone ? "Demand" : "Supply"), "zone: ",zones[zoneCount].name," at ",lowPrice, " - ", highPrice, " endTime: ",TimeToString(zones[zoneCount].endTime));  
            PrintZones(zones);  
         }  
      }  
   }  
}

Step by step: Set "startIndex" to look back "consolidationBars + 1" candles. If not enough bars ("iBars"), skip. Flag consolidated true, grab high/low from start. Loop back, update max/min— if range too wide ("maxConsolidationSpread" points), flag false. If tight, check last candle's close/low/high. Demand if close > range high and low >= range low; supply if close < range low and high <= range high. Yes? Check size—if restricted and out of bounds, skip. Get last bar time.

Scan "zones[]" for overlaps (crossing prices in active zones) or duplicates (near-identical prices). If so, ignore. For impulse validation, check "potentialZones[]" similarly. Add to "potentialZones[]" (resize, fill prices/times/flags/name, log creation). No validation? Add to "zones[]" (trim if maxed, resize, fill, log, call "PrintZones"). Self explanatory really.

Finally, OnTick—the EA's watchful eye, running on every price tick like a caffeinated guard dog. We need this now to detect the zones.

//+------------------------------------------------------------------+  
//|                                             Expert tick function |  
//+------------------------------------------------------------------+  
void OnTick() {  
//---  
   static datetime lastBarTime = 0;  
   datetime currentBarTime = iTime(_Symbol,_Period,0);  
   bool isNewBar = (currentBarTime != lastBarTime);  
   if (isNewBar){  
      lastBarTime = currentBarTime;  
      //Print("This is a new bar");  
      DetectZones();  
   }  
}

Static "lastBarTime" tracks the current bar ("iTime" index 0). New bar? Update and call "DetectZones." Efficient—only hunts on fresh data, saving energy for the big moves! When we run this, we get the following outcome.

There we go, solid foundation for zone spotting! But wait, there's more brewing—validation and updates are up next to polish these potentials into trading stars. Keep reading; the chart's about to light up!

Okay, let's amp up the thrill as we dig deeper into our EA's brainpower—now that we've got zones bubbling up from detections, it's time to put them through the wringer with some validation magic. Think of this as the EA's quality control checkpoint: "Is this zone a keeper or a sleeper?" We'll sift through potential zones, check for that explosive impulse breakout (because who wants a dud?), and only promote the stars to the big leagues. And yep, we'll weave this into our tick routine to keep the flow seamless. It's like auditioning for a talent show—only the impulsive ones get the spotlight!

Here's the "ValidatePotentialZones" function, our discerning judge that loops through candidates, times them out if expired, and tests for that "wow" factor before adding them to the main zones array or tossing them out.

//+------------------------------------------------------------------+  
//|                                         Validate Potential Zones |  
//+------------------------------------------------------------------+  
void ValidatePotentialZones(){  
   datetime lastClosedBarTime = iTime(_Symbol, _Period, 1);  
   for (int p = ArraySize(potentialZones) - 1; p >=0; p--){  
      if (lastClosedBarTime >= potentialZones[p].endTime){  
         Print("Potential zone expired and removed from array: ",potentialZones[p].name," endTime: ",TimeToString(potentialZones[p].endTime));  
         ArrayRemove(potentialZones, p, 1);  
         continue;  
      }  
       
      if (TimeCurrent() > potentialZones[p].breakoutTime + impulseCheckBars * PeriodSeconds(_Period)){  
         bool isImpulse = false;  
         int breakoutShift = iBarShift(_Symbol,_Period,potentialZones[p].breakoutTime,false);  
         double range = potentialZones[p].high - potentialZones[p].low;  
         double threshold = range * impulseMultiplier;  
         for (int shift = 1; shift <= impulseCheckBars; shift++){  
            if (shift+breakoutShift >= iBars(_Symbol, _Period)) continue;  
            double cl = iClose(_Symbol,_Period,shift);  
            if (potentialZones[p].isDemand){  
               if (cl >= potentialZones[p].high + threshold){  
                  isImpulse = true;  
                  break;  
               }  
            }  
            else {  
               if (cl <= potentialZones[p].low - threshold){  
                  isImpulse = true;  
                  break;  
               }  
            }  
         }  
         if (isImpulse){  
            double zoneSize = (potentialZones[p].high - potentialZones[p].low)/_Point;  
            if (zoneSizeRestriction == EnforceLimits && (zoneSize < minZonePoints || zoneSize > maxZonePoints)){  
               ArrayRemove(potentialZones, p, 1);  
               continue;  
            }  
            bool overlaps = false;  
            for (int j=0; j<ArraySize(zones); j++){  
               if (lastClosedBarTime < zones[j].endTime){  
                  double maxLow = MathMax(potentialZones[p].low,zones[j].low);  
                  double minHigh = MathMin(potentialZones[p].high,zones[j].high);  
                  if (maxLow <= minHigh){  
                     overlaps = true;  
                     break;  
                  }  
               }  
            }  
             
            bool duplicate = false;  
            for (int j=0; j<ArraySize(zones); j++){  
               if (lastClosedBarTime < zones[j].endTime){  
                  if (MathAbs(zones[j].high - potentialZones[p].high) < _Point && MathAbs(zones[j].low - potentialZones[p].low) < _Point){  
                     duplicate = true;  
                     break;  
                  }  
               }  
            }  
             
            if (overlaps || duplicate){  
               Print("Validated zone overlapsor duplicates, discarded");  
               ArrayRemove(potentialZones, p, 1);  
               continue;  
            }  
            int zoneCount = ArraySize(zones);  
            if (zoneCount >= maxZones){  
               ArrayRemove(zones, 0, 1);  
               zoneCount--;  
            }  
            ArrayResize(zones, zoneCount + 1);  
            zones[zoneCount] = potentialZones[p];  
            zones[zoneCount].name = "SDZone_"+TimeToString(zones[zoneCount].startTime,TIME_DATE|TIME_SECONDS);  
            zones[zoneCount].endTime = TimeCurrent() + PeriodSeconds(_Period)*zoneExtensionBars;  
            Print("Zone Validated ");  
            ArrayRemove(potentialZones, p, 1);  
            PrintZones(zones);  
         }  
         else {  
            Print("Potential Zone not impulsive, discarded.");  
            ArrayRemove(potentialZones, p, 1);  
         }  
      }  
   }  
}

Let's unpack this gem step by step, like opening a mystery box full of trading treats. We grab the time of the last closed bar with iTime at index 1—our reference point. Then, loop backward through "potentialZones[]" (from last to first with "ArraySize - 1" down to 0) to avoid index chaos when removing items.

First check: If the last bar time is past the zone's "endTime," it's expired—log it with "Print" and zap it using ArrayRemove at position p, then "continue" to the next. No lingering ghosts!

Next, see if enough time has passed since breakout ("TimeCurrent" > breakoutTime + "impulseCheckBars" periods via PeriodSeconds). If yes, flag "isImpulse" false. Get the bar shift of the breakout with "iBarShift" (false for exact match). Calculate the zone "range" (high - low) and "threshold" (range * "impulseMultiplier"—our "wow" factor).

Loop through post-breakout bars (shift 1 to "impulseCheckBars"): Skip if out of bounds ("iBars" check). Grab close with "iClose." For demand, if any close >= high + threshold, impulse true—break! For supply, if <= low - threshold, same deal. Strong move? Yes!

If impulsive, recalculate "zoneSize" in points—if restricted and off-limits, remove and continue. Then, check overlaps/duplicates against main "zones[]": Similar loops as in detection, comparing active zones' prices. If clash, log discard, remove from potentials, continue.

No issues? Add to "zones[]": Trim oldest if at "maxZones," resize, copy the struct, update name to "SDZone_," extend endTime, log "Zone Validated," remove from potentials, and call "PrintZones" to show the updated roster.

Not impulsive? Log discard and remove. Simple as that—only the firecrackers make the cut!

To make this hum in real-time, we slot it into "OnTick" right after "DetectZones" in the new bar block, like adding another step to our morning routine:

//+------------------------------------------------------------------+  
//|                                             Expert tick function |  
//+------------------------------------------------------------------+  
void OnTick() {  
//---  
   static datetime lastBarTime = 0;  
   datetime currentBarTime = iTime(_Symbol,_Period,0);  
   bool isNewBar = (currentBarTime != lastBarTime);  
   if (isNewBar){  
      lastBarTime = currentBarTime;  
      //Print("This is a new bar");  
      DetectZones();  
     ValidatePotentialZones();     }  
}
We have highlighted the specific change for you to be able to see where to add the function. We get this upon compilation.

Now the EA detects potentials and validates them on every new bar—efficient teamwork! But zones need love too; up next, we'll update and visualize them on the chart, turning code into colorful action. Get ready for some eye candy—your MetaTrader's about to pop! 😂 Just kidding though baddies!

Alright, let's turn up the visual vibes in our EA adventure—after all, what's the point of spotting killer supply and demand zones if you can't see them lighting up your chart like a forex fireworks show? We're now crafting the "UpdateZones" function, the artist of our code that draws those zones as rectangles, slaps on labels for clarity, and keeps everything fresh by handling expirations, readiness checks, and even broken zones. Why do we need this? Well, without updating and visualizing, your EA would be like a detective solving crimes but never sharing the clues—you'd have zones in memory, but no way to eyeball them on the chart for quick decisions or backtesting reviews. Plus, dynamic updates ensure the EA adapts to market moves: expiring old zones prevents clutter (charts get messy fast!), checking "readiness" waits for price to move away first (avoiding fakeouts right after breakout), and handling breaks respects real-world invalidation (e.g., a demand zone shattered by a deep drop means it's no longer reliable for buys). This keeps your strategy honest and your chart readable, turning raw data into actionable insights. Let's dive in with some deeper digs where it counts to make sure even newbies get why each bit matters.

//+------------------------------------------------------------------+  
//|                                        Draw and Update the zones |  
//+------------------------------------------------------------------+  
void UpdateZones(){  
   datetime lastClosedBarTime = iTime(_Symbol,_Period,1);  
   for (int i = ArraySize(zones)- 1; i>= 0; i--){  
      if (lastClosedBarTime >= zones[i].endTime){  
         Print("Zone expired and removed from array");  
         if (deleteExpiredZonesFromChart){  
            ObjectDelete(0,zones[i].name);  
            ObjectDelete(0,zones[i].name+"Label");  
         }  
         ArrayRemove(zones, i, 1);  
         continue;  
      }  
      bool wasReady = zones[i].readyForTest;  
      if (!zones[i].readyForTest){  
         double currentClose = iClose(_Symbol,_Period,1);  
         double zoneLevel = zones[i].isDemand ? zones[i].high : zones[i].low;  
         double distance = zones[i].isDemand ? (currentClose - zoneLevel) : (zoneLevel - currentClose);  
         if (distance > minMoveAwayPoints * _Point){  
            zones[i].readyForTest = true;  
         }  
      }  
       
      if (!wasReady && zones[i].readyForTest){  
         Print("Zone ready for test: ",zones[i].name);  
      }  
      if (brokenZoneMode == AllowBroken && !zones[i].tested){  
         double currentClose = iClose(_Symbol,_Period,1);  
         bool wasBroken = zones[i].broken;  
         if (zones[i].isDemand){  
            if (currentClose < zones[i].low){  
               zones[i].broken = true;  
            }  
         }  
         else {  
            if (currentClose > zones[i].high){  
               zones[i].broken = true;  
            }  
         }  
         if (!wasBroken && zones[i].broken){  
            Print("Zone broken in UpdateZones: ",zones[i].name);  
            ObjectSetInteger(0,zones[i].name, OBJPROP_COLOR,brokenZoneColor);  
            string labelName = zones[i].name + "Label";  
            string labelText = zones[i].isDemand ? "Demand Zone (Broken)" : "Supply Zone (Broken)";  
            ObjectSetString(0,labelName, OBJPROP_TEXT,labelText);  
            if (deleteBrokenZonesFromChart){  
               ObjectDelete(0,zones[i].name);  
               ObjectDelete(0,labelName);  
            }  
         }  
      }  
      if (ObjectFind(0, zones[i].name) >= 0 || (!zones[i].broken || !deleteBrokenZonesFromChart)){  
         color zoneColor;  
         if (zones[i].tested){  
            zoneColor = zones[i].isDemand ? testedDemandZoneColor : testedSupplyZoneColor;  
         }  
         else if (zones[i].broken){  
            zoneColor = brokenZoneColor;  
         }  
         else {  
            zoneColor = zones[i].isDemand ? demandZoneColor : supplyZoneColor;  
         }  
          
         ObjectCreate(0, zones[i].name, OBJ_RECTANGLE, 0, zones[i].startTime, zones[i].high, zones[i].endTime, zones[i].low);  
         ObjectSetInteger(0, zones[i].name, OBJPROP_COLOR, zoneColor);  
         ObjectSetInteger(0, zones[i].name, OBJPROP_FILL, true);  
         ObjectSetInteger(0, zones[i].name, OBJPROP_BACK, true);  
         string labelName = zones[i].name + "Label";  
         string labelText = zones[i].isDemand ? "Demand Zone" : "Supply Zone";  
         if (zones[i].tested) labelText += " (Tested)";  
         else if (zones[i].broken) labelText += " (Broken)";  
         datetime labelTime = zones[i].startTime + (zones[i].endTime - zones[i].startTime)/2;  
         double labelPrice = (zones[i].high + zones[i].low) /2;  
         ObjectCreate(0, labelName, OBJ_TEXT, 0, labelTime,labelPrice);  
         ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);  
         ObjectSetInteger(0, labelName, OBJPROP_COLOR, labelTextColor);  
         ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_CENTER);  
      }  
   }  
   ChartRedraw();  
}

Breaking it down like a pro trader dissecting a chart pattern—why? Because understanding the "why" here helps you tweak the EA for your style, like knowing why a stop-loss matters before setting it. We start with "lastClosedBarTime" from "iTime" at index 1 (the most recent complete candle)—this is our anchor for checks, ensuring we use stable data, not the live-ticking bar 0 that could change mid-update and cause glitches. Actually, that bar is incomplete always, but you can use it if you want! 😎

Loop backward through "zones[]" (ArraySize - 1 to 0) to safely remove items without messing indices—forward loops can skip or crash when deleting! First, if the zone's "endTime" is past (expired), log it, optionally delete its rectangle and label with ObjectDelete if "deleteExpiredZonesFromChart" is true (why? Keeps charts uncluttered; old zones lose relevance as market evolves), then "ArrayRemove" it and "continue." This pruning is key for performance—too many zones bog down the EA and your terminal. You can see the image we had previously kept all the 44 zones, and would still continue until we have I don't know how many; maybe an exponential tri-zillion? 😂

Next, check readiness: Store old "readyForTest" in "wasReady." If not ready, grab latest close with "iClose." For demand, "zoneLevel" is high (entry point); supply, low. Calculate "distance" away (close - level for demand, reverse for supply). If > "minMoveAwayPoints" in actual points (* _Point converts to price scale—important because points vary by symbol, like 0.00001 on EURUSD vs. 0.01 on stocks), flip to true. Why this distance? It ensures price has "left the nest" after breakout, filtering out immediate fake retests that could trigger bad trades—think of it as giving the market breathing room to confirm the zone's strength. Maybe you have heard of "Let the market breathe my boy!" And then 1 second later you are like, sorry bro, the account is gone but keep strong 😂. You need such friend, maybe we are meant to be!

Anyway, if it just became ready, log it—handy for tracking in tests. Then, if "brokenZoneMode" allows breaks and zone isn't tested: Grab close again, store old broken flag. For demand, if close < low, it's broken (price punched through support—zone invalidated). Supply: close > high (resistance breached). If newly broken, log, change rectangle color to "brokenZoneColor" with ObjectSetInteger (OBJPROP_COLOR), update label text to "(Broken)" via "ObjectSetString," and optionally delete objects if "deleteBrokenZonesFromChart" is on. Why handle breaks? In real trading, zones aren't eternal; breaking them signals shift (e.g., demand to supply flip), so flagging prevents trading invalid setups while letting you visualize history if desired.

Now, drawing time—if the object exists (ObjectFind >=0) or it's not broken (or breaks aren't deleted): Pick "zoneColor" based on status (tested? Use tested colors; broken? Gray it out; fresh? Standard blue/red). Why color-code? Visual cues speed up analysis—blue for demand pops, violet for tested means "been there, traded that," gray screams "skip me!" Create rectangle with ObjectCreate (OBJ_RECTANGLE, times/prices for corners), set color/fill (true for solid)/back (true to layer behind prices). Why fill and back? Makes zones stand out without obscuring candles—practical for live monitoring. Add label: Name as zone + "Label," text as "Demand/Supply Zone" plus status, midpoint time/price for center placement. Create OBJ_TEXT, set text/color/anchor (ANCHOR_CENTER for neat middle align). Why labels? Charts without context are confusing—labels tell you at a glance "this is a tested demand," saving mental gymnastics.

Finally, "ChartRedraw" to refresh—MetaTrader sometimes needs a nudge to show changes instantly. Why redraw? Ensures visuals update in real-time, not lagging behind price action. To weave this into the action, pop it into "OnTick" after validation in the new bar if-block:

//+------------------------------------------------------------------+  
//|                                             Expert tick function |  
//+------------------------------------------------------------------+  
void OnTick() {  
//---  
   static datetime lastBarTime = 0;  
   datetime currentBarTime = iTime(_Symbol,_Period,0);  
   bool isNewBar = (currentBarTime != lastBarTime);  
   if (isNewBar){  
      lastBarTime = currentBarTime;  
      //Print("This is a new bar");  
      DetectZones();  
      ValidatePotentialZones();  
      UpdateZones();   }  
}

We did it again for you, and we get this.

Now your EA detects, validates, and updates/draws on every new bar—like a well-oiled machine painting the market's story. But wait, the real payoff? Trading those zones! Hang on, we're heading there next for some buy/sell fireworks. Who's ready to turn zones into pips? 😂

Alright, traders, we've got our zones detected, validated, and splashed across the chart like a masterpiece—now it's time to put our money where our mouth is (or rather, where the zones are)! Enter the "TradeOnZones" function, the action hero of our EA that scans for retest signals, confirms conditions, and fires off buys or sells with all the risk bells and whistles. Why bother with this? Well, spotting zones is cool, but without automated trading, you're just a chart-watcher, not a pip-collector. This function bridges the gap: it waits for price to "tap" a zone (like knocking on a door for entry), adds layers like trend filters to avoid bad vibes (because jumping into a counter-trend zone is like swimming upstream—exhausting and risky), and handles retests based on your mode (no/limited/unlimited) to match your strategy's aggression level. Deeper dive: In forex, zones shine on retests, but false taps happen—hence the overlap/candle close check for confirmation (ensures price respected the zone, not just grazed it). Trend confirmation? It's your safety net; without it, you might buy a demand zone in a raging downtrend, turning a setup into a stop-out nightmare. And updating zone status post-trade? Keeps the EA smart—tested zones change color/label for visuals, and trade counts prevent over-trading one spot (like not betting the farm on a single horse). Let's unpack this powerhouse step by step, so even if you're new to this, you'll see how it turns analysis into automated profits.

//+------------------------------------------------------------------+  
//|                                                   Trade on Zones |  
//+------------------------------------------------------------------+  
void TradeOnZones(bool isNewBar){  
   static datetime lastTradeCheck = 0;  
   datetime currentBarTime = iTime(_Symbol,_Period, 0);  
   if (!isNewBar || lastTradeCheck == currentBarTime) return;  
   lastTradeCheck = currentBarTime;  
   double currentBid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);;  
   double currentAsk = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);;  
    
   for (int i=0; i<ArraySize(zones); i++){  
      if (zones[i].broken) continue;  
      if (tradeTestedMode == NoRetrade && zones[i].tested) continue;  
      if (tradeTestedMode == LimitedRetrade && zones[i].tested && zones[i].tradeCount >= maxTradesPerZone) continue;  
      if (!zones[i].readyForTest) continue;  
       
       
      double prevHigh = iHigh(_Symbol,_Period, 1);  
      double prevLow = iLow(_Symbol,_Period, 1);  
      double prevClose = iClose(_Symbol,_Period, 1);  
      bool tapped = false;  
      bool overlap = (prevLow <= zones[i].high && prevHigh >= zones[i].low);  
      if (zones[i].isDemand){  
         if (overlap && prevClose > zones[i].high){  
            tapped = true;  
         }  
      }  
      else {  
         if (overlap && prevClose < zones[i].low){  
            tapped = true;  
         }  
      }  
       
      if (tapped){  
         bool trendConfirmed = (trendConfirmation == NoConfirmation);  
         if (trendConfirmation == ConfirmTrend){  
            int oldShift = 2+trendLookbackbars - 1;  
            if (oldShift >= iBars(_Symbol,_Period)) continue;  
            double oldClose = iClose(_Symbol,_Period,oldShift);  
            double recentClose = iClose(_Symbol,_Period,2);  
            double minChange = minTrendPoints * _Point;  
            if (zones[i].isDemand){  
               trendConfirmed = (oldClose >recentClose + minChange);  
            }  
            else {  
               trendConfirmed = (oldClose < recentClose - minChange);  
            }  
         }  
         if (!trendConfirmed) continue;  
         bool wasTested = zones[i].tested;  
          
         if (zones[i].isDemand){  
            double entryPrice = currentAsk;  
            double stopLossPrice = NormalizeDouble(zones[i].low - stoplossDistance * _Point,_Digits);  
            double takeProfitPrice = NormalizeDouble(entryPrice + takeprofitDistance * _Point,_Digits);  
            obj_Trade.Buy(tradeLotSize,_Symbol,entryPrice,stopLossPrice,takeProfitPrice,"Buy at Demand Zone");  
            Print("Buy trade entered at Demand Zone: ",zones[i].name);  
         }  
         else {  
            double entryPrice = currentBid;  
            double stopLossPrice = NormalizeDouble(zones[i].high + stoplossDistance * _Point,_Digits);  
            double takeProfitPrice = NormalizeDouble(entryPrice - takeprofitDistance * _Point,_Digits);  
            obj_Trade.Sell(tradeLotSize,_Symbol,entryPrice,stopLossPrice,takeProfitPrice,"Sell at Supply Zone");  
            Print("Sell trade entered at Supply Zone: ",zones[i].name);  
         }  
         zones[i].tested = true;  
         zones[i].tradeCount++;  
         if (!wasTested && zones[i].tested){  
            Print("Zone tested: ",zones[i].name,", Trade count: ",zones[i].tradeCount);  
         }  
         color zoneColor = zones[i].isDemand ? testedDemandZoneColor : testedSupplyZoneColor;  
         ObjectSetInteger(0, zones[i].name, OBJPROP_COLOR, zoneColor);  
         string labelName = zones[i].name + "Label";  
         string labelText = zones[i].isDemand ? "Demand Zone (Tested)" : "Supply Zone (Tested)";  
         ObjectSetString(0,labelName, OBJPROP_TEXT, labelText);  
      }  
   }  
   ChartRedraw();  
}

Let's roll through this like a smooth trade setup—why the static "lastTradeCheck" and bar time checks? To run only on new bars and once per bar (via "isNewBar" from OnTick and comparing to stored time), preventing spam trades on every tick (markets tick fast; you'd flood your broker otherwise!). Update "lastTradeCheck" to current time, grab normalized Bid/Ask with SymbolInfoDouble and "NormalizeDouble" (why normalize? Ensures prices match symbol's digits, like 5 for forex—prevents order rejections from bad formatting).

Loop forward through "zones[]" (0 to ArraySize—order doesn't matter here since no removals). Skip if broken (invalid), or if no-retest mode and tested, or limited-retest with max trades hit (why? Over-retesting risks whipsaws; limits control exposure per zone). Also skip if not "readyForTest" (from earlier distance check—ensures maturity).

Grab prev candle (index 1) high/low/close with "iHigh"/"iLow"/"iClose." Check "overlap" (prev candle touches zone bounds—price entered the area). For demand: if overlap and close > high, "tapped" true (bounced off support). Supply: overlap and close < low (rejected at resistance). Why this? Confirms a respectful retest—overlap shows interaction, close direction shows reversal strength (e.g., demand close above means buyers held).

If tapped, set "trendConfirmed" true if no confirmation needed. If required: Calculate "oldShift" as lookback (2 + "trendLookbackbars" -1—why 2? Skips current/prev for true history). Skip if out of bars. Grab old/recent closes. "minChange" as points (* _Point for scale). For demand: Confirm if old > recent + change (downtrend into zone—aligns with demand reversal). Supply: old < recent - change (uptrend fading). Why trend filter? Boosts win rate; trading against trend is like fighting gravity—possible but harder. Skip if not confirmed.

Store old tested flag. For demand: Entry at Ask, SL below low by "stoplossDistance" points (normalized—protects capital from breakdowns), TP above entry by "takeprofitDistance" (risk-reward setup). Use "obj_Trade.Buy" with lot/symbol/entry/SL/TP/comment. Log it. Supply: Entry at Bid, SL above high, TP below— "obj_Trade.Sell." Why comments? Helps journal filtering.

Flip tested true, bump tradeCount. If newly tested, log with count. Update color to tested shade with "ObjectSetInteger," refresh label text to "(Tested)" via "ObjectSetString." Why update visuals? Immediate feedback—see at a glance what's been traded, aiding manual oversight or strategy tweaks. End with "ChartRedraw"—forces instant chart refresh (MetaTrader can be lazy otherwise). To activate, call it in OnTick after updates if trading enabled, passing "isNewBar":

//+------------------------------------------------------------------+  
//|                                             Expert tick function |  
//+------------------------------------------------------------------+  
void OnTick() {  
//---  
   static datetime lastBarTime = 0;  
   datetime currentBarTime = iTime(_Symbol,_Period,0);  
   bool isNewBar = (currentBarTime != lastBarTime);  
   if (isNewBar){  
      lastBarTime = currentBarTime;  
      //Print("This is a new bar");  
      DetectZones();  
      ValidatePotentialZones();  
      UpdateZones();  
   }  
   if (enableTrading == true){  
      TradeOnZones(isNewBar);  
   }}
You now should get those zones being traded as below.

Now your EA's a full-fledged trader! But to lock in those wins, trailing stops are next—stay tuned for the profit-protector that trails like a loyal sidekick. Who's feeling the trading buzz? 😂 Make some trades and buy me coffee ☕ man, we have come a long way!

We're nearing the home stretch in our EA masterpiece, folks—think of it as adding the cherry on top of an already delicious forex sundae. With zones detected, visualized, and traded, we need a way to protect those hard-earned profits as the market dances in our favor, so it won't be hard for you to buy me that coffee, I ain't going to forget 🤡. Keep it aside, for now though. That's where trailing stops come in: they're like a smart bodyguard that follows price, locking in gains while giving trades room to breathe. Why include this? Fixed stops are great for initial risk, but markets trend, and without trailing, you might leave money on the table (e.g., a 100-pip winner closes at 50 if it pulls back).

Trailing dynamically adjusts the stop closer as price moves your way, turning potential losers into break-evens or winners—crucial for trend-following strategies like supply/demand, where impulses can run far. Deeper insight: It only activates after a minimum profit ("minProfitToTrail"), preventing premature tightening in choppy moves (why? Early trails can stop you out on noise, killing good trades). We loop through open positions backward (safe for modifications—forward can skip if closing shifts indices), filter by symbol/magic (isolates our EA's trades), and update only if the new stop is better and profit threshold met. Normalization ensures precision (matches digits, avoids broker errors). Let's break down this profit-preserver function clearly!

//+------------------------------------------------------------------+  
//|                                              Apply Trailing Stop |  
//+------------------------------------------------------------------+  
void ApplyTrailingStop(){  
   double point = _Point;  
   for (int i = PositionsTotal()-1; i>=0; i--){  
      if (PositionGetTicket(i) > 0){  
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == uniqueMagicNumber){  
            double sl = PositionGetDouble(POSITION_SL);  
            double tp = PositionGetDouble(POSITION_TP);  
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);  
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){  
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID) - trailingStopPoints * point,_Digits);  
               if (newSL > sl && SymbolInfoDouble(_Symbol,SYMBOL_BID) - openPrice > minProfitToTrail * point){  
                  obj_Trade.PositionModify(PositionGetInteger(POSITION_TICKET),newSL,tp);  
               }  
            }  
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){  
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK) + trailingStopPoints * point,_Digits);  
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol,SYMBOL_ASK) > minProfitToTrail * point){  
                  obj_Trade.PositionModify(PositionGetInteger(POSITION_TICKET),newSL,tp);  
               }  
            }  
         }  
      }  
   }  
}

Step-by-step, like trailing a sneaky market move: Grab "_Point" (symbol's point value—e.g., 0.00001 for 5-digit pairs; why? Calculations scale correctly across assets). Loop backward through all positions (PositionsTotal -1 to 0). Get ticket with PositionGetTicket (ensures valid), then filter: Symbol matches current ("PositionGetString"), magic matches ours ("PositionGetInteger"—isolates from other EAs/manual trades; why? Prevents messing with unrelated positions).

Fetch current SL/TP/open with "PositionGetDouble." For buys ("PositionGetInteger" == POSITION_TYPE_BUY): Calc "newSL" as Bid minus "trailingStopPoints" * point (normalized to digits—ensures valid price). If newSL > old SL (better protection) AND profit (Bid - open) > "minProfitToTrail" * point (threshold met), modify with "obj_Trade.PositionModify" (ticket, newSL, keep TP). Why Bid for buys? Trails based on current market exit price.

For sells: newSL as Ask plus points (normalized). If newSL < old SL (tighter) AND profit (open - Ask) > threshold, modify. This runs every tick if enabled, dynamically adjusting—efficient since loops are small. Why per-tick? Markets move fast; delayed trails miss locking gains.

To fire it up, slot it at the top of OnTick (before bar checks—trails work anytime, not just new bars):

//+------------------------------------------------------------------+  
//|                                             Expert tick function |  
//+------------------------------------------------------------------+  
void OnTick() {  
//---  
   if (enableTrailingStop){  
      ApplyTrailingStop();  
   }  
   static datetime lastBarTime = 0;  
   datetime currentBarTime = iTime(_Symbol,_Period,0);  
   bool isNewBar = (currentBarTime != lastBarTime);  
   if (isNewBar){  
      lastBarTime = currentBarTime;  
      //Print("This is a new bar");  
      DetectZones();  
      ValidatePotentialZones();  
      UpdateZones();  
   }  
   if (enableTrading == true){  
      TradeOnZones(isNewBar);  
   }  
     
     
     
}
Let's compile and see.

Boom—your EA's now a complete beast: detects, validates, visualizes, trades, and trails! But does it work? Time for backtesting tales and wrap-up wisdom. Stick around; the results might just blow your mind! 😂


Backtesting


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

Conclusion


We've powered through the finish line, crafting a powerhouse MQL5 Expert Advisor that automates supply and demand zone trading with smarts and style. From "DetectZones" sniffing out consolidations and "ValidatePotentialZones" confirming those impulsive breakouts, to "UpdateZones" painting vivid rectangles and labels on your chart, and "TradeOnZones" jumping in with buys/sells complete with SL/TP—our EA zeroes in on those institutional imbalance spots like a pro. Toss in "ApplyTrailingStop" for that dynamic profit-locking edge, and you've got a tool that slashes manual grunt work while enforcing solid risk rules for consistent plays. As we button this up, it's your launchpad for forex automation—tinker with params, layer on extras like news filters, or scale it up. If videos are your jam, hop over to our YouTube video tutorial for step-by-step walkthrough!

Disclaimer: This MQL5 Expert Advisor is purely educational and offers no profit promises; forex trading packs serious risk, and we're not on the hook for any losses. Always demo-test rigorously before going live with real dough.

This lays the solid groundwork for our supply and demand EA, ripe for your custom spins—toss in new bells, whistles, or tweaks to claim it as yours. Cheers to smart trading and fat pips! That coffee though! 😂

Attached Files


S/N Name  Type Description
 1 Supply and Demand Zone EA.ex5 Executable Contains the executable file format
 2 Supply and Demand Zone EA.mq5 Code File Contains all the EA human-readable source codes

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!