Multi-Level Grid Trading System in MQL5

Viewing the resource: Multi-Level Grid Trading System in MQL5

Important Note

Enjoy the content and feel free to discuss in the comments section below.

Multi-Level Grid Trading System in MQL5

Allan Munene Mutiiria 2025-06-22 01:26:06 167 Views
This article explores the MQL5 code for the Multi-Level Grid System EA, automating grid trades with ...

Introduction

Imagine orchestrating a trading operation with the precision of an engineer, where each trade is a calculated component within a robust framework. The Multi-Level Grid System EA is your advanced trading engine, designed to automate grid-based trades triggered by crossovers of a 21-period Simple Moving Average ("inpMAPeriod"=21). When prices cross the MA, the EA opens a 0.01-lot trade ("inpLotSize"=0.01), establishing a grid with 1000-pip spacing ("inpGridSize"=1000). If prices move against the trade, it adds positions with doubled lots ("inpMultiplier"=2.0) at each grid level, aiming to reach a breakeven point plus 50 pips ("inpBreakEvenPts"=50) or a $100 profit target ("profitTotal_currency"=100). Up to 5 grid baskets ("maxBaskets"=5) operate simultaneously, each with unique magic numbers ("inpMagicNo"=122346). The EA supports two closure modes ("ClosureMode": "CLOSE_BY_POINTS", "CLOSE_BY_PROFITS") for flexible trade management, making it ideal for traders navigating dynamic markets with a structured, high-risk approach.

This article is crafted with a professional, engaging, and seamless narrative, flowing like a well-engineered trading system, designed to inform and captivate readers. Tailored for both novice and experienced traders, we’ll dissect each code component with clear, precise explanations, as if guiding an apprentice engineer through a complex project. With vivid examples—like managing a grid on EURUSD—and a polished tone, we’ll explore how the EA initializes, triggers trades, manages grids, and ensures cleanup. Using a precision trading framework metaphor, this guide will illuminate the code’s technical rigor, empowering you to harness grid trading with confidence. Let’s power up the system and begin this technical expedition!

Strategy Blueprint

Let’s outline the EA’s trading framework, like drafting specifications for a robust system:

  • Signal Detection: Triggers 0.01-lot buys/sells ("inpLotSize") when prices cross above/below a 21-period SMA ("inpMAPeriod"), using "iClose()", "maData".

  • Grid Management: Establishes 1000-pip grid levels ("inpGridSize", "gridsize_spacing") below buys/above sells, adding doubled lots ("inpMultiplier"=2.0) at each level.

  • Closure Logic: Closes baskets at breakeven plus 50 pips ("inpBreakEvenPts", "CalculateBreakEvenPrice()") in "CLOSE_BY_POINTS" mode or $100 profit ("profitTotal_currency") in "CLOSE_BY_PROFITS" mode, using "CheckBreakevenClose()", "CheckAndCloseProfitTargets()".

  • Basket Control: Manages up to 5 baskets ("maxBaskets", "BasketInfo", "baskets[]") with unique magic numbers ("inpMagicNo", "magic").

  • Execution: Processes signals and grids on new bars ("IsNewBar()", "iBars()") with "ExecuteInitialTrade()", "ManageGridPositions()".

  • Enhancements: Adjustable lot sizes or grid spacing could improve flexibility. This framework automates grid trading with precision, balancing risk with structured closures.

Code Implementation

Let’s step into the trading workshop and dissect the MQL5 code that powers this Multi-Level Grid System EA. We’ll guide you through each phase like expert engineers, ensuring the narrative flows seamlessly with professional clarity and engaging precision that captivates readers. We’ll cover initialization, signal detection, grid management, trade closure, and cleanup, with detailed explanations and examples—like trading on EURUSD—to make it accessible for beginners. Each phase will build on the last, crafting a cohesive technical narrative that transforms code into a compelling trading project. Let’s activate the system and begin!

Phase 1: Constructing the Framework—Initialization

We start by building the trading system, initializing indicators and settings to prepare for grid operations.

//+------------------------------------------------------------------+
//|                                   MULTI-LEVEL GRID SYSTEM 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 ClosureMode{
   CLOSE_BY_PROFITS,
   CLOSE_BY_POINTS
};

input ClosureMode closureMode = CLOSE_BY_POINTS;
input double inpLotSize = 0.01;
input long inpMagicNo = 122346;
input int inpTp_points = 200;
input int inpGridSize = 1000;
input double inpMultiplier = 2.0;
input int inpBreakEvenPts = 50;
input int maxBaskets = 5;
input int inpMAPeriod = 21;

struct BasketInfo{
   int basketId;
   long magic;
   int direction;
   double initialLotSize;
   double currentLotSize;
   double gridSize;
   double takeProfit;
   datetime signalTime;
};

BasketInfo baskets[];

int nextBasketId = 1;
long baseMagic = inpMagicNo;
double takeprofitPts = inpTp_points * _Point;
double gridsize_spacing = inpGridSize * _Point;
double profitTotal_currency = 100;

int totalBars = 0;
int handle;
double maData[];

int OnInit(){
   handle = iMA(_Symbol,_Period,inpMAPeriod,0,MODE_SMA,PRICE_CLOSE);
   if (handle == INVALID_HANDLE){
      return (INIT_FAILED);
   }
   ArraySetAsSeries(maData,true);
   ArrayResize(baskets,0);
   obj_Trade.SetExpertMagicNumber(baseMagic);
   return(INIT_SUCCEEDED);
}

The system kicks off with the #property header, setting the stage with copyright and contact details, like laying the foundation for a trading framework. The "OnInit()" function initializes the setup, including "Trade/Trade.mqh" for trading via "obj_Trade", setting the base magic number ("baseMagic"=122346) with "SetExpertMagicNumber()", and creating a 21-period SMA handle ("handle", "iMA()", "inpMAPeriod"=21). Inputs define parameters: "closureMode"=CLOSE_BY_POINTS, "inpLotSize"=0.01, "inpMagicNo"=122346, "inpTp_points"=200, "inpGridSize"=1000, "inpMultiplier"=2.0, "inpBreakEvenPts"=50, "maxBaskets"=5, "inpMAPeriod"=21, like customizable framework settings. The BasketInfo struct and "baskets[]" array track grid details ("basketId", "magic", "direction", etc.), with variables ("nextBasketId", "takeprofitPts", "gridsize_spacing", "profitTotal_currency"=100, "totalBars", "maData[]") setting operational boundaries. Returning INIT_SUCCEEDED signals, “Framework is ready, let’s trade!” This setup primes the EA for grid trading, like a trading system poised for action.

Phase 2: Mapping Market Signals—Detecting MA Crossovers

With the framework in place, we scan for moving average crossovers to trigger new grid baskets, like plotting trade signals on a market blueprint.

void OnTick(){
   if (IsNewBar()){
      UpdateMovingAverage();
      double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      CheckForNewSignal(ask,bid);
      // ... (grid management and closure logic)
   }
}

void UpdateMovingAverage(){
   if (CopyBuffer(handle,0,1,3,maData) < 3){
      Print("ERROR RETRIEVEING THE MA DATA. REVERTING");
   }
}

void CheckForNewSignal(double ask, double bid){
   double close1 = iClose(_Symbol,_Period,1);
   double close2 = iClose(_Symbol,_Period,2);
   datetime currentBarTime = iTime(_Symbol,_Period,1);
   if (ArraySize(baskets) >= maxBaskets) return;
   if (close1 > maData[1] && close2 < maData[1]){
      for (int i=0; i<ArraySize(baskets); i++){
         if (baskets[i].signalTime == currentBarTime) return;
      }
      int basketIdx = ArraySize(baskets);
      ArrayResize(baskets,basketIdx+1);
      if (ExecuteInitialTrade(basketIdx,ask,bid,POSITION_TYPE_BUY)){
         baskets[basketIdx].signalTime = currentBarTime;
      }
   }
   else if (close1 < maData[1] && close2 > maData[1]){
      for (int i=0; i<ArraySize(baskets); i++){
         if (baskets[i].signalTime == currentBarTime) return;
      }
      int basketIdx = ArraySize(baskets);
      ArrayResize(baskets,basketIdx+1);
      if (ExecuteInitialTrade(basketIdx,ask,bid,POSITION_TYPE_SELL)){
         baskets[basketIdx].signalTime = currentBarTime;
      }
   }
}

bool ExecuteInitialTrade(int basketIdx, double ask, double bid, int direction){
   baskets[basketIdx].basketId = nextBasketId++;
   baskets[basketIdx].magic = baseMagic+baskets[basketIdx].basketId*10000;
   baskets[basketIdx].initialLotSize = inpLotSize;
   baskets[basketIdx].currentLotSize = inpLotSize;
   baskets[basketIdx].direction = direction;
   bool isTradeExecuted = false;
   string comment = GetPositionComment(baskets[basketIdx].basketId,true);
   obj_Trade.SetExpertMagicNumber(baskets[basketIdx].magic);
   if (direction == POSITION_TYPE_BUY){
      baskets[basketIdx].gridSize = ask - gridsize_spacing;
      baskets[basketIdx].takeProfit = ask + takeprofitPts;
      if (obj_Trade.Buy(baskets[basketIdx].currentLotSize,_Symbol,ask,0,baskets[basketIdx].takeProfit,comment)){
         Print("Basket ",baskets[basketIdx].basketId,": Initial BUY at ",ask," | Magic: ",baskets[basketIdx].magic);
         isTradeExecuted = true;
      }
      else {
         Print("Basket ",baskets[basketIdx].basketId,": Initial BUY failed, error: ",GetLastError());
         ArrayResize(baskets,ArraySize(baskets)-1);
      }
   }
   else if (direction == POSITION_TYPE_SELL){
      baskets[basketIdx].gridSize = bid + gridsize_spacing;
      baskets[basketIdx].takeProfit = bid - takeprofitPts;
      if (obj_Trade.Sell(baskets[basketIdx].currentLotSize,_Symbol,bid,0,baskets[basketIdx].takeProfit,comment)){
         Print("Basket ",baskets[basketIdx].basketId,": Initial SELL at ",bid," | Magic: ",baskets[basketIdx].magic);
         isTradeExecuted = true;
      }
      else {
         Print("Basket ",baskets[basketIdx].basketId,": Initial SELL failed, error: ",GetLastError());
         ArrayResize(baskets,ArraySize(baskets)-1);
      }
   }
   return(isTradeExecuted);
}

In the signal mapping hub, "OnTick()" runs on new bars ("IsNewBar()", "iBars()", "totalBars"), updating the MA with "UpdateMovingAverage()", "CopyBuffer()", "maData". The "CheckForNewSignal()" function checks if the close price crosses the MA ("iClose()", "maData[1]") and "ArraySize(baskets) < maxBaskets"=5. For a bullish crossover ("close1 > maData[1] && close2 < maData[1]"), it adds a basket ("ArrayResize()", "baskets") and calls "ExecuteInitialTrade()", which sets "basketId", "magic", "initialLotSize"=0.01, "currentLotSize", "direction", "gridSize" (ask minus "gridsize_spacing" for buys), and "takeProfit" (ask plus "takeprofitPts"=200). It opens a trade with "obj_Trade.Buy()" or "obj_Trade.Sell()" using "GetPositionComment()", logging with "Print()". If the trade fails ("GetLastError()"), it removes the basket. Bearish crossovers mirror this. For example, on EURUSD H1, a close at 1.2005 above the MA triggers a buy at 1.2007, setting "gridSize"=1.1007, "takeProfit"=1.2207, like plotting a trade signal.

Phase 3: Expanding the Grid—Managing Additional Positions

With a basket initiated, we manage grid positions, scaling lots and updating take profits, like reinforcing a trading framework.

void OnTick(){
   // ... (signal detection)
   for (int i=0; i<ArraySize(baskets); i++){
      ManageGridPositions(i,ask,bid);
      // ... (closure logic)
   }
}

void ManageGridPositions(int basketIdx, double ask, double bid){
   bool newPositionOpened = false;
   string comment = GetPositionComment(baskets[basketIdx].basketId,false);
   obj_Trade.SetExpertMagicNumber(baskets[basketIdx].magic);
   if (baskets[basketIdx].direction == POSITION_TYPE_BUY){
      if (ask <= baskets[basketIdx].gridSize){
         baskets[basketIdx].currentLotSize *= inpMultiplier;
         if (obj_Trade.Buy(baskets[basketIdx].currentLotSize,_Symbol,ask,0,baskets[basketIdx].takeProfit,comment)){
            newPositionOpened = true;
            baskets[basketIdx].gridSize = ask-gridsize_spacing;
         }
      }
   }
   else if (baskets[basketIdx].direction == POSITION_TYPE_SELL){
      if (bid >= baskets[basketIdx].gridSize){
         baskets[basketIdx].currentLotSize *= inpMultiplier;
         if (obj_Trade.Sell(baskets[basketIdx].currentLotSize,_Symbol,bid,0,baskets[basketIdx].takeProfit,comment)){
            newPositionOpened = true;
            baskets[basketIdx].gridSize = bid+gridsize_spacing;
         }
      }
   }
   if (newPositionOpened && CountBasketPositions(baskets[basketIdx].basketId) > 1){
      double breakEvenPrice = CalculateBreakEvenPrice(baskets[basketIdx].basketId);
      double newTp = (baskets[basketIdx].direction == POSITION_TYPE_BUY) ?
                      breakEvenPrice + (inpBreakEvenPts*_Point) :
                      breakEvenPrice - (inpBreakEvenPts*_Point);
      baskets[basketIdx].takeProfit = newTp;
      for (int j=PositionsTotal()-1; j>=0; j--){
         ulong ticket = PositionGetTicket(j);
         if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL)==_Symbol && PositionGetInteger(POSITION_MAGIC)==baskets[basketIdx].magic){
            Print(baskets[basketIdx].basketId," ticket = ",ticket,", lots",PositionGetDouble(POSITION_VOLUME));
            if (!obj_Trade.PositionModify(ticket,0,newTp)){
               Print("Basket ",baskets[basketIdx].basketId,": Failed to modify TP for Ticket ",ticket);
            }
         }
      }
      Print("Basket ",baskets[basketIdx].basketId,": BreakEven Price = ",breakEvenPrice,", New TP = ",newTp);
   }
}

double CalculateBreakEvenPrice(int basketId){
   double weightedSum = 0.0;
   double totalLots = 0.0;
   for (int i=0; i<PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL)==_Symbol && StringFind(PositionGetString(POSITION_COMMENT),"Basket_"+IntegerToString(basketId)) >= 0){
         double lot = PositionGetDouble(POSITION_VOLUME);
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         weightedSum += openPrice * lot;
         totalLots += lot;
      }
   }
   return(totalLots > 0) ? (weightedSum/totalLots) : 0;
}

int CountBasketPositions(int basketId){
   int count = 0;
   for (int i=0; i<PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket) && StringFind(PositionGetString(POSITION_COMMENT),"Basket_"+IntegerToString(basketId)) >= 0){
         count++;
      }
   }
   return count;
}

In the grid expansion hub, "OnTick()" loops through "baskets" to call "ManageGridPositions()". For buys, if "ask" falls below "baskets[basketIdx].gridSize", it doubles "currentLotSize" ("inpMultiplier"=2.0), opens a new buy with "obj_Trade.Buy()", updates "gridSize" (down by "gridsize_spacing"=1000), and sets "newPositionOpened"=true. Sells mirror this, checking "bid" above "gridSize". If new positions open and "CountBasketPositions()" > 1, it calculates the breakeven price ("CalculateBreakEvenPrice()", weighted average via "PositionGetDouble()", "StringFind()") and sets a new take profit ("newTp") at breakeven plus/minus "inpBreakEvenPts"=50 pips, modifying all positions with "obj_Trade.PositionModify()", logging with "Print()". For example, a EURUSD buy at 1.2007 adds a 0.02-lot buy at 1.1007, setting "takeProfit"=1.1057 (breakeven 1.1007 + 50 pips), like reinforcing a trading structure.

Phase 4: Securing Profits—Closing Baskets

With grids active, we close baskets at breakeven or profit targets, like finalizing a trading framework’s output.

void OnTick(){
   // ... (grid management)
   for (int i=0; i<ArraySize(baskets); i++){
      if (closureMode == CLOSE_BY_PROFITS) CheckAndCloseProfitTargets();
      if (closureMode == CLOSE_BY_POINTS && CountBasketPositions(baskets[i].basketId) > 1){
         CheckBreakevenClose(i,ask,bid);
      }
   }
   for (int i=ArraySize(baskets)-1; i>=0; i--){
      if (CountBasketPositions(baskets[i].basketId)==0){
         Print("Removing inactive basket ID ",baskets[i].basketId);
         for (int j=i; j<ArraySize(baskets)-1; j++){
            baskets[j] = baskets[j+1];
         }
         ArrayResize(baskets,ArraySize(baskets)-1);
      }
   }
}

void CheckBreakevenClose(int basketIdx, double ask, double bid){
   double breakEvenPrice = CalculateBreakEvenPrice(baskets[basketIdx].basketId);
   if (baskets[basketIdx].direction == POSITION_TYPE_BUY){
      if (bid >= breakEvenPrice+(inpBreakEvenPts*_Point)){
         CloseBasketPositions(baskets[basketIdx].basketId);
      }
   }
   else if (baskets[basketIdx].direction == POSITION_TYPE_SELL){
      if (ask <= breakEvenPrice-(inpBreakEvenPts*_Point)){
         CloseBasketPositions(baskets[basketIdx].basketId);
      }
   }
}

void CheckAndCloseProfitTargets(){
   for (int i=0; i<ArraySize(baskets); i++){
      int posCount = CountBasketPositions(baskets[i].basketId);
      if (posCount <= 1) continue;
      double totalProfit = 0;
      for (int j=PositionsTotal()-1; j>=0; j--){
         ulong ticket = PositionGetTicket(j);
         if (PositionSelectByTicket(ticket) && StringFind(PositionGetString(POSITION_COMMENT),"Basket_"+IntegerToString(baskets[i].basketId)) >= 0){
            totalProfit += PositionGetDouble(POSITION_PROFIT);
         }
      }
      if (totalProfit >= profitTotal_currency){
         CloseBasketPositions(baskets[i].basketId);
      }
   }
}

void CloseBasketPositions(int basketId){
   for (int i=PositionsTotal()-1; i>=0; i--){
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket) && StringFind(PositionGetString(POSITION_COMMENT),"Basket_"+IntegerToString(basketId)) >= 0){
         if (obj_Trade.PositionClose(ticket)){
            Print("Basket ",basketId,": Closed position ticket ",ticket);
         }
      }
   }
}

In the profit securing hub, "OnTick()" checks closure conditions. In "CLOSE_BY_POINTS" mode, "CheckBreakevenClose()" closes buys if "bid" reaches breakeven ("CalculateBreakEvenPrice()") plus "inpBreakEvenPts"=50 pips, or sells if "ask" falls below breakeven minus 50 pips, using "CloseBasketPositions()", "PositionGetTicket()", "obj_Trade.PositionClose()". In "CLOSE_BY_PROFITS" mode, "CheckAndCloseProfitTargets()" sums profits ("PositionGetDouble(POSITION_PROFIT)") across basket positions, closing if "totalProfit >= profitTotal_currency"=100. Inactive baskets ("CountBasketPositions()=0") are removed with "ArrayResize()". For example, a EURUSD buy basket with breakeven at 1.1007 closes at 1.1057, or a $100 profit triggers closure, like locking in framework gains. The EA also removes empty baskets, maintaining efficiency.

Phase 5: Shutting Down the Framework—Cleaning Up Resources

As our expedition concludes, we shut down the system, ensuring resources are cleared for the next project.

void OnDeinit(const int reason){
   IndicatorRelease(handle);
}

In the shutdown workshop, "OnDeinit()" releases the MA handle with "IndicatorRelease()", like dismantling a framework’s core component. Adding cleanup for "baskets[]" and open positions would enhance completeness:

ArrayFree(baskets);
for (int i=PositionsTotal()-1; i>=0; i--){
   ulong ticket = PositionGetTicket(i);
   if (PositionSelectByTicket(ticket) && PositionGetInteger(POSITION_MAGIC) >= baseMagic){
      obj_Trade.PositionClose(ticket);
   }
}

This ensures a clean slate, like archiving a trading framework, ready for the next task.

Why This EA is a Trading Triumph

The Multi-Level Grid System EA is a high-risk trading triumph, automating grid trades with precision, like a robust trading framework. Its MA crossovers ("iMA()", "maData") and grid scaling ("ManageGridPositions()") offer strategic depth, with closure modes ("closureMode") ensuring flexibility. Potential enhancements like adjustable grids or stop losses could refine it further. Picture a EURUSD buy grid closing at 1.1057 for breakeven plus 50 pips—strategic brilliance! Beginners will value the clear logic, while experts can enhance its robust structure, making it essential for grid traders.

Putting It All Together

To deploy this EA:

  1. Open MetaEditor in MetaTrader 5, like entering your trading workshop.

  2. Copy the code, compile with F5, and verify no errors—no engineer wants a faulty framework!

  3. Attach the EA to your chart, enable AutoTrading, and watch it open and manage grid baskets.

  4. Monitor logs (e.g., “Basket 1: Initial BUY at 1.2007”) for trade tracking, like system diagnostics.

  5. Test on a demo account first—real capital deserves a trial run!

Conclusion

We’ve engineered a Multi-Level Grid Trader that automates grid trading with precision, like a master-crafted trading framework. This MQL5 code is your strategic tool, brought to life with a seamless, professional narrative packed with clear explanations and vivid examples to fuel your trading ambition. Whether you’re a novice engineer or a seasoned market strategist, this EA empowers you to conquer grid trading with confidence. Ready to trade? Watch our video guide on the website for a step-by-step creation process. Now, build your trading legacy with precision! 🔧

Disclaimer: Trading is like engineering complex systems—challenging and risky. Losses can exceed deposits. Test strategies on a demo account before going live.

Recent Comments

Go to discussion to Comment or View other Comments

No comments yet. Be the first to comment!