To build an automated trade manager in MT4, you have to account for a critical quirk in the MQL4 engine: Ticket Splitting.
When you partially close a position in MT4, the server moves the original order ticket into history and instantly creates a new ticket for the remaining lot size. If your code relies on tracking the original ticket number, it will instantly lose control of the trade after the partial close.
To solve this, we use the Breakeven (BE) modification as a "State Flag." By modifying the Stop Loss to the entry price before executing the partial close, the new split ticket will automatically inherit the updated breakeven Stop Loss. This prevents the EA from getting stuck in an infinite loop.
Here is the production-grade trade management block.
The MQL4 Implementation
Code: Select all
// External parameters for user optimization
extern double RR_Target = 1.0; // Risk:Reward ratio to trigger the event (e.g., 1.0 = 1R)
extern double PartialClosePercent = 50.0; // Percentage of the position to close (e.g., 50%)
extern int BreakevenOffsetPips = 1; // Pips to lock in to cover commissions/swap
//+------------------------------------------------------------------+
//| Moves SL to Breakeven and takes partial profit at an RR target |
//+------------------------------------------------------------------+
void ManageTrades() {
// Loop backwards to safely handle index shifts during OrderClose
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
// Filter for our EA's trades on the current chart
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
int type = OrderType();
double openPrice = OrderOpenPrice();
double currentSL = OrderStopLoss();
// Calculate offset in points (using previous fractional digit logic)
double offsetPoints = BreakevenOffsetPips * 10 * Point; // Assuming 3/5 digit broker
double bePrice = (type == OP_BUY) ? openPrice + offsetPoints : openPrice - offsetPoints;
// 1. STATE CHECK: If SL is already at or past BE, skip to prevent infinite loops
if ((type == OP_BUY && currentSL >= bePrice) ||
(type == OP_SELL && (currentSL <= bePrice && currentSL != 0))) {
continue;
}
// 2. Calculate the original risk distance to find the exact RR target
double riskDistance = MathAbs(openPrice - currentSL);
if (riskDistance == 0) continue; // Safety check
bool targetHit = false;
if (type == OP_BUY) {
double targetPrice = openPrice + (riskDistance * RR_Target);
if (Bid >= targetPrice) targetHit = true;
}
else if (type == OP_SELL) {
double targetPrice = openPrice - (riskDistance * RR_Target);
if (Ask <= targetPrice) targetHit = true;
}
// 3. EXECUTION: Target Reached
if (targetHit) {
// STEP A: Move Stop Loss to Breakeven FIRST
// The new child ticket will inherit this modification
bool slMoved = OrderModify(OrderTicket(), openPrice, bePrice, OrderTakeProfit(), 0, Blue);
// STEP B: Execute Partial Close
if (slMoved) {
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
// Calculate exact lots to close, rounded down to broker's lot step
double rawLots = OrderLots() * (PartialClosePercent / 100.0);
double lotsToClose = MathFloor(rawLots / lotStep) * lotStep;
// Ensure we don't try to close less than the minimum allowed, or the full position
if (lotsToClose >= minLot && lotsToClose < OrderLots()) {
double closePrice = (type == OP_BUY) ? Bid : Ask;
int safeSlippage = NormalizeSlippage(2); // From previous slippage control
bool closed = OrderClose(OrderTicket(), lotsToClose, closePrice, safeSlippage, clrGreen);
if (!closed) {
Print("Partial close failed. Error: ", GetLastError());
}
}
} else {
Print("Failed to move SL to Breakeven. Error: ", GetLastError());
}
}
}
}
}
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.