preview
Automating Trading Strategies in MQL5 (Part 7): Building a Grid Trading EA with Dynamic Lot Scaling

Automating Trading Strategies in MQL5 (Part 7): Building a Grid Trading EA with Dynamic Lot Scaling

MetaTrader 5Trading | 18 February 2025, 08:41
6 595 9
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In the previous article (Part 6), we developed an automated Order Block Detection System in MetaQuotes Language 5 (MQL5). Now, in Part 7, we focus on grid trading, a strategy that places trades at fixed price intervals, combined with dynamic lot scaling to optimize risk and reward. This approach adapts position sizing based on market conditions, aiming to enhance profitability and risk management. We will cover:

  1. Strategy Blueprint
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you'll have a fully functional Grid Trading program with dynamic lot scaling, ready for testing and optimization. Let’s begin!


Strategy Blueprint

Grid trading is a systematic approach that places buy and sell orders at predetermined price intervals, allowing traders to capitalize on market fluctuations without requiring precise trend predictions. This strategy benefits from market volatility by continuously opening and closing trades within a defined price range. To enhance its performance, we will integrate dynamic lot scaling, which will adjust position sizes based on predefined conditions, such as account balance, volatility, or previous trade outcomes. Our Grid Trading system will operate with the following key components:

  • Grid Structure – We will define the spacing between orders.
  • Entry and Execution Rules – We will determine when to open grid trades based on fixed distances using a Moving Average indicator strategy.
  • Dynamic Lot Scaling – We will implement an adaptive lot-sizing mechanism that adjusts position sizes based on market conditions or predefined risk parameters.
  • Trade Management – We will incorporate stop-loss, take-profit, and optional breakeven mechanisms to manage risk effectively.
  • Exit Strategy – We will develop logic to close positions based on profit targets, risk limits, or trend reversals.

In a nutshell, here is the whole strategy blueprint visualization for ease of understanding.

GRID LAYOUT

By combining a structured grid system with adaptive lot sizing, we will create an EA that maximizes returns while effectively managing risk. Next, we will implement these concepts in MQL5.


Implementation in MQL5

To create the program in MQL5, open the MetaEditor, go to the Navigator, locate the Indicators folder, click on the "New" tab, and follow the prompts to create the file. Once it is made, in the coding environment, we will need to declare some global variables that we will use throughout the program.

//+------------------------------------------------------------------+
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA trades based on Grid Strategy"
#property strict

#include <Trade/Trade.mqh>                     //--- Include trading library
CTrade obj_Trade;                            //--- Trading object instance

//--- Closure Mode Enumeration and Inputs
enum ClosureMode {
   CLOSE_BY_PROFIT,      //--- Use total profit (in currency) to close positions
   CLOSE_BY_POINTS       //--- Use a points threshold from breakeven to close positions
};

input group "General EA Inputs"
input ClosureMode closureMode = CLOSE_BY_POINTS;    //Select closure mode

double breakevenPoints = 50 * _Point;                 //--- Points offset to add/subtract to/from breakeven

//--- Global Variables
double TakeProfit;                           //--- Current take profit level
double initialLotsize      = 0.1;            //--- Initial lot size for the first trade
double takeProfitPts       = 200 * _Point;    //--- Take profit distance in points
double profitTotal_inCurrency = 100;          //--- Profit target (in currency) to close positions

double gridSize;                             //--- Price level at which grid orders are triggered
double gridSize_Spacing   = 500 * _Point;      //--- Grid spacing in points
double LotSize;                              //--- Current lot size (increased with grid orders)

bool isTradeAllowed      = true;              //--- Flag to allow trade on a new bar
int totalBars            = 0;                 //--- Count of bars seen so far
int handle;                                  //--- Handle for the Moving Average indicator
double maData[];                             //--- Array for Moving Average data

Here, we include the "Trade/Trade.mqh" library using #include and instantiate the object "obj_Trade" to handle our trades. We define a "ClosureMode" enumeration with options for closing positions and set up user inputs like "closureMode" and "breakevenPoints". Next, we declare variables to manage our take profit levels, initial lot size, grid spacing, and dynamic lot sizing, along with flags and counters for trade control and moving average indicator data. We then need to declare the prototypes for our key functions that will structure the program as follows.

//--- Function Prototypes
void   CheckAndCloseProfitTargets();         //--- Closes all positions if total profit meets target
void   ExecuteInitialTrade(double ask, double bid); //--- Executes the initial BUY/SELL trade (initial positions)
void   ManageGridPositions(double ask, double bid); //--- Adds grid orders when market moves to grid level (grid positions)
void   UpdateMovingAverage();                //--- Updates MA indicator data from its buffer
bool   IsNewBar();                           //--- Checks if a new bar has formed
double CalculateWeightedBreakevenPrice();    //--- Calculates the weighted average entry price for positions
void   CheckBreakevenClose(double ask, double bid); //--- Closes positions if price meets breakeven+/- threshold
void   CloseAllPositions();                  //--- Closes all open positions

For the functions, we will implement "CheckAndCloseProfitTargets" to monitor overall profitability and close positions once our target is reached, and "ExecuteInitialTrade" to kick off the strategy with the initial BUY or SELL order. "ManageGridPositions" will add additional orders at set grid intervals as the market moves, while "UpdateMovingAverage" ensures our indicator data is current for decision-making. "IsNewBar" detects new bars to prevent multiple trades on the same candle, "CalculateWeightedBreakevenPrice" computes the average entry price across positions, and "CheckBreakevenClose" uses that information to exit trades when favorable conditions are met. Lastly, "CloseAllPositions" will methodically close all open trades when necessary.

After setting all these on the "global scope", we are set to continue with the program initialization, which is on the "OnInit" event handler.

//+------------------------------------------------------------------+
//--- Expert initialization function
//+------------------------------------------------------------------+
int OnInit(){
   //--- Initialize the Moving Average indicator (Period: 21, SMA, Price: Close)
   handle = iMA(_Symbol, _Period, 21, 0, MODE_SMA, PRICE_CLOSE);
   if (handle == INVALID_HANDLE){
      Print("ERROR: UNABLE TO INITIALIZE THE INDICATOR. REVERTING NOW!");
      return (INIT_FAILED);
   }
   ArraySetAsSeries(maData, true);            //--- Ensure MA data array is in series order
   return(INIT_SUCCEEDED);
}

Here, we initialize the program by setting up our Moving Average indicator using the iMA function with a period of 21, SMA type, and PRICE_CLOSE to capture closing prices. We check if the indicator handle is valid—if it isn't (INVALID_HANDLE), we print an error message and return INIT_FAILED to stop the program from running. Finally, we call the ArraySetAsSeries function on the "maData" array to ensure the Moving Average data is arranged in the correct order before returning INIT_SUCCEEDED to confirm successful initialization. Once initialized correctly, we can proceed to the OnTick event handler to build the logic for opening and managing the positions.

//+------------------------------------------------------------------+
//--- Expert tick function
//+------------------------------------------------------------------+
void OnTick(){
   
   //--- Allow new trade signals on a new bar
   if(IsNewBar())
      isTradeAllowed = true;
   
   //--- Update the Moving Average data
   UpdateMovingAverage();
   
}

Since we don't want to check for trades on every tick, but on every bar, we call the function "IsNewBar" and use it to set the "isTradeAllowed" variable to true if a new bar is formed. We then call the function responsible for getting the moving average values. The function's definitions are as follows.

//+-------------------------------------------------------------------+
//--- Function: UpdateMovingAverage
//--- Description: Copies the latest data from the MA indicator buffer.
//+-------------------------------------------------------------------+
void UpdateMovingAverage(){
   if(CopyBuffer(handle, 0, 1, 3, maData) < 0)
      Print("Error: Unable to update Moving Average data.");
}
  
//+-------------------------------------------------------------------+
//--- Function: IsNewBar
//--- Description: Checks if a new bar has been formed.
//+-------------------------------------------------------------------+
bool IsNewBar(){
   int bars = iBars(_Symbol, _Period);
   if(bars > totalBars){
      totalBars = bars;
      return true;
   }
   return false;
}

Here, we implement "UpdateMovingAverage" to refresh our indicator data by copying the latest values from the Moving Average buffer using the CopyBuffer function. If this function call fails, we print an error message to alert us that the update was unsuccessful. In the "IsNewBar" function, we check whether a new bar has formed by comparing the current number of bars, obtained via the iBars function, to our stored "totalBars" count; if the number has increased, we update "totalBars" and return "true", indicating that a new bar is available for trading decisions. We then continue with the tick function to execute trades based on retrieved indicator values.

//--- Reset lot size if no positions are open
if(PositionsTotal() == 0)
   LotSize = initialLotsize;

//--- Retrieve recent bar prices for trade signal logic
double low1  = iLow(_Symbol, _Period, 1);
double low2  = iLow(_Symbol, _Period, 2);
double high1 = iHigh(_Symbol, _Period, 1);
double high2 = iHigh(_Symbol, _Period, 2);

//--- Get current Ask and Bid prices (normalized)
double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);

//--- If no positions are open and trading is allowed, check for an initial trade signal
if(PositionsTotal() == 0 && isTradeAllowed){
   ExecuteInitialTrade(ask, bid);
}

Here, we first check if no positions are open using the PositionsTotal function, and if so, we reset the "LotSize" to "initialLotsize". Next, we retrieve recent bar prices by calling iLow and iHigh to capture the highs and lows of the previous two bars, which will help form our trade signals. We then obtain the current "ask" and "bid" prices using SymbolInfoDouble, normalizing them with NormalizeDouble to ensure accuracy. Finally, if trading is allowed (as indicated by "isTradeAllowed") and no positions are currently open, we call the "ExecuteInitialTrade" function with the "ask" and "bid" prices to initiate our first trade. The function definition is as below.

//+---------------------------------------------------------------------------+
//--- Function: ExecuteInitialTrade
//--- Description: Executes the initial BUY or SELL trade based on MA criteria.
//---              (These are considered "initial positions.")
//+---------------------------------------------------------------------------+
void ExecuteInitialTrade(double ask, double bid){
   //--- BUY Signal: previous bar's low above MA and bar before that below MA
   if(iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){
      gridSize = ask - gridSize_Spacing;     //--- Set grid trigger below current ask
      TakeProfit = ask + takeProfitPts;      //--- Set TP for BUY
      if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Initial Buy"))
         Print("Initial BUY order executed at ", ask, " with LotSize: ", LotSize);
      else
         Print("Initial BUY order failed at ", ask);
      isTradeAllowed = false;
   }
   //--- SELL Signal: previous bar's high below MA and bar before that above MA
   else if(iHigh(_Symbol, _Period, 1) < maData[1] && iHigh(_Symbol, _Period, 2) > maData[1]){
      gridSize = bid + gridSize_Spacing;     //--- Set grid trigger above current bid
      TakeProfit = bid - takeProfitPts;      //--- Set TP for SELL
      if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Initial Sell"))
         Print("Initial SELL order executed at ", bid, " with LotSize: ", LotSize);
      else
         Print("Initial SELL order failed at ", bid);
      isTradeAllowed = false;
   }
}

Here, we implement the "ExecuteInitialTrade" function to open an initial trade based on the "maData" values. We retrieve the low prices of the previous two bars using the function iLow and the high prices using the function iHigh. For a BUY signal, we check if the low of the previous bar is above "maData" while the bar before that was below it. If this condition is met, we set "gridSize" below the current "ask" using "gridSize_Spacing" to determine the next grid level, calculate "TakeProfit" by adding "takeProfitPts" to the "ask", and execute a BUY trade using "obj_Trade.Buy" method.

For a SELL signal, we check if the high of the previous bar is below "maData" while the bar before that was above it. If true, we set "gridSize" above the "bid", determine "TakeProfit" by subtracting "takeProfitPts" from the "bid", and attempt to execute a SELL trade using "obj_Trade.Sell". Once a trade is executed, we set "isTradeAllowed" to false to prevent additional entries until further conditions are met. Here is the outcome.

TRADE EXECUTION

From the image, we can see that we have the confirmed trades being executed. We now need to move on to manage the trades by opening the grid positions.

//--- If positions exist, manage grid orders
if(PositionsTotal() > 0){
   ManageGridPositions(ask, bid);
}

We check if there are open positions using the function PositionsTotal. If the number of positions is greater than zero, we call the function "ManageGridPositions" to handle additional grid trades. The function takes "ask" and "bid" as parameters to determine the appropriate price levels for placing new grid orders based on market movement. The function's code snippet implementation is as below.

//+------------------------------------------------------------------------+
//--- Function: ManageGridPositions
//--- Description: When an initial position exists, grid orders are added 
//---              if the market moves to the grid level. (These orders are 
//---              considered "grid positions.") The lot size is doubled 
//---              with each grid order.
//+------------------------------------------------------------------------+
void ManageGridPositions(double ask, double bid){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         int positionType = (int)PositionGetInteger(POSITION_TYPE);
         //--- Grid management for BUY positions
         if(positionType == POSITION_TYPE_BUY){
            if(ask <= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Grid Position BUY"))
                  Print("Grid BUY order executed at ", ask, " with LotSize: ", LotSize);
               else
                  Print("Grid BUY order failed at ", ask);
               gridSize = ask - gridSize_Spacing; //--- Update grid trigger
            }
         }
         //--- Grid management for SELL positions
         else if(positionType == POSITION_TYPE_SELL){
            if(bid >= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Grid Position SELL"))
                  Print("Grid SELL order executed at ", bid, " with LotSize: ", LotSize);
               else
                  Print("Grid SELL order failed at ", bid);
               gridSize = bid + gridSize_Spacing; //--- Update grid trigger
            }
         }
      }
   }
}

We implement the "ManageGridPositions" function to manage grid orders. We iterate through all open positions in reverse using a for loop and retrieve each position’s ticket with the function PositionGetTicket. We then select the position using PositionSelectByTicket and determine whether it is a BUY or SELL trade using PositionGetInteger with the parameter POSITION_TYPE. If the position is a BUY, we check if the market price "ask" has reached or dropped below "gridSize". If true, we double "LotSize" and execute a new grid BUY order using the function "obj_Trade.Buy". If the order is successful, we print a confirmation message; otherwise, we print an error message. We then update "gridSize" to the next grid level below.

Similarly, if the position is a SELL, we check if "bid" has reached or exceeded "gridSize". If true, we double "LotSize" and place a new grid SELL order using "obj_Trade.Sell". The grid trigger "gridSize" is then updated to the next level above. After opening the grid positions, we need to track and manage the positions by closing them once we hit the defined as below.

//--- Check if total profit meets the target (only used if closureMode == CLOSE_BY_PROFIT)
if(closureMode == CLOSE_BY_PROFIT)
   CheckAndCloseProfitTargets();

If "closureMode" is set to "CLOSE_BY_PROFIT", we call the function "CheckAndCloseProfitTargets" to check if the total profit has reached the predefined target and close all positions accordingly. The function declaration is as below.

//+----------------------------------------------------------------------------+
//--- Function: CheckAndCloseProfitTargets
//--- Description: Closes all positions if the combined profit meets or exceeds
//---              the user-defined profit target.
//+----------------------------------------------------------------------------+
void CheckAndCloseProfitTargets(){
   if(PositionsTotal() > 1){
      double totalProfit = 0;
      for(int i = PositionsTotal()-1; i >= 0; i--){
         ulong tkt = PositionGetTicket(i);
         if(PositionSelectByTicket(tkt))
            totalProfit += PositionGetDouble(POSITION_PROFIT);
      }
      if(totalProfit >= profitTotal_inCurrency){
         Print("Profit target reached (", totalProfit, "). Closing all positions.");
         CloseAllPositions();
      }
   }
}

To ensure that all positions are closed if the total accumulated profit meets or exceeds the predefined profit target, we first, check if there is more than one open position using PositionsTotal. We initialize "totalProfit" to track the combined profit of all positions. We then loop through all open positions, retrieving each position’s ticket using PositionGetTicket and selecting it with PositionSelectByTicket. For each selected position, we retrieve its profit using PositionGetDouble with the parameter POSITION_PROFIT and add it to "totalProfit". If "totalProfit" meets or exceeds "profitTotal_inCurrency", we print a message indicating that the profit target has been reached and call the "CloseAllPositions" function, whose definition is as below, to close all open trades.

//+------------------------------------------------------------------+
//--- Function: CloseAllPositions
//--- Description: Iterates through and closes all open positions.
//+------------------------------------------------------------------+
void CloseAllPositions(){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong posTkt = PositionGetTicket(i);
      if(PositionSelectByTicket(posTkt)){
         if(obj_Trade.PositionClose(posTkt))
            Print("Closed position ticket: ", posTkt);
         else
            Print("Failed to close position ticket: ", posTkt);
      }
   }
}

The function just iterates over all open positions and for every selected position, is closed using the "obj_Trade.PositionClose" method. Finally, we define the logic to close the positions on breakeven.

//--- If using CLOSE_BY_POINTS and more than one position exists (i.e. grid), check breakeven closure
if(closureMode == CLOSE_BY_POINTS && PositionsTotal() > 1)
   CheckBreakevenClose(ask, bid);

If "closureMode" is set to "CLOSE_BY_POINTS" and there is more than one open position, we call the function "CheckBreakevenClose" with parameters "ask" and "bid" to determine whether the price has reached the breakeven threshold, allowing positions to be closed based on predefined points from breakeven. The following is the function definition.

//+----------------------------------------------------------------------------+
//--- Function: CalculateWeightedBreakevenPrice
//--- Description: Calculates the weighted average entry price (breakeven)
//---              of all open positions (assumed to be in the same direction).
//+----------------------------------------------------------------------------+
double CalculateWeightedBreakevenPrice(){
   double totalCost = 0;
   double totalVolume = 0;
   int posType = -1;
   //--- Determine the type from the first position
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   //--- Sum the cost and volume for positions matching the type
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         if(PositionGetInteger(POSITION_TYPE) == posType){
            double price = PositionGetDouble(POSITION_PRICE_OPEN);
            double volume = PositionGetDouble(POSITION_VOLUME);
            totalCost += price * volume;
            totalVolume += volume;
         }
      }
   }
   if(totalVolume > 0)
      return(totalCost / totalVolume);
   else
      return(0);
}
  
//+-----------------------------------------------------------------------------+
//--- Function: CheckBreakevenClose
//--- Description: When using CLOSE_BY_POINTS and multiple positions exist,
//---              calculates the weighted breakeven price and checks if the
//---              current price has moved the specified points in a profitable
//---              direction relative to breakeven. If so, closes all positions.
//+-----------------------------------------------------------------------------+
void CheckBreakevenClose(double ask, double bid){
   //--- Ensure we have more than one position (grid positions)
   if(PositionsTotal() <= 1)
      return;
      
   double weightedBreakeven = CalculateWeightedBreakevenPrice();
   int posType = -1;
   //--- Determine the trade type from one of the positions
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   if(posType == -1)
      return;
      
   //--- For BUY positions, profit when Bid >= breakeven + threshold
   if(posType == POSITION_TYPE_BUY){
      if(bid >= weightedBreakeven + breakevenPoints){
         Print("Closing BUY positions: Bid (", bid, ") >= Breakeven (", weightedBreakeven, ") + ", breakevenPoints);
         CloseAllPositions();
      }
   }
   //--- For SELL positions, profit when Ask <= breakeven - threshold
   else if(posType == POSITION_TYPE_SELL){
      if(ask <= weightedBreakeven - breakevenPoints){
         Print("Closing SELL positions: Ask (", ask, ") <= Breakeven (", weightedBreakeven, ") - ", breakevenPoints);
         CloseAllPositions();
      }
   }
}

Here, we calculate the breakeven price for all open positions and determine if the market price has moved a specified distance beyond it to close positions for profit. In "CalculateWeightedBreakevenPrice", we compute the weighted breakeven price by summing the total cost of all open positions using POSITION_PRICE_OPEN and weighting it by "POSITION_VOLUME". We first determine the position type (BUY or SELL) from the first open position using POSITION_TYPE. We then loop through all positions, summing the total cost and volume for positions matching the identified type. If the total volume is greater than zero, we return the weighted breakeven price by dividing the total cost by the total volume. Otherwise, we return zero.

In "CheckBreakevenClose", we first confirm there are multiple open positions using the PositionsTotal function. We then retrieve the weighted breakeven price by calling "CalculateWeightedBreakevenPrice". We determine the position type by selecting a position and retrieving POSITION_TYPE. If the type is invalid, we exit the function. For BUY positions, we check if the "bid" price has reached or exceeded "weightedBreakeven" plus "breakevenPoints". If so, we print a message and call "CloseAllPositions". For SELL positions, we check if the "ask" price has dropped below "weightedBreakeven" minus "breakevenPoints". If this condition is met, we also print a message and call the "CloseAllPositions" function to secure profits. Upon compilation and running the program, we have the following outcome.

GRID GIF

From the visualization, we can see that the positions are opened and managed via the grid system and closed when the defined closure levels are hit, hence achieving our objective of creating a grid system with dynamic lot sizing. The thing that remains is backtesting the program, and that is handled in the next section.


Backtesting

After thorough backtesting, we have the following results.

Backtest graph:

GRAPH

Backtest report:

REPORT

Here is also a video format showcasing the whole strategy backtest within a period of 1 year, 2024.



Conclusion

In conclusion, we have demonstrated the process of developing an MQL5 Expert Advisor (EA) utilizing a dynamic grid trading strategy. By combining key elements such as grid order placement, dynamic lot scaling, and targeted profit and breakeven management, we created a system that adapts to market fluctuations, aiming to optimize risk-to-reward ratios and recover from adverse price movements.

Disclaimer: This article is for educational purposes only. Trading involves significant financial risk, and market behavior can be highly unpredictable. While the strategies outlined offer a structured approach to grid trading, they do not guarantee future profitability. Rigorous backtesting and risk management are essential before live trading.

By implementing these techniques, you can refine your grid trading systems, enhance your market analysis, and elevate your algorithmic trading strategies. Best of luck on your trading journey!

Last comments | Go to discussion (9)
testtestmio71
testtestmio71 | 4 Apr 2025 at 14:49
double low1  = iLow(_Symbol, _Period, 1);

for example.....for buy signal you used iLow and not Low1 variabile

if (iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){

is only for my study, thanks!!!

testtestmio71
testtestmio71 | 4 Apr 2025 at 14:58

these four lines can be commented

// double low1  = iLow(_Symbol, _Period, 1);
// double low2  = iLow(_Symbol, _Period, 2);
// double high1 = iHigh(_Symbol, _Period, 1);
// double high2 = iHigh(_Symbol, _Period, 2);
Allan Munene Mutiiria
Allan Munene Mutiiria | 4 Apr 2025 at 15:13
testtestmio71 #:

these four lines can be commented

Sure. So is everything okay now?

testtestmio71
testtestmio71 | 4 Apr 2025 at 15:19

is ok.....best EA .

The 4 lines are confusing for a newbie

Allan Munene Mutiiria
Allan Munene Mutiiria | 4 Apr 2025 at 16:35
testtestmio71 #:

is ok.....best EA .

The 4 lines are confusing for a newbie

Okay

Animal Migration Optimization (AMO) algorithm Animal Migration Optimization (AMO) algorithm
The article is devoted to the AMO algorithm, which models the seasonal migration of animals in search of optimal conditions for life and reproduction. The main features of AMO include the use of topological neighborhood and a probabilistic update mechanism, which makes it easy to implement and flexible for various optimization tasks.
Price Action Analysis Toolkit Development (Part 13): RSI Sentinel Tool Price Action Analysis Toolkit Development (Part 13): RSI Sentinel Tool
Price action can be effectively analyzed by identifying divergences, with technical indicators such as the RSI providing crucial confirmation signals. In the article below, we explain how automated RSI divergence analysis can identify trend continuations and reversals, thereby offering valuable insights into market sentiment.
Master MQL5 from beginner to pro (Part IV): About Arrays, Functions and Global Terminal Variables Master MQL5 from beginner to pro (Part IV): About Arrays, Functions and Global Terminal Variables
The article is a continuation of the series for beginners. It covers in detail data arrays, the interaction of data and functions, as well as global terminal variables that allow data exchange between different MQL5 programs.
Creating a Trading Administrator Panel in MQL5 (Part IX): Code Organization (II): Modularization Creating a Trading Administrator Panel in MQL5 (Part IX): Code Organization (II): Modularization
In this discussion, we take a step further in breaking down our MQL5 program into smaller, more manageable modules. These modular components will then be integrated into the main program, enhancing its organization and maintainability. This approach simplifies the structure of our main program and makes the individual components reusable in other Expert Advisors (EAs) and indicator developments. By adopting this modular design, we create a solid foundation for future enhancements, benefiting both our project and the broader developer community.