Managing trading positions programmatically in MetaTrader 5 requires precise control over various order types. Pending orders, such as Buy Limit, Sell Limit, Buy Stop, Sell Stop, Buy Stop Limit, and Sell Stop Limit, are crucial tools for executing trades at specific price levels or conditions in the future.
Understanding Pending Orders: A Brief Overview
Pending orders are instructions to the broker to open a trade position when the market price reaches a predetermined level. Unlike market orders, which are executed immediately at the current price, pending orders wait for specific price conditions to be met before activation. They are essential for implementing strategies that rely on anticipated price movements, breakouts, or pullbacks without constant manual monitoring.
Importance of Closing Pending Orders
While placing pending orders is straightforward, effectively managing them, including cancellation (often referred to as closing or deleting), is equally vital. Unmanaged pending orders can trigger unexpectedly, leading to unintended positions or exposure. Strategies often require cancelling pending orders if market conditions change, if the target price is missed within a timeframe, or as part of a broader trade management exit strategy. Programmatic cancellation ensures that your automated system maintains control and adapts to dynamic market situations.
Article Objectives: What You Will Learn
This article aims to provide a comprehensive guide for MQL5 developers on how to programmatically close (delete) pending orders. We will cover the primary function used for this purpose, explore practical examples for deleting specific orders and iterating through all pending orders, discuss essential error handling, and touch upon advanced considerations for more robust solutions.
Methods for Closing Pending Orders in MQL5
In MQL5, managing trade operations, including the deletion of pending orders, is primarily handled through the MqlTradeRequest structure and the OrderSend() function, or more conveniently, through the CTrade class available in the Standard Library.
Using OrderDelete() Function
The fundamental operation to cancel a pending order involves the TRADE_ACTION_DELETE action within a trade request. Historically, MQL4 used OrderDelete(), but in MQL5, the trade operations are unified. The recommended modern approach in MQL5 leverages the CTrade class, which encapsulates the complexities of filling the MqlTradeRequest structure and calling OrderSend(). The CTrade class provides a method specifically for this task: OrderDelete().
This method requires the ticket number of the pending order you wish to cancel.
class CTrade { ... }; // Part of the MQL5 Standard Library
// Instantiate the CTrade class
CTrade trade;
// Assuming 'ticket' is the ticket number of the pending order to delete
long ticket = 12345; // Example ticket number
// Attempt to delete the order
if(trade.OrderDelete(ticket))
{
Print("Pending order ", ticket, " successfully deleted.");
}
else
{
Print("Failed to delete pending order ", ticket, ". Error: ", trade.ResultRetcode());
}
Closing by Ticket Number
The most direct way to close a single, known pending order is by using its unique ticket number. When a pending order is successfully placed, the broker assigns it a ticket number. This number is essential for any subsequent operations on that specific order, including modification or deletion.
To close a specific order by its ticket, you simply pass the ticket number to the CTrade::OrderDelete() method as shown in the previous section.
Closing All Pending Orders
Often, automated strategies require the cancellation of all active pending orders, perhaps at the end of a trading session, upon a significant market event, or before placing a new set of orders. To achieve this, you need to iterate through all orders currently present in the terminal’s order pool, identify the pending ones, and then attempt to delete each identified pending order.
The OrdersTotal() function returns the total number of orders (both pending and executed positions) in the current account. You can then loop from 0 to OrdersTotal() - 1 and use OrderGetTicket(index) or OrderGetInteger(index, ORDER_TICKET) to retrieve the ticket number for each order at the given index. Inside the loop, you retrieve the order type using OrderGetInteger(index, ORDER_TYPE) and check if it corresponds to a pending order type (ORDER_TYPE_BUY_LIMIT, ORDER_TYPE_SELL_LIMIT, etc.). If it’s a pending order, you call CTrade::OrderDelete() for that ticket.
Important Consideration: When deleting orders in a loop, especially from index 0 upwards, deleting an order changes the indices of subsequent orders. It is generally safer to iterate backwards from OrdersTotal() - 1 down to 0 when deleting items from a collection you are iterating over. Alternatively, you can collect the tickets of pending orders in a dynamic array first and then iterate through the collected tickets to perform deletions.
Practical Examples and Code Snippets
Let’s illustrate the concepts with practical code examples.
Example 1: Closing a Specific Pending Order by Ticket
This snippet demonstrates how to close a single pending order with a known ticket number. This would typically be used after placing an order and storing its ticket, or if your EA tracks pending orders by their tickets.
#include <Trade/Trade.mqh>
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
// Assume this is the ticket number of the pending order we want to delete
long ticketToDelete = 12345;
// Instantiate the CTrade class
CTrade trade;
Print("Attempting to delete pending order with ticket: ", ticketToDelete);
// Attempt to delete the order
if(trade.OrderDelete(ticketToDelete))
{
Print("Order ", ticketToDelete, " successfully sent for deletion.");
// Optional: Wait for the result or check the order status later
// For synchronous confirmation, you might poll OrderSelect(ticketToDelete)
// and check if it fails or if the order is no longer pending.
}
else
{
Print("Failed to send deletion request for order ", ticketToDelete, ". Error code: ", trade.ResultRetcode());
Print("Error description: ", trade.ResultRetcodeDescription());
}
}
Example 2: Closing All Pending Orders in a Loop
This example shows how to iterate through all orders and delete those that are pending for the current symbol.
#include <Trade/Trade.mqh>
#include <Arrays/ArrayLong.mqh>
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
CTrade trade;
CArrayLong pendingOrderTickets; // Array to store tickets of pending orders
// First, collect the tickets of all pending orders for the current symbol
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
// Select the order by index
if (OrderSelect(i, SELECT_BY_POS))
{
// Check if it's a pending order and for the current symbol
if (OrderGetInteger(ORDER_TYPE) >= ORDER_TYPE_BUY_LIMIT &&
OrderGetInteger(ORDER_TYPE) <= ORDER_TYPE_SELL_STOP_LIMIT &&
OrderGetString(ORDER_SYMBOL) == Symbol())
{
pendingOrderTickets.Add(OrderGetInteger(ORDER_TICKET));
}
}
else
{
Print("Error selecting order by index ", i, ": ", GetLastError());
}
}
// Now, iterate through the collected tickets and attempt to delete each one
Print("Found ", pendingOrderTickets.Total(), " pending orders for deletion.");
for (int i = 0; i < pendingOrderTickets.Total(); i++)
{
long currentTicket = pendingOrderTickets.At(i);
Print("Attempting to delete pending order: ", currentTicket);
if (trade.OrderDelete(currentTicket))
{
Print("Deletion request sent for order ", currentTicket);
// Note: Order may not be deleted immediately.
// Confirmation involves monitoring trade history or OrderSelect failure.
}
else
{
Print("Failed to send deletion request for order ", currentTicket, ". Error code: ", trade.ResultRetcode());
Print("Error description: ", trade.ResultRetcodeDescription());
}
}
if (pendingOrderTickets.Total() == 0)
{
Print("No pending orders found for deletion on ", Symbol());
}
}
Error Handling and Verification
Trade operations, including deleting orders, are not always successful. Network issues, invalid ticket numbers, or broker-side restrictions can cause failures. It is crucial to check the return value of CTrade::OrderDelete(). If it returns false, it means the request failed to be sent to the broker. The CTrade::ResultRetcode() method provides the specific error code from the trade server, and CTrade::ResultRetcodeDescription() gives a human-readable description.
However, a true return from OrderDelete() only indicates that the request was successfully sent to the trade server. It does not guarantee that the order was actually deleted. The final status is determined by the broker’s trade server response. For robust systems, especially in EAs, you might need to implement logic to verify the deletion asynchronously. This could involve:
- Checking trade history for the deleted order’s
TRADE_ACTION_DELETEevent. - Periodically attempting
OrderSelect(ticket)for the ticket you tried to delete. If it fails (OrderSelectreturnsfalse), the order no longer exists in the active order pool. - Listening for
OnTradeTransactionevents and examining theMqlTradeTransactionstructure for relevant operations (TRADE_TRANSACTION_DEAL_OR_ORDER_ADD,TRADE_TRANSACTION_ORDER_DELETE, etc.) related to the ticket.
Proper logging of trade operation results and subsequent verification is vital for debugging and ensuring the reliability of your automated strategy.
Advanced Techniques and Considerations
Beyond simple deletion by ticket, more complex scenarios require additional filtering and careful handling of the trade context.
Closing Orders Based on Specific Criteria (e.g., Symbol, Magic Number)
The example for closing all pending orders already demonstrated filtering by symbol. You can extend this to filter by other criteria stored with the order, most notably the Magic Number (ORDER_MAGIC). The Magic Number is a custom identifier assigned by the EA when placing an order, allowing the EA to distinguish its own orders from those placed manually or by other EAs.
To close only pending orders belonging to a specific EA instance (identified by its unique Magic Number), modify the filtering condition inside the loop:
// Inside the loop iterating through orders:
if (OrderSelect(i, SELECT_BY_POS))
{
// Check if it's a pending order AND has the correct Magic Number AND is for the current symbol
if (OrderGetInteger(ORDER_TYPE) >= ORDER_TYPE_BUY_LIMIT &&
OrderGetInteger(ORDER_TYPE) <= ORDER_TYPE_SELL_STOP_LIMIT &&
OrderGetInteger(ORDER_MAGIC) == YOUR_MAGIC_NUMBER && // <-- Add Magic Number check
OrderGetString(ORDER_SYMBOL) == Symbol())
{
pendingOrderTickets.Add(OrderGetInteger(ORDER_TICKET));
}
}
Replace YOUR_MAGIC_NUMBER with the actual magic number used by your EA. This prevents your EA from interfering with pending orders placed by other EAs or manually.
Asynchronous Order Deletion
Trade operations in MQL5 are inherently asynchronous. When you call CTrade::OrderDelete(), the request is sent, and the function returns (true or false) relatively quickly. The actual deletion on the broker’s server happens afterwards, and the result is communicated back via trade transactions. Relying solely on the immediate return of OrderDelete() is insufficient for guaranteed execution confirmation. For critical operations, implement an asynchronous verification mechanism using OnTradeTransaction or polling OrderSelect within a limited timeframe.
Handling Trade Context Errors
Occasionally, CTrade::OrderDelete() or OrderSend() with TRADE_ACTION_DELETE might fail with specific trade context errors (e.g., TRADE_RETCODE_BUSY, TRADE_RETCODE_MARKET_CLOSED, TRADE_RETCODE_TOO_MANY_REQUESTS).
TRADE_RETCODE_BUSY: The trade subsystem is currently processing another request. This often happens if you attempt multiple trade operations simultaneously. Implement retry logic with small delays or ensure only one trade operation is attempted at a time from your EA’s main logic flow.TRADE_RETCODE_MARKET_CLOSED: The market for the symbol is closed. You cannot perform trade operations when the market is closed.TRADE_RETCODE_TOO_MANY_REQUESTS: You are sending trade requests too frequently. Brokers often have limits on the rate of requests. Space out your trade operations.
Understanding and handling these error codes gracefully is essential for writing robust and reliable EAs.
Conclusion
Effectively managing pending orders is a fundamental aspect of developing sophisticated algorithmic trading strategies in MQL5. The ability to programmatically cancel pending orders ensures that your system remains responsive to market changes and maintains precise control over potential future trades.
Summary of Key Points
- Pending orders (Limit, Stop, Stop Limit) are future trade instructions.
- Cancelling them programmatically is crucial for strategy control and adaptation.
- In MQL5, the
CTrade::OrderDelete()method is the standard way to cancel a pending order using its ticket number. - To delete multiple or all pending orders, iterate through
OrdersTotal(), filter by type, symbol, and optionally Magic Number, and callOrderDelete()for each. - Always check the return value of
OrderDelete()andResultRetcode()for initial request success/failure. - For guaranteed confirmation, implement asynchronous verification using
OnTradeTransactionor by checking if the order is still present viaOrderSelect. - Handle trade context errors like
TRADE_RETCODE_BUSYfor robustness.
Best Practices for Managing Pending Orders
- Always use a unique Magic Number for your EA’s orders.
- Store the ticket numbers of pending orders placed by your EA if you need to refer to them individually.
- Iterate backwards or collect tickets into a separate array before deleting orders in a loop.
- Implement robust error handling and logging for all trade operations.
- Understand the asynchronous nature of trade operations and implement verification where necessary.
Further Learning Resources
- MQL5 Reference on
OrderSendandMqlTradeRequest - MQL5 Reference on
CTradeclass - MQL5 Reference on
OnTradeTransaction - MQL5 Reference on Order Information functions (
OrderSelect,OrderGetInteger, etc.)
Mastering these techniques provides the control necessary to build reliable and effective automated trading systems in MQL5.