Problem Description
Solution
To close half of an open trade in MQL4, you must perform a "Partial Close." This involves using the OrderClose() function but specifying a lot size smaller than the total volume of the order.
The most critical part of this logic is calculating the lot size correctly. You cannot simply divide by 2; you must normalize the result to match the broker's Lot Step (e.g., 0.01 or 0.1) and ensure it meets the Minimum Lot requirement.
MQL4 Code Implementation
Here is a robust function CloseHalfTrade that handles lot normalization and executes the partial close.
//+------------------------------------------------------------------+
//| Function to close half of a specific order |
//+------------------------------------------------------------------+
bool CloseHalfTrade(int ticket)
{
// 1. Select the order
if(!OrderSelect(ticket, SELECT_BY_TICKET))
{
Print("CloseHalfTrade: Failed to select ticket ", ticket, ". Error: ", GetLastError());
return(false);
}
// 2. Check if it is a market order (OP_BUY or OP_SELL)
int type = OrderType();
if(type != OP_BUY && type != OP_SELL)
{
Print("CloseHalfTrade: Ticket ", ticket, " is not a market order.");
return(false);
}
string symbol = OrderSymbol();
// 3. Get Market Info for Lot calculations
double lotStep = MarketInfo(symbol, MODE_LOTSTEP);
double minLot = MarketInfo(symbol, MODE_MINLOT);
double currentLots = OrderLots();
// 4. Calculate Half Lots
double halfLots = currentLots / 2.0;
// 5. Normalize Lots to the Broker's Step
// We use MathFloor to ensure we don't round up to a value we don't possess if math is tight,
// or MathRound for standard rounding. Here MathRound is generally safer for "half".
halfLots = MathRound(halfLots / lotStep) * lotStep;
// 6. Validation
if(halfLots < minLot)
{
Print("CloseHalfTrade: Calculated half lots (", halfLots, ") is below minimum lot size.");
return(false);
}
if(currentLots <= minLot)
{
Print("CloseHalfTrade: Order is already at minimum lot size, cannot split.");
return(false);
}
// 7. Determine Closing Price
// Note: We use MarketInfo to get the price of the Order's symbol,
// in case the EA is running on a different chart.
double closePrice = 0.0;
color arrowColor = clrNONE;
RefreshRates(); // Ensure prices are current
if(type == OP_BUY)
{
closePrice = MarketInfo(symbol, MODE_BID);
arrowColor = clrRed;
}
else // OP_SELL
{
closePrice = MarketInfo(symbol, MODE_ASK);
arrowColor = clrBlue;
}
// 8. Execute Partial Close
// We pass 'halfLots' as the amount to close. The remaining volume stays open with the same ticket number.
bool result = OrderClose(ticket, halfLots, closePrice, 3, arrowColor);
if(!result)
{
Print("CloseHalfTrade: Failed to close half. Error: ", GetLastError());
return(false);
}
Print("CloseHalfTrade: Successfully closed ", halfLots, " lots of ticket ", ticket);
return(true);
}
Usage Example
You can call this function inside your OnTick() function or wherever your strategy logic dictates a partial exit.
void OnTick()
{
// Example: Loop through open orders and close half if profit > 50 pips
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
// Check if the order belongs to this EA and Symbol
if(OrderSymbol() == Symbol() && OrderMagicNumber() == 12345)
{
// Calculate profit in pips
double pips = 0;
if(OrderType() == OP_BUY)
pips = (Bid - OrderOpenPrice()) / Point;
else if(OrderType() == OP_SELL)
pips = (OrderOpenPrice() - Ask) / Point;
// If profit is greater than 50 pips and lots > 0.01 (example check)
if(pips > 50 && OrderLots() > 0.01)
{
// Call the function
CloseHalfTrade(OrderTicket());
}
}
}
}
}
Key Considerations
- Lot Normalization: The line
MathRound(halfLots / lotStep) * lotStepis crucial. If a broker has a lot step of0.01and you try to close0.015lots, the server will reject the order. This math forces the number to the nearest valid increment (e.g., 0.01 or 0.02). - Minimum Lots: If you have a trade of
0.01lots and the minimum allowed is0.01, you cannot close half. The function includes a check for this (if(halfLots < minLot)). - Ticket Numbers: In MT4, when you perform a partial close, the original ticket number is preserved for the remaining open portion of the trade. The closed portion is moved to history with a new ticket number, and the comment usually indicates "from #OriginalTicket".
- Symbol Context: The function uses
MarketInfo(symbol, ...)instead of the predefinedBidorAskvariables. This ensures the code works correctly even if the EA is attached to a EURUSD chart but is managing a GBPUSD trade.
Q&A
Q: What happens to the Stop Loss and Take Profit after a partial close?
A: The Stop Loss and Take Profit levels remain attached to the remaining open portion of the trade. They are not removed or changed unless you explicitly modify the order using OrderModify().
Q: Can I close a specific percentage other than 50%?
A: Yes. Change the calculation line double halfLots = currentLots / 2.0; to double partLots = currentLots * 0.33; (to close 33%, for example). The normalization logic will handle the rest.
Q: Why do I need RefreshRates()?
A: MQL4 stores price data in a local cache. If your EA performs complex calculations that take time, the market price might change before OrderClose is called. RefreshRates() updates the local variables to the very latest server tick data to minimize slippage or "Invalid Price" errors.