🚀 AI One-Click Generation of mql4 Strategy Code

How to Code Multiple Take Profit Levels in an Mql4 EA

mql4 | Published on: 2025/12/15
The following content is generated by EasyQuant .

Problem Description

How can I set up a trade to close parts of its volume at different profit targets?

Solution

To implement a partial close strategy in MQL4, you need to use the OrderClose() function but specify a lot size smaller than the total order volume. When you do this, MetaTrader 4 automatically closes the specified amount and keeps the remaining volume open as a new order (usually preserving the open price and ticket lineage).

Here is a complete, robust Expert Advisor (EA) structure designed to manage trades by closing portions of the volume at two distinct profit targets.

Strategy Logic

  1. Target 1: When profit reaches X pips, close Y% of the original volume.
  2. Target 2: When profit reaches Z pips, close another W% (or the remainder).
  3. State Management: The code checks the current OrderLots() against the InitialLots setting to determine if a partial close has already occurred. This prevents the EA from trying to close the same target multiple times.

MQL4 Code

//+------------------------------------------------------------------+
//|                                        PartialCloseManager.mq4   |
//|                        Copyright 2023, Quantitative Trading AI   |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative Trading AI"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//--- Input Parameters
input double InitialLots = 1.0;       // The starting lot size of your trades
input int    MagicNumber = 123456;    // Magic number to identify trades
input int    Slippage    = 3;         // Max slippage in pips

input string Separator1  = "=== Target 1 Settings ===";
input int    TP1_Pips    = 20;        // Pips required for 1st Partial Close
input double TP1_Percent = 50.0;      // Percentage of INITIAL lots to close at TP1

input string Separator2  = "=== Target 2 Settings ===";
input int    TP2_Pips    = 40;        // Pips required for 2nd Partial Close
input double TP2_Percent = 25.0;      // Percentage of INITIAL lots to close at TP2

//--- Global variables for lot step normalization
double g_lotStep;
double g_minLot;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Get broker limitations for lot sizing
   g_lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
   g_minLot  = MarketInfo(Symbol(), MODE_MINLOT);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Loop through all open orders
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         // Filter by Symbol and Magic Number
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
         {
            // Determine order type and calculate profit in pips
            int type = OrderType();
            double pipsProfit = 0;
            
            // Calculate Pips based on direction
            if(type == OP_BUY)
            {
               pipsProfit = (Bid - OrderOpenPrice()) / Point;
            }
            else if(type == OP_SELL)
            {
               pipsProfit = (OrderOpenPrice() - Ask) / Point;
            }
            
            // Adjust for 5-digit brokers (10 points = 1 pip)
            // If Point is 0.00001, we divide by 10 to get standard pips
            if(Digits == 3 || Digits == 5) pipsProfit /= 10;

            //--- LOGIC FOR PARTIAL CLOSES ---
            
            double currentLots = OrderLots();
            
            // Check Target 1
            // Condition: Profit hit AND we haven't closed TP1 yet.
            // We assume TP1 is not taken if current lots are close to InitialLots.
            if(pipsProfit >= TP1_Pips && currentLots >= (InitialLots - 0.001)) 
            {
               double lotsToClose = NormalizeLotSize(InitialLots * (TP1_Percent / 100.0));
               ClosePartial(OrderTicket(), lotsToClose);
            }
            
            // Check Target 2
            // Condition: Profit hit AND we have already taken TP1 (Lots are smaller than initial)
            // AND we haven't taken TP2 yet (Lots are larger than what would remain after TP2).
            else if(pipsProfit >= TP2_Pips)
            {
               // Calculate expected remaining lots after TP1
               double lotsAfterTP1 = InitialLots - NormalizeLotSize(InitialLots * (TP1_Percent / 100.0));
               
               // If current lots match the state "After TP1", execute TP2
               if(MathAbs(currentLots - lotsAfterTP1) < 0.001)
               {
                  double lotsToClose = NormalizeLotSize(InitialLots * (TP2_Percent / 100.0));
                  ClosePartial(OrderTicket(), lotsToClose);
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Helper: Close a specific volume of an order                      |
//+------------------------------------------------------------------+
void ClosePartial(int ticket, double lots)
{
   if(OrderSelect(ticket, SELECT_BY_TICKET))
   {
      // Ensure we don't try to close more than exists or less than minimum
      if(lots > OrderLots()) lots = OrderLots();
      if(lots < g_minLot) return;

      bool res = false;
      int type = OrderType();
      
      // Refresh rates before closing
      RefreshRates();
      
      if(type == OP_BUY)
      {
         res = OrderClose(ticket, lots, Bid, Slippage, clrGreen);
      }
      else if(type == OP_SELL)
      {
         res = OrderClose(ticket, lots, Ask, Slippage, clrRed);
      }
      
      if(res)
      {
         Print("Partial Close Successful. Ticket: ", ticket, " Volume: ", lots);
      }
      else
      {
         Print("Partial Close Failed. Error: ", GetLastError());
      }
   }
}

//+------------------------------------------------------------------+
//| Helper: Normalize lots to broker step                            |
//+------------------------------------------------------------------+
double NormalizeLotSize(double lots)
{
   // Round to nearest step
   double steps = MathRound(lots / g_lotStep);
   double normalized = steps * g_lotStep;
   
   // Clamp to min/max limits
   if(normalized < g_minLot) normalized = g_minLot;
   double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
   if(normalized > maxLot) normalized = maxLot;
   
   return normalized;
}

Key Components Explained

  1. Inputs:

    • InitialLots: The EA needs to know the starting size of the trade to calculate percentages correctly.
    • TP1_Pips / TP1_Percent: Defines the first partial close.
    • TP2_Pips / TP2_Percent: Defines the second partial close.
  2. Logic for Detecting State:

    • The EA does not use a database to remember if it closed a trade. Instead, it looks at the Current Lot Size.
    • TP1 Check: if(currentLots >= InitialLots)
      • If the current volume is equal to the initial volume, we know we haven't closed anything yet. If profit > TP1, we close the first chunk.
    • TP2 Check: if(MathAbs(currentLots - lotsAfterTP1) < 0.001)
      • We calculate what the lot size should be after TP1 is taken. If the current order matches that size, and price hits TP2, we close the second chunk.
  3. NormalizeLotSize Function:

    • This is critical. If you calculate 50% of 0.15 lots, you get 0.075. Most brokers only accept steps of 0.01. This function rounds the calculated volume to the nearest valid step (e.g., 0.08) to prevent OrderClose Error 131 (Invalid Trade Volume).
  4. Pip Calculation:

    • The code automatically detects 3 or 5 digit brokers (where 1 pip = 10 points) to ensure TP1_Pips = 20 actually means 20 pips, not 2 pips.

How to Use This

  1. Compile the code in MetaEditor.
  2. Attach the EA to a chart.
  3. Important: Ensure the InitialLots input matches the size of the trades you plan to open (or modify the code to detect the initial size dynamically if you are opening trades manually).
  4. When a trade with the matching MagicNumber hits the profit targets, the EA will automatically close the calculated volume.

Q&A

Q: What happens to the Stop Loss after a partial close?
A: In MT4, when you perform a partial close, the remaining open position keeps the original Stop Loss and Take Profit prices. If you want to move the Stop Loss to Breakeven after TP1, you would need to add an OrderModify call inside the TP1 logic block.

Q: Can I use this on manually opened trades?
A: Yes, but you must set MagicNumber to 0 in the inputs (manual trades usually have Magic Number 0) and ensure InitialLots matches your manual trade size exactly.

Q: Why did the EA close slightly more or less than the exact percentage?
A: This is due to NormalizeLotSize. If you trade 0.10 lots and ask to close 33%, the math is 0.033. The broker likely only accepts 0.01 steps, so it will round to 0.03.