Partial order closing is a crucial technique in managing trades, allowing traders to secure profits or reduce risk without exiting a position entirely. In MQL4, this capability is achieved by carefully utilizing built-in trading functions.
Understanding the concept of partial order closing
Partial order closing refers to the process of closing only a portion of an open trading position. For instance, if you have a 1.0 lot BUY order on EURUSD, you might decide to close 0.5 lots to lock in some profit while leaving the remaining 0.5 lots open to potentially capture further gains.
The original order’s ticket number remains the same, but its volume is reduced.
Why use partial order closing?
Traders employ partial closing for several strategic reasons:
- Profit Taking: Secure a portion of unrealized profits, especially when approaching a significant resistance/support level or a target price.
- Risk Reduction: Decrease the exposure of a trade that has moved favorably, effectively reducing the potential loss if the market reverses. The breakeven point of the overall position can also be managed this way.
- Scaling Out: Systematically exit a position in stages as the price moves in the desired direction, which is a common component of many trading strategies.
- Adapting to Market Conditions: Adjust position size based on changing volatility or new market information without completely abandoning the trade idea.
Prerequisites: Basic MQL4 Knowledge
Before diving into partial order closing, it’s assumed you have a solid understanding of:
- MQL4 syntax and program structure (EAs, scripts).
- Order management basics:
OrderSend(),OrderSelect(),OrderLots(),OrderTicket(),OrderType(), etc. - Looping through open orders in the trade pool.
- Basic error handling using
GetLastError().
Core Function: OrderClose() in MQL4
The primary function used for closing orders, including partial closures, in MQL4 is OrderClose().
Syntax and parameters of the OrderClose() function
The function signature is:
bool OrderClose(int ticket, double lots, double price, int slippage, color arrow_color=CLR_NONE)
ticket: The unique ticket number of the order to be closed.lots: The volume (in lots) to be closed. This is the key parameter for partial closing.price: The price at which to close the specified volume. For BUY orders, this is the current Bid price. For SELL orders, this is the current Ask price. Ensure to useMarketInfo(Symbol(), MODE_BID)orMarketInfo(Symbol(), MODE_ASK)appropriately, orOrderClosePrice()if it’s a pending order being triggered and closed.slippage: The maximum allowed deviation in pips from the specifiedprice. This is crucial for execution in volatile markets.arrow_color: The color of the closing arrow on the chart. Optional, defaults toCLR_NONE(no arrow).
Understanding ‘lots’ parameter for partial closing
For a full close, you would pass OrderLots() (the current volume of the selected order) to the lots parameter. For a partial close, you pass a volume less than OrderLots(). For example, to close half of a 1.0 lot order, you would pass 0.5.
It’s crucial to ensure the lots parameter adheres to the symbol’s lot step and minimum lot size requirements (e.g., MarketInfo(Symbol(), MODE_LOTSTEP), MarketInfo(Symbol(), MODE_MINLOT)). The volume to close must also not exceed the current OrderLots().
Other relevant parameters: slippage, color
- Slippage: Defines the maximum price difference (in pips) between the requested close price and the actual execution price. If the market price moves beyond this slippage limit during the execution attempt, the order will not be closed, and
OrderClose()will returnfalse. - Color: Purely visual. It allows you to specify the color of the arrow displayed on the chart where the order (or part of it) was closed. This can be useful for visual backtesting or EA monitoring.
Implementing Partial Order Closing: Step-by-Step
Implementing partial order closing involves a sequence of checks and actions.
Selecting the Order to Modify (OrderSelect())
Before you can close any part of an order, you must first select it using OrderSelect().
bool SelectOrder(int ticket)
{
if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
// Order selected successfully
return(true);
}
else
{
Print("Error selecting order #", ticket, ": ", GetLastError());
return(false);
}
}
Typically, you’ll iterate through the OrdersTotal() count, selecting orders based on criteria like symbol, magic number, or order type.
Calculating the volume to close partially
This is where your strategy’s logic comes in. You might want to close a fixed percentage, a specific lot amount, or based on profit levels.
Example: Closing 50% of current order volume
double lotsToClose = OrderLots() * 0.5;
// Normalize the lots according to symbol's lot step
lotsToClose = NormalizeDouble(lotsToClose, Digitsロットステップ()); // Custom function to get lot step digits
// Ensure it doesn't go below minimum lot size or above current lots
if (lotsToClose < MarketInfo(OrderSymbol(), MODE_MINLOT))
{
lotsToClose = 0; // Or handle as an error: cannot close such small part
}
if (lotsToClose > OrderLots())
{
lotsToClose = OrderLots(); // Should not happen if calculated from OrderLots() initially
}
// Helper function for lot step normalization (simplified example)
// In a real scenario, use MarketInfo(OrderSymbol(), MODE_LOTSTEP)
int Digitsロットステップ()
{
double lotStep = MarketInfo(OrderSymbol(), MODE_LOTSTEP);
if (lotStep == 0.1) return 1;
if (lotStep == 0.01) return 2;
// Add more cases or a more generic calculation
return 2; // Default
}
Ensure the calculated lotsToClose is a valid lot size and respects the MODE_LOTSTEP for the symbol.
Executing the OrderClose() function for partial closing
Once the order is selected and the volume to close is determined, you can call OrderClose().
int ticket = OrderTicket();
double currentLots = OrderLots();
double lotsToClose = NormalizeDouble(currentLots * 0.5, Digitsロットステップ()); // e.g., 50%
double closePrice;
if(OrderType() == OP_BUY)
{
closePrice = MarketInfo(OrderSymbol(), MODE_BID);
}
else if(OrderType() == OP_SELL)
{
closePrice = MarketInfo(OrderSymbol(), MODE_ASK);
}
else
{
Print("Cannot partially close pending orders this way.");
return; // Or handle differently
}
if (lotsToClose >= MarketInfo(OrderSymbol(), MODE_MINLOT) && lotsToClose < currentLots)
{
RefreshRates(); // Ensure prices are up-to-date
bool closed = OrderClose(ticket, lotsToClose, closePrice, 3, Red); // 3 pips slippage
if(closed)
{
Print("Partially closed order #", ticket, ", closed lots: ", lotsToClose);
}
else
{
Print("Error partially closing order #", ticket, ": ", GetLastError());
}
}
else if (lotsToClose >= currentLots) {
Print("Lots to close (", lotsToClose, ") is equal or greater than current lots (", currentLots, "). Performing full close instead or skipping.");
// Optionally, perform a full close if that's desired behavior
} else {
Print("Calculated lots to close (", lotsToClose, ") is less than minimum lot size for ", OrderSymbol());
}
Error handling and validation
Always check the return value of OrderClose(). If it’s false, use GetLastError() to understand why the operation failed. Common errors include:
ERR_INVALID_TICKET(130): Order ticket is incorrect or order is already closed/deleted.ERR_INVALID_LOTS(131): Lots value is incorrect (e.g., zero, negative, too large, or doesn’t meet lot step requirements).ERR_INVALID_PRICE(129): The price specified is too far from the current market price (check slippage).ERR_SERVER_BUSY(137),ERR_REQUOTE(138),ERR_TRADE_TIMEOUT(148): Server-side issues or rapid market movement.ERR_NOT_ENOUGH_MONEY(134): Not usually for closing, but good to be aware of general trade errors.ERR_TRADE_TOO_MANY_ORDERS(140): If the partial close somehow leads to exceeding broker limits (rare for closes).
Ensure the remaining volume after partial close (OrderLots() - lotsToClose) is not less than MarketInfo(OrderSymbol(), MODE_MINLOT), unless you are closing the entire remaining portion.
Practical Examples and Code Snippets
Below are focused examples for common partial closing scenarios.
Closing 50% of the order volume
This function attempts to close 50% of a specified order.
bool PartiallyCloseOrder_Percent(int ticket, double percentToClose, int slippagePips)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
Print("Error selecting order #", ticket, " for partial close: ", GetLastError());
return(false);
}
if(OrderSymbol() != Symbol())
{
Print("Order symbol ", OrderSymbol(), " does not match chart symbol ", Symbol());
// Potentially an issue if MarketInfo is called for Symbol() but order is on different one
// It's safer to use OrderSymbol() for MarketInfo calls related to the order.
}
double currentLots = OrderLots();
double lotsToClose = currentLots * (percentToClose / 100.0);
// Normalize lotsToClose to symbol's lot step
double lotStep = MarketInfo(OrderSymbol(), MODE_LOTSTEP);
lotsToClose = MathFloor(lotsToClose / lotStep) * lotStep;
lotsToClose = NormalizeDouble(lotsToClose, MarketInfo(OrderSymbol(), MODE_DIGITS) == 3 || MarketInfo(OrderSymbol(), MODE_DIGITS) == 5 ? (lotStep == 0.01 ? 2 : (lotStep == 0.1 ? 1 : 0)) : (lotStep == 0.01 ? 2 : (lotStep == 0.1 ? 1 : 0)) ); // Simplified, better to determine digits based on lotStep directly
int lotStepDigits = 0;
if(lotStep == 0.1) lotStepDigits = 1;
else if (lotStep == 0.01) lotStepDigits = 2;
else if (lotStep == 0.001) lotStepDigits = 3; // etc.
lotsToClose = NormalizeDouble(lotsToClose, lotStepDigits);
if(lotsToClose < MarketInfo(OrderSymbol(), MODE_MINLOT))
{
Print("Cannot close ", lotsToClose, " lots. Less than MINLOT for ", OrderSymbol());
return(false);
}
// Ensure remaining volume is valid
if (NormalizeDouble(currentLots - lotsToClose, lotStepDigits) < MarketInfo(OrderSymbol(), MODE_MINLOT) &&
NormalizeDouble(currentLots - lotsToClose, lotStepDigits) > 0) {
Print("Partial close would leave remaining volume (", NormalizeDouble(currentLots - lotsToClose, lotStepDigits), ") less than MINLOT. Closing full order instead or aborting.");
// Decide: either close all (lotsToClose = currentLots) or abort.
// For this example, we abort.
// If you want to close fully, uncomment next line:
// lotsToClose = currentLots;
// else return(false);
return(false); // Aborting to prevent invalid remaining volume
}
if(lotsToClose >= currentLots)
{
Print("Calculated lots to close (", lotsToClose, ") meets or exceeds current lots (", currentLots, "). Performing full close.");
lotsToClose = currentLots; // Ensure it's a full close
}
double price;
if(OrderType() == OP_BUY)
price = MarketInfo(OrderSymbol(), MODE_BID);
else if(OrderType() == OP_SELL)
price = MarketInfo(OrderSymbol(), MODE_ASK);
else
return(false); // Not a market order
RefreshRates();
if(!OrderClose(OrderTicket(), lotsToClose, price, slippagePips, Red))
{
Print("Error partially closing order #", OrderTicket(), ": ", GetLastError());
return(false);
}
Print("Successfully partially closed ", lotsToClose, " lots of order #", OrderTicket());
return(true);
}
// Usage:
// PartiallyCloseOrder_Percent(someTicket, 50.0, 3); // Close 50% with 3 pips slippage
Closing a specific amount of lots (e.g., 0.1 lots)
This function attempts to close a fixed number of lots from an order.
bool PartiallyCloseOrder_FixedLots(int ticket, double lotsToCloseParam, int slippagePips)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
Print("Error selecting order #", ticket, " for partial close: ", GetLastError());
return(false);
}
double currentLots = OrderLots();
double lotsToClose = lotsToCloseParam;
// Normalize lotsToClose to symbol's lot step (essential!)
double lotStep = MarketInfo(OrderSymbol(), MODE_LOTSTEP);
int lotStepDigits = 0;
if(lotStep == 0.1) lotStepDigits = 1;
else if (lotStep == 0.01) lotStepDigits = 2;
else if (lotStep == 0.001) lotStepDigits = 3;
lotsToClose = NormalizeDouble(MathFloor(lotsToClose / lotStep) * lotStep, lotStepDigits);
if(lotsToClose <= 0)
{
Print("Lots to close must be positive.");
return(false);
}
if(lotsToClose >= currentLots)
{
Print("Lots to close (", lotsToClose, ") is >= current lots (", currentLots, "). Closing entire order.");
lotsToClose = currentLots; // This becomes a full close
}
else if (NormalizeDouble(currentLots - lotsToClose, lotStepDigits) < MarketInfo(OrderSymbol(), MODE_MINLOT))
{
Print("Partial close of ", lotsToClose, " lots would leave remaining volume (", NormalizeDouble(currentLots - lotsToClose, lotStepDigits), ") less than MINLOT for ", OrderSymbol(), ". Aborting.");
return(false); // Abort to prevent invalid remaining volume
}
if(lotsToClose < MarketInfo(OrderSymbol(), MODE_MINLOT))
{
Print("Lots to close (", lotsToClose, ") is less than MINLOT (", MarketInfo(OrderSymbol(), MODE_MINLOT), ") for ", OrderSymbol(), ". Aborting.");
return(false);
}
double price;
if(OrderType() == OP_BUY)
price = MarketInfo(OrderSymbol(), MODE_BID);
else if(OrderType() == OP_SELL)
price = MarketInfo(OrderSymbol(), MODE_ASK);
else
return(false); // Not a market order
RefreshRates();
if(!OrderClose(OrderTicket(), lotsToClose, price, slippagePips, Blue))
{
Print("Error partially closing ", lotsToClose, " lots of order #", OrderTicket(), ": ", GetLastError());
return(false);
}
Print("Successfully partially closed ", lotsToClose, " lots of order #", OrderTicket(), ". Remaining lots: ", NormalizeDouble(OrderLots() - lotsToClose, lotStepDigits) ); // Note: OrderLots() will update after successful partial close. Better to calculate remaining based on prior state for this print statement.
return(true);
}
// Usage:
// PartiallyCloseOrder_FixedLots(someTicket, 0.1, 3); // Close 0.1 lots with 3 pips slippage
Conditional partial closing based on profit/loss
This can be integrated into an EA’s OnTick() function or a script.
void CheckAndPartiallyCloseOnProfit(int magicNumber, double targetProfitForPartialClose, double percentageToClose)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == magicNumber)
{
if(OrderProfit() + OrderSwap() >= targetProfitForPartialClose)
{
// Assuming PartiallyCloseOrder_Percent is defined as above
PartiallyCloseOrder_Percent(OrderTicket(), percentageToClose, 3);
// Potentially add a flag to prevent repeated partial closures on the same order for the same profit level
}
}
}
}
}
// Usage within OnTick():
// CheckAndPartiallyCloseOnProfit(12345, 50.0, 50.0); // If profit >= $50, close 50% of orders with magic 12345
Advanced Techniques and Considerations
Managing the Remaining Position After Partial Close
When OrderClose() successfully executes a partial close:
- The original
OrderTicket()remains the same. OrderLots()is reduced by the amount closed.OrderOpenPrice()remains the same as the original order’s open price.OrderProfit(),OrderCommission(), andOrderSwap()will be recalculated by the terminal for the new, smaller volume.- The history will show a new entry for the closed portion, detailing its execution price and profit/loss.
It’s important to re-select the order (or ensure it’s still selected) and refresh its data using OrderSelect() again if you need to work with its updated properties immediately after a partial close within the same code block or tick.
Managing order comments and magic numbers
- Magic Number: The
OrderMagicNumber()of the remaining portion of the order remains unchanged. - Order Comment: The
OrderComment()also remains unchanged. If you need to update the comment (e.g., to signify it’s a partially closed order), you would need to useOrderModify(). However,OrderModify()cannot change the volume. The comment modification would be a separate operation on the order after the partial close, typically only changing the stop loss, take profit, or comment.
mq4
// After successful partial close...
// if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) // Re-select to be safe
// {
// OrderModify(OrderTicket(), OrderOpenPrice(), OrderStopLoss(), OrderTakeProfit(), 0, "Partially Closed - " + OrderComment());
// }
Note: Modifying comments frequently might not be ideal. Consider if it’s truly necessary.
Partial closing within Expert Advisors (EAs)
In EAs, partial closing is often part of a more complex trade management strategy:
- Multiple Profit Targets: Close portions of an order at different predefined profit levels.
- Trailing Stop with Partial Close: As profit increases, trail the stop loss and simultaneously close parts of the position to lock in gains.
- Risk Management: If a trade goes into a certain amount of drawdown but then recovers to a smaller loss or breakeven, an EA might partially close to reduce further risk.
Careful state management is required. For instance, an EA needs to track if a partial close has already been performed for a specific condition on an order to avoid repeated attempts.
Potential issues and troubleshooting
- Lot Size Normalization: Always normalize lot sizes using
NormalizeDouble()and respectMODE_LOTSTEP,MODE_MINLOT, andMODE_MAXLOT. Failure to do so is a common source ofERR_INVALID_LOTS(131). - Remaining Volume: Ensure the volume remaining after a partial close (
OrderLots() - lotsToClose) is either zero (full close) or greater than or equal toMarketInfo(OrderSymbol(), MODE_MINLOT). Trying to leave a remaining volume like 0.001 when min lot is 0.01 will fail. - Price Synchronization: Use
RefreshRates()before fetchingMarketInfo(Symbol(), MODE_BID)orMarketInfo(Symbol(), MODE_ASK)to get the most current prices, especially in fast-moving markets. - Slippage: Too tight slippage in volatile markets can lead to frequent
ERR_REQUOTEor failed closures. Too wide slippage can result in unfavorable closing prices. - Order Selection: Double-check that the correct order is selected via
OrderSelect()before attemptingOrderClose(). Incorrect selection can lead to closing the wrong trade or errors. - Context Busy (Error 146 –
ERR_TRADE_CONTEXT_BUSY): If multiple trade operations are attempted too rapidly, you might encounter this. Implement short pauses or retry mechanisms if necessary. - Broker Restrictions: Some brokers might have specific rules or limitations regarding frequent partial closures or minimum time between trade operations. Always test with your specific broker.
By understanding OrderClose() and carefully managing lot sizes and error conditions, you can effectively implement partial order closing strategies in your MQL4 programs, giving you finer control over your trade management.