Welcome, fellow MQL developers. In algorithmic trading, precise position management is paramount. Beyond simply opening or closing a position, the ability to adjust exposure dynamically is a powerful tool. This article delves into the specifics of partially closing positions in MQL5, leveraging the dedicated PositionClosePartial() function.
Introduction to Partial Position Closing in MQL5
Understanding Position Management in MetaTrader 5
MetaTrader 5 introduced a more robust and object-oriented approach to trading operations compared to MetaTrader 4. Instead of managing individual orders (like in MT4), MT5 consolidates deals related to the same financial instrument and direction into a single position. Each position has a unique ticket, volume, open price, and other parameters accessible via dedicated functions like PositionSelect, PositionGetDouble, PositionGetInteger, etc. This position-centric model simplifies tracking overall exposure on a symbol.
Why Partially Close Positions? Advantages and Use Cases
Partial position closing allows a trader or an Expert Advisor (EA) to reduce the risk associated with an open position without fully exiting the trade. This technique offers several advantages:
- Risk Reduction: As a trade moves favourably, closing a portion locks in some profit while allowing the remainder to potentially capture further gains with reduced capital at risk.
- Scaling Out: Positions can be gradually reduced as price approaches profit targets or encounters resistance levels.
- Adaptability: Respond to changing market conditions by trimming exposure without a complete reversal of sentiment on the trade.
- Capital Management: Free up margin by reducing position size, making capital available for other potential trades.
Common use cases include taking partial profit at key levels, implementing multi-tier profit targets, or adjusting position size based on volatility changes.
Overview of the PositionClosePartial() Function
MQL5 provides the built-in function PositionClosePartial() specifically for this purpose. Unlike MQL4 where partial closing involved calling OrderClose with a smaller lot size than the original order, MQL5’s position management provides a dedicated function that acts directly on the consolidated position. This function is typically called from an EA or script to reduce the volume of a specified open position.
Implementing Partial Position Closing with PositionClosePartial()
Syntax and Parameters of PositionClosePartial()
The PositionClosePartial() function has the following syntax:
bool PositionClosePartial(
ulong ticket, // position ticket
double volume, // volume to close
ulong deviation // allowed deviation
);
ticket: This is the unique identifier of the position you wish to partially close. You obtain this ticket usually by iterating through open positions or by selecting a position based on criteria like symbol and magic number.volume: The specific volume (in lots) that you want to remove from the existing position. This volume must be less than the current volume of the position and greater than the minimum allowed volume for the symbol (typically 0.01). If you attempt to close the entire remaining volume, the function effectively performs a full close.deviation: This parameter specifies the maximum allowable deviation (in points) between the expected closing price and the actual execution price due to potential slippage. A value of 0 means no deviation is allowed, which might result in the request being rejected in volatile markets.
The function returns true if the request was successfully sent to the trading server and false otherwise. It’s crucial to understand that true only indicates successful sending, not successful execution of the trade operation. You must check the trade result and the position status subsequently.
Example 1: Simple Partial Close Based on Volume
This example demonstrates how to find a position by symbol and magic number and close a fixed volume (e.g., 0.02 lots) from it.
//+------------------------------------------------------------------+
//| Expert Advisor |
//| |
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property link "Your Website"
#property version "1.00"
// Define a magic number for our trades
input ulong InpMagicNumber = 12345;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Example: Attempt to partially close 0.02 lots from a specific position
// This is a simplified example, a real EA would use conditions
static bool partial_closed = false; // Simple flag to avoid closing on every tick
if (!partial_closed)
{
long position_ticket = -1;
double current_volume = 0.0;
// Iterate through open positions to find ours
int total_positions = PositionsTotal();
for (int i = 0; i < total_positions; i++)
{
ulong ticket = PositionGetTicket(i);
if (PositionSelectByTicket(ticket))
{
if (PositionGetString(POSITION_SYMBOL) == Symbol() &&
PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
position_ticket = ticket;
current_volume = PositionGetDouble(POSITION_VOLUME);
break; // Found our position, exit loop
}
}
}
// If a matching position was found and has enough volume
double volume_to_close = 0.02;
double min_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
if (position_ticket != -1 && current_volume >= volume_to_close + min_volume)
{
// Allowed deviation in points (e.g., 5 points)
ulong allowed_deviation = 5;
Print("Attempting to partially close ", volume_to_close, " lots from position ", position_ticket);
// Send the partial close request
bool success = PositionClosePartial(position_ticket, volume_to_close, allowed_deviation);
if (success)
{
Print("Partial close request sent successfully.");
partial_closed = true; // Set flag, in real EA you'd wait for OnTradeTransaction
}
else
{
Print("Failed to send partial close request. Error: ", GetLastError());
}
}
else if (position_ticket != -1 && current_volume < volume_to_close + min_volume && current_volume > min_volume)
{
Print("Position volume ", current_volume, " is less than volume to close ", volume_to_close);
}
}
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Cleanup code if necessary
}
// In a real EA, you would handle the trade transaction result
/*
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const long deal)
{
if (trans.type == TRADE_TRANSACTION_DEAL
&& trans.deal_type == DEAL_IN
&& trans.deal_entry == DEAL_ENTRY_OUT)
{
// This deal might be a partial close.
// Check trans.position and trans.volume to confirm.
// Update EA state based on successful partial close.
}
}
*/
Note: The simple flag partial_closed is used here only to prevent the example from attempting the close on every tick. A real-world EA would trigger partial closes based on specific trading conditions (e.g., target hit, time elapsed) and manage state using OnTradeTransaction to confirm execution.
Example 2: Partial Close Based on Percentage of Position Size
This example shows how to close a percentage (e.g., 50%) of the current position volume.
// ... (Previous code includes headers, magic number, OnInit, OnDeinit) ...
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
static bool fifty_percent_closed = false; // Simple flag
if (!fifty_percent_closed)
{
long position_ticket = -1;
double current_volume = 0.0;
// Iterate through open positions
int total_positions = PositionsTotal();
for (int i = 0; i < total_positions; i++)
{
ulong ticket = PositionGetTicket(i);
if (PositionSelectByTicket(ticket))
{
if (PositionGetString(POSITION_SYMBOL) == Symbol() &&
PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
position_ticket = ticket;
current_volume = PositionGetDouble(POSITION_VOLUME);
break;
}
}
}
// If a matching position was found
if (position_ticket != -1)
{
double percentage_to_close = 0.50; // Close 50%
double volume_to_close = current_volume * percentage_to_close;
double min_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
double volume_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
// Ensure volume is tradable (multiple of step, >= min volume)
// Round down to nearest step
volume_to_close = MathFloor(volume_to_close / volume_step) * volume_step;
// Ensure remaining volume is >= min_volume after closing
if (current_volume - volume_to_close < min_volume && current_volume - volume_to_close > 0)
{
// Adjust volume_to_close if the remainder is too small but not zero
volume_to_close = current_volume - min_volume;
// Re-adjust to step if necessary, though this case is tricky
volume_to_close = MathFloor(volume_to_close / volume_step) * volume_step;
}
// Handle case where proposed closure leaves volume < min_volume or zero
if (current_volume - volume_to_close < min_volume && current_volume - volume_to_close != 0)
{
Print("Attempted 50% close would leave less than min volume. Not closing partially.");
}
else if (volume_to_close >= min_volume) // Only proceed if calculated volume is valid
{
ulong allowed_deviation = 5;
Print("Attempting to partially close ", volume_to_close, " lots (", percentage_to_close*100, "%) from position ", position_ticket);
bool success = PositionClosePartial(position_ticket, volume_to_close, allowed_deviation);
if (success)
{
Print("Partial close request sent successfully.");
fifty_percent_closed = true; // Set flag
}
else
{
Print("Failed to send partial close request. Error: ", GetLastError());
}
}
else
{
Print("Calculated volume to close (", volume_to_close, ") is less than min volume (", min_volume, "). Not closing.");
}
}
}
}
// ... (Rest of code) ...
Important Consideration: When calculating partial volume based on percentage, always account for the symbol’s minimum volume (SYMBOL_VOLUME_MIN) and volume step (SYMBOL_VOLUME_STEP) to ensure the calculated volume is valid for trading and that the remaining volume after the partial close is also valid (either zero for a full close, or >= SYMBOL_VOLUME_MIN). Incorrect volume can lead to request rejections.
Error Handling and Result Checking
Simply calling PositionClosePartial() and checking its boolean return value is insufficient for robust EAs. The true return only confirms the request was placed in the terminal’s queue to be sent to the server. Actual execution success or failure is notified asynchronously via trade transactions.
- Check
GetLastError(): IfPositionClosePartial()returnsfalse, callGetLastError()immediately to get the reason (e.g., invalid parameters, terminal busy). Clear the error code afterwards usingResetLastError(). Possible errors includeERR_INVALID_PARAMETER,ERR_TRADE_SEND_FAILED, etc. - Monitor
OnTradeTransaction: Implement theOnTradeTransactionevent handler. When a deal occurs that corresponds to your partial close request, the system will trigger this event. Examine theMqlTradeTransactionandMqlTradeRequeststructures passed to the handler. Look for transactions withtrans.type == TRADE_TRANSACTION_DEAL,trans.deal_typeindicating an exit deal (e.g.,DEAL_INfor closing a Sell position,DEAL_OUTfor closing a Buy position), and crucially, checktrans.positionto match it with your position ticket. Thetrans.volumewill show the actual volume closed in that specific deal. - Verify Position State: After receiving confirmation via
OnTradeTransaction(or after a short delay if you don’t use the event handler, though not recommended), re-select the position usingPositionSelectByTicket(ticket)and check its newPOSITION_VOLUMEto confirm the partial close took effect and the remaining volume is as expected.
Robust error handling and transaction monitoring are critical for reliable automated trading systems.
Advanced Techniques for Partial Position Closing
Partial closing isn’t just for fixed volumes or percentages. More sophisticated strategies tie partial closures to dynamic market conditions or time.
Partial Closing Based on Profit Levels
A common technique is to take partial profit once a position reaches a predefined profit target in pips or currency. This requires monitoring the position’s current profit.
// Check if position exists and is selected
if (PositionSelectByTicket(position_ticket))
{
double current_profit = PositionGetDouble(POSITION_PROFIT);
double profit_target_currency = 100.0; // Example: $100 profit target
double volume_to_close = 0.05; // Example: Close 0.05 lots at this target
ulong allowed_deviation = 10;
if (current_profit >= profit_target_currency && PositionGetDouble(POSITION_VOLUME) >= volume_to_close + SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN))
{
Print("Position ", position_ticket, " reached profit target. Attempting partial close.");
bool success = PositionClosePartial(position_ticket, volume_to_close, allowed_deviation);
// Add error handling and state management here
}
}
This logic would typically reside within the OnTick or a timer function, iterating through relevant positions and checking their profit status against predefined thresholds.
Partial Closing Based on Time
You might want to reduce exposure as a trading session ends, before a major news event, or simply after a position has been open for a specific duration.
// Check if position exists and is selected
if (PositionSelectByTicket(position_ticket))
{
long open_time = PositionGetInteger(POSITION_TIME);
long current_time = TimeCurrent();
long duration_in_seconds = current_time - open_time;
long time_threshold_seconds = 4 * 3600; // Example: 4 hours
double volume_to_close = PositionGetDouble(POSITION_VOLUME) * 0.25; // Close 25%
ulong allowed_deviation = 5;
double min_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
double volume_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
volume_to_close = MathFloor(volume_to_close / volume_step) * volume_step;
if (duration_in_seconds >= time_threshold_seconds && volume_to_close >= min_volume)
{
Print("Position ", position_ticket, " open for ", duration_in_seconds, " seconds. Attempting partial close.");
bool success = PositionClosePartial(position_ticket, volume_to_close, allowed_deviation);
// Add error handling and state management here
}
}
This snippet checks if a position has been open for more than a specified duration and then attempts to close a percentage of its volume, ensuring the volume calculation respects trade parameters.
Combining Multiple Conditions for Partial Closing
Complex strategies often require multiple criteria to be met before a partial close occurs. You can combine conditions using logical operators (&&, ||).
// Example: Close half position if profit target reached AND it's within a specific time window
if (PositionSelectByTicket(position_ticket))
{
double current_profit = PositionGetDouble(POSITION_PROFIT);
double profit_target_currency = 200.0;
datetime current_datetime = TimeCurrent();
int hour = TimeHour(current_datetime);
bool profit_condition = (current_profit >= profit_target_currency);
bool time_condition = (hour >= 9 && hour < 17); // Example: Between 9 AM and 5 PM
double percentage_to_close = 0.50;
double current_volume = PositionGetDouble(POSITION_VOLUME);
double volume_to_close = current_volume * percentage_to_close;
double min_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
double volume_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
volume_to_close = MathFloor(volume_to_close / volume_step) * volume_step;
if (profit_condition && time_condition && volume_to_close >= min_volume)
{
Print("Conditions met for partial close on position ", position_ticket);
bool success = PositionClosePartial(position_ticket, volume_to_close, 10);
// Add error handling and state management
}
}
By combining checks for profit, time, technical indicator signals, or other custom logic, you can create highly specific partial exit strategies.
Practical Applications and Strategies
Partial closing is a fundamental component of many sophisticated trading strategies.
Using Partial Closes for Risk Management
Implementing multi-tier profit targets is a prime example. Instead of a single take profit level, an EA might close:
- 25% of the position at TP1.
- 50% of the initial volume (or 2/3 of the remaining volume) at TP2.
- The final remaining volume at TP3 or trailed to exit.
This approach locks in profits early while keeping the potential for larger gains. It effectively moves the average exit price higher if later targets are hit. Furthermore, after securing the first partial profit, the stop loss on the remaining portion can often be moved to break-even or trailing, significantly reducing or eliminating risk on the trade.
Implementing Trailing Stops with Partial Closes
A standard trailing stop reduces risk as price moves favourably. A variation involves using partial closes. Instead of trailing the stop for the entire position, you could:
- Trail the stop for the initial portion.
- Upon the trailing stop being hit, close only a part of the position instead of the whole. The stop for the remaining volume would then be adjusted.
This creates a less aggressive exit, allowing the trade more room to breathe while still reducing exposure as adverse movement occurs. It’s a custom trailing logic built around partial exits rather than the standard stop-loss modification.
Scaling Out of Positions Using Partial Closes
Scaling out is the process of progressively reducing the size of a position as price moves in the desired direction. This is directly implemented using PositionClosePartial() at predefined levels or based on indicator signals. For instance, a long position might be scaled out at:
- Resistance level 1.
- A key Fibonacci extension.
- Upon a momentum indicator signaling potential reversal.
Each scaling-out step involves calling PositionClosePartial() with a calculated volume, reducing the position size and locking in profit for that portion. This contrasts with scaling in, which involves adding to a position.
Conclusion
Summary of Key Concepts
- MetaTrader 5 manages trades via positions, a consolidation of deals.
- Partial closing reduces position volume and risk without full exit.
- MQL5 provides
PositionClosePartial(ticket, volume, deviation)for this. PositionClosePartialrequires the position ticket, the volume to close, and allowed slippage.- Robust implementation requires checking the return value, handling errors with
GetLastError(), and confirming execution viaOnTradeTransactionor by verifying the position’s new volume. - Partial closing can be triggered by various conditions: fixed volume/percentage, profit levels, time elapsed, or combined criteria.
Best Practices for Partial Position Closing in MQL5
- Always validate volume: Ensure the volume to close is greater than or equal to the symbol’s minimum volume and a multiple of the volume step. Also, verify that the remaining volume will be valid (either zero or >= minimum volume).
- Handle errors: Use
GetLastError()ifPositionClosePartial()returnsfalseand log the error. - Confirm execution: Rely on
OnTradeTransactionto definitively know if and when the partial close occurred and what volume was executed. Update your EA’s internal state based on trade events, not just the return value of the function call. - Manage state: For multi-stage partial closes, track which stages have been completed for each position.
- Consider slippage: Use a reasonable
deviationvalue based on market volatility and the symbol’s characteristics. - Test thoroughly: Backtest and forward-test your partial closing logic extensively to ensure it behaves as expected under various market conditions.
Further Exploration and Resources
To deepen your understanding, explore the MQL5 Reference documentation for PositionClosePartial, OnTradeTransaction, and the various PositionGet* functions. Study example EAs that implement multi-tier profit taking. Understanding trade transaction handling is fundamental to mastering position management in MQL5.