How to Get a Position by Ticket in MQL5?

Effective position management is fundamental to successful algorithmic trading. In MQL5, interacting with open positions, modifying them, or extracting information is a routine task for Expert Advisors (EAs). While MQL5 provides various functions to manage positions, accessing a specific position quickly and reliably, particularly when you know its unique identifier or ‘ticket’, is a common requirement.

Overview of Positions and Tickets in MQL5

In MQL5, a position represents the total open exposure for a specific trading instrument (symbol) in one direction (buy or sell). Unlike MQL4 where a position was often synonymous with an open order, MQL5 clearly distinguishes between orders, deals, and positions.

A ticket in MetaTrader serves as a unique identifier for various trading operations and entities. You encounter tickets for pending orders, market orders, executed deals, and importantly, for open positions.

Why Accessing Positions by Ticket is Important

Knowing the ticket of a specific position allows for direct interaction with it. This is crucial in several scenarios:

  • Managing Individual Trades: If your EA tracks specific trades (perhaps opened under certain conditions), storing the position ticket is essential for later modification (e.g., adjusting stop loss/take profit) or closure.
  • Relating Orders and Positions: An order ticket leads to a deal ticket, which contributes to a position. While the direct link isn’t always a simple 1:1 ticket mapping after aggregation, knowing a position’s ticket allows you to select and inspect it.
  • Inter-EA Communication (Advanced): In complex setups involving multiple EAs or panels, passing position tickets can be a mechanism for one component to signal another to manage a specific open trade.
  • Error Handling and State Restoration: Storing relevant position tickets can help an EA recover its state after a terminal restart or unexpected event.

Understanding Trade Tickets and Position Identifiers

Navigating the various identifiers in MQL5’s trade operations requires clarity on their roles.

What is a Trade Ticket?

A trade ticket is a numerical ID assigned by the trading server to identify a specific trading operation or entity. This includes:

  • Order Tickets: Unique IDs for pending or market orders.
  • Deal Tickets: Unique IDs for executed trades (buy or sell). Each deal represents a specific transaction volume at a specific price.
  • Position Tickets: Unique IDs for open positions. A single position accumulates volume from multiple deals for the same symbol and direction.

Relationship Between Trade Tickets and Position IDs

When a market order is executed or a pending order is triggered, it results in one or more deals. These deals, in turn, contribute to the formation or modification of an open position for that symbol and direction. The position is assigned its own unique ticket.

While deals carry information about the order that generated them (DealGetInteger(DEAL_ORDER)), and deals contribute to a position, there isn’t a direct function to get the initial order ticket solely from the position ticket in real-time without examining historical deals or orders.

The PositionGetInteger(POSITION_IDENTIFIER) property provides a unique identifier for the position instance, which is its ticket.

Distinction between Order Tickets and Position Tickets

It’s vital not to confuse an order ticket with a position ticket. An order ticket identifies a request to buy or sell. A position ticket identifies the resulting open exposure after execution (deals) have occurred and potentially aggregated.

For example, you might place a buy order for 1 lot of EURUSD. If executed, this creates a deal. This deal then establishes or adds to a BUY position for EURUSD. The order had one ticket, the deal another, and the resulting position yet another.

Methods for Retrieving Positions by Ticket in MQL5

The most direct and recommended method for accessing an active position using its ticket in MQL5 is PositionSelectByTicket(). While HistorySelectByTicket() exists, it is used for selecting historical operations (deals or orders) related to a ticket, not for selecting current open positions.

Using PositionSelectByTicket(ticket) to Find a Current Position

This function attempts to select an open position by its ticket. If successful, subsequent PositionGet*() functions will operate on this selected position.

bool PositionSelectByTicket(ulong ticket);
  • ticket: The ticket of the position to select.
  • Returns true if a position with the specified ticket is found and selected, false otherwise. Use GetLastError() for details on failure.

This is the most efficient way when you already possess the position ticket.

Iterating Through Open Positions

An alternative, albeit less direct when you only need one position by ticket, is to iterate through all open positions using PositionsTotal() and PositionGetTicket().

int totalPositions = PositionsTotal();
for(int i = 0; i < totalPositions; i++)
{
   // Select position by index
   if(PositionSelectByIndex(i))
   {
      // Get the ticket of the selected position
      ulong currentTicket = PositionGetInteger(POSITION_IDENTIFIER);

      // Check if it matches the desired ticket
      if(currentTicket == targetTicket)
      {
         // Position found, now use PositionGet* functions
         // ... break loop or process ...
      }
   }
}

This approach is more suitable if you need to process all positions or find a position based on criteria other than just the ticket (e.g., symbol, magic number) and you don’t have the ticket beforehand. If you have the ticket, PositionSelectByTicket is preferred for performance.

HistorySelectByTicket() and its Scope

It’s important to clarify that HistorySelectByTicket(ticket) is designed to select historical deals and orders associated with a specific ticket (which could be an order ticket, a deal ticket, or even a position ticket if querying history related to that position’s lifecycle). It operates on trade history, not the currently open positions queue.

While you could potentially use it to find the historical deal(s) that opened a position, and then perhaps infer the position’s characteristics from history, it is not the function to use for selecting a current open position by its ticket for real-time management.

Code Examples: Implementing Position Retrieval by Ticket

Let’s look at practical examples using the PositionSelectByTicket function.

Example 1: Retrieving Position Properties by Ticket

This function attempts to find a position by its ticket and print some of its properties.

bool GetAndPrintPositionDetails(ulong positionTicket)
{
   // Attempt to select the position by its ticket
   if(PositionSelectByTicket(positionTicket))
   {
      // Position found and selected
      Print("Position found: Ticket=", positionTicket);
      Print("  Symbol: ", PositionGetString(POSITION_SYMBOL));
      Print("  Magic Number: ", PositionGetInteger(POSITION_MAGIC));
      Print("  Type: ", PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? "BUY" : "SELL");
      Print("  Volume: ", PositionGetDouble(POSITION_VOLUME));
      Print("  Open Price: ", PositionGetDouble(POSITION_PRICE_OPEN));
      Print("  Current Price: ", PositionGetDouble(POSITION_PRICE_CURRENT));
      Print("  Profit: ", PositionGetDouble(POSITION_PROFIT));
      return true;
   }
   else
   {
      // Position not found or selection failed
      Print("Failed to select position by ticket ", positionTicket, ". Error: ", GetLastError());
      return false;
   }
}

// Example usage (e.g., in OnTick or a script)
// ulong myPositionTicket = ...; // Get a position ticket from a successful OrderSendAsync result or elsewhere
// GetAndPrintPositionDetails(myPositionTicket);

Example 2: Handling Errors and Invalid Tickets

Robust code must handle cases where the ticket is invalid, the position doesn’t exist (e.g., already closed), or selection fails for other reasons.

// Attempt to close a position by ticket
bool ClosePositionByTicket(ulong positionTicket)
{
   // 1. Attempt to select the position
   if(PositionSelectByTicket(positionTicket))
   {
      // 2. Get necessary details before closing
      string symbol = PositionGetString(POSITION_SYMBOL);
      double volume = PositionGetDouble(POSITION_VOLUME);
      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      // 3. Prepare the trade request
      MqlTradeRequest request = {0};
      MqlTradeResult result = {0};

      request.action = TRADE_ACTION_CLOSE;
      request.position = positionTicket; // Identify position by ticket
      request.symbol = symbol;
      request.volume = volume;
      request.deviation = 10; // Max price deviation in points
      request.type = type;

      // 4. Send the close request
      Print("Attempting to close position ", positionTicket, " for ", symbol);
      if(OrderSend(request, result))
      {
         // OrderSend successful (doesn't guarantee execution, but request accepted)
         Print("OrderSend successful. Result code: ", result.retcode);
         // Need to handle TRADE_RETCODE_DONE for immediate closure confirmation
         if(result.retcode == TRADE_RETCODE_DONE)
         {
            Print("Position ", positionTicket, " closed successfully.");
            return true;
         }
         else
         {
             Print("OrderSend returned non-DONE code: ", result.retcode, ". Deal ticket: ", result.deal, ". Order ticket: ", result.order);
             // Potentially handle partial fills or requotes if needed
             return false; // Indicate closure wasn't immediately confirmed
         }
      }
      else
      {
         // OrderSend failed
         Print("OrderSend failed. Error code: ", GetLastError());
         return false;
      }
   }
   else
   {
      // Position selection failed (ticket not found or already closed)
      int error = GetLastError();
      if(error == ERR_TRADE_REQUEST_VALIDATE)
      {
          // Often indicates position already closed or invalid ticket
          Print("Position with ticket ", positionTicket, " not found or already closed. Error: ", error);
      }
      else
      {
          Print("Failed to select position ", positionTicket, ". Error: ", error);
      }
      return false;
   }
}

This example demonstrates how to select a position by ticket, retrieve its details, and then use the ticket in a TRADE_ACTION_CLOSE request. Crucially, it includes error checking for PositionSelectByTicket and OrderSend.

Example 3: Retrieving Associated Order Information (Conceptual)

As mentioned, getting the initial order ticket directly from a position ticket in real-time is not a single function call. You would typically retrieve the position ticket when the position is first opened (often via the OrderSendAsync result or by monitoring new deals/positions). The MqlTradeResult structure returned by OrderSend or OrderSendAsync for a market order includes the deal and order tickets.

To find the historical orders/deals that contributed to a position after it’s open, you’d need to query history. This involves HistorySelect to retrieve a range of historical operations and then iterating through deals (HistoryDealsTotal, HistoryDealSelect) and orders (HistoryOrdersTotal, HistoryOrderSelect), checking properties like DEAL_POSITION_ID or ORDER_POSITION_ID to match the position ticket.

Here’s a conceptual snippet showing how to get the deal ticket that closed a position using HistorySelectByPosition:

// This is conceptual - retrieving the OPENING order is more complex
// and requires iterating through history filtered by position ticket.

bool FindClosingDealForPosition(ulong positionTicket, ulong& closingDealTicket)
{
   // Select history related to this specific position ticket
   if(HistorySelectByPosition(positionTicket))
   {
      // Iterate through deals related to this position
      int totalDeals = HistoryDealsTotal();
      for(int i = 0; i < totalDeals; i++)
      {
         if(HistoryDealSelectByIndex(i))
         {
            // Check if this deal closed the position
            // This requires checking the deal type and potentially comparing volumes.
            // A simple check: find a DEAL_OUT deal for this position
            if(DealGetInteger(DEAL_ENTRY) == DEAL_ENTRY_OUT)
            {
               closingDealTicket = DealGetInteger(DEAL_TICKET);
               Print("Found closing deal ", closingDealTicket, " for position ", positionTicket);
               return true;
            }
         }
      }
   }
   else
   {
      Print("Failed to select history by position ticket ", positionTicket, ". Error: ", GetLastError());
   }
   return false;
}

// Example usage:
// ulong closedPositionTicket = ...; // Assume you have the ticket of a recently closed position
// ulong closingDeal = 0;
// if(FindClosingDealForPosition(closedPositionTicket, closingDeal))
// {
//    // Use closingDeal
// }

Note that retrieving the opening order/deal for a position established by aggregation of multiple deals is significantly more involved and outside the scope of simply ‘getting a position by ticket’, which focuses on accessing the current position object.

Best Practices and Common Pitfalls

Working with position tickets requires careful handling to avoid common issues.

Ensuring Ticket Validity and Existence

A position ticket is only valid as long as the position is open. If the position is closed (either manually, by your EA, or by TP/SL), its ticket becomes part of the trade history and PositionSelectByTicket will fail.

  • Best Practice: Always wrap calls to PositionSelectByTicket in an if check and handle the false case. Use GetLastError() to understand why it failed (e.g., ERR_TRADE_REQUEST_VALIDATE often means the position is gone).
  • Pitfall: Assuming a stored ticket from a previous state still corresponds to an open position without validation.

Efficiently Managing Position Data

If your EA needs to perform operations on multiple positions or frequently access position data, avoid excessive calls to PositionSelectByTicket within tight loops if iterating is more appropriate. However, for accessing a single position by its known ticket, PositionSelectByTicket is generally the most efficient method compared to iterating through all positions.

Handling Deleted or Closed Positions

As mentioned, PositionSelectByTicket will fail for closed positions. If you need to access information about a closed position using its ticket, you must use the history functions (HistorySelectByTicket, HistorySelectByPosition, HistorySelect) and then iterate through deals and orders, matching the relevant identifiers (DEAL_POSITION_ID, ORDER_POSITION_ID).

Performance Considerations when Iterating Through History

While not directly about getting a current position by ticket, the conceptual example of finding historical data highlights a performance point: accessing trade history can be resource-intensive, especially if the history is long. Be mindful of the time ranges selected with HistorySelect and avoid unnecessary iteration over large historical datasets.

By understanding the lifecycle of tickets and positions, and leveraging PositionSelectByTicket correctly, you can effectively manage specific trades within your MQL5 Expert Advisors.


Leave a Reply