How to Select Positions by Index in MQL5?

Introduction to Position Selection in MQL5

Understanding Position Management in MQL5

MQL5 marked a significant evolution from MQL4’s order-centric model, introducing distinct position management concepts, especially with netting and hedging account types. In a netting account (often the default for exchange-traded instruments and some Forex brokers), a trader can only hold one aggregate position per financial instrument. Conversely, hedging accounts permit multiple independent positions for the same symbol, offering a paradigm more akin to MQL4’s handling of multiple orders.

Regardless of the account’s position accounting system, MQL5 provides mechanisms to access and manage all currently open positions. The PositionsTotal() function serves as a fundamental starting point, returning the total count of open positions across all traded symbols. Each position is uniquely identified by a ticket (a long integer) and also possesses an index (an int) within the trading terminal’s internal list of open positions. This index is the key to sequentially accessing positions.

The Need for Selecting Positions by Index

While MQL5 provides PositionSelect(symbol) for directly targeting the net position of a specific symbol (most relevant in netting accounts), the capability to select positions by their index unlocks broader and more flexible control for advanced trading algorithms and utility scripts.

Selecting positions by index becomes crucial in scenarios such as:

  • Iterating through all open positions: When an Expert Advisor (EA) or script needs to systematically monitor, analyze, or modify every open position, regardless of its symbol, iterating by index is the standard approach.
  • Accessing positions in a specific sequence: Logic requiring interaction with the first position in the current list (index 0) or the last one (index PositionsTotal() - 1) often relies on index-based selection. This could be for implementing FIFO (First-In, First-Out) rules or for analyzing the most recent market entry.
  • Comprehensive Portfolio Management: EAs designed to manage diverse portfolios across multiple instruments benefit from indexed iteration to apply logic consistently to each open trade.
  • Developing Utility Scripts: Scripts that generate reports on open trades, perform bulk modifications, or gather statistics frequently use indexed access to process all relevant positions.

Methods for Selecting Positions by Index

Using PositionGetTicket() with Index

The primary MQL5 function for retrieving a position based on its sequential number in the list of open positions is PositionGetTicket(index). A thorough understanding of its behavior is essential for robust position management.

  • Function Signature and Purpose:
    long PositionGetTicket(int index)
    This function takes a zero-based integer index as input. It attempts to retrieve the unique ticket (a long value) of the open position located at that specific index in the terminal’s current list of all open positions.

  • Return Value:

    • If successful, PositionGetTicket() returns the position ticket, which is a positive long integer.
    • If the provided index is invalid (e.g., less than 0 or greater than or equal to PositionsTotal()) or if an internal error prevents retrieval, the function returns 0. It is imperative to check this return value before proceeding.
  • Important Note on Index-Based Order:
    The MQL5 documentation explicitly states: “The order of positions in the list is not guaranteed and can change.” While in practice, positions might often appear in the order they were opened, relying on this assumption for critical trading logic is strongly discouraged. The index merely refers to a position’s current ordinal placement in an internally managed list, which can be subject to change, particularly when positions are opened or closed.

Iterating Through Positions with a Loop

A common and effective pattern for processing all open positions involves combining PositionsTotal() with PositionGetTicket() inside a for loop. This allows you to systematically access each position’s ticket.

void IterateAndProcessAllPositions()
{
    int totalOpenPositions = PositionsTotal();
    PrintFormat("Total open positions: %d", totalOpenPositions);

    for(int i = 0; i < totalOpenPositions; i++)
    {
        long positionTicket = PositionGetTicket(i);
        if(positionTicket > 0)
        {
            // Successfully retrieved the ticket, now select the position to access its properties
            if(PositionSelectByTicket(positionTicket)) // Or PositionSelect(positionTicket) in newer builds
            {
                string symbol = PositionGetString(POSITION_SYMBOL);
                double volume = PositionGetDouble(POSITION_VOLUME);
                long magic = PositionGetInteger(POSITION_MAGIC);
                datetime timeOpen = (datetime)PositionGetInteger(POSITION_TIME);

                PrintFormat("Index: %d, Ticket: %I64d, Symbol: %s, Volume: %.2f, Magic: %I64d, Open Time: %s",
                            i, positionTicket, symbol, volume, magic, TimeToString(timeOpen, TIME_DATE|TIME_SECONDS));

                // Further processing of the selected position can be done here
            }
            else
            {
                PrintFormat("Failed to select position with ticket %I64d obtained from index %d. Error: %d", 
                            positionTicket, i, GetLastError());
            }
        }
        else
        {
            // This might happen if the list changed during iteration, though less common with forward iteration without modification
            PrintFormat("Failed to get ticket for position at index %d. Error: %d", i, GetLastError());
        }
    }
}

In this example, PositionSelectByTicket(ticket) is used to make the selected position active, allowing subsequent calls to PositionGetSymbol(), PositionGetDouble(), etc., to retrieve its details. Modern MQL5 builds often allow PositionSelect(ticket) to achieve the same.

Practical Examples of Selecting Positions by Index

Selecting the Position at Index 0

This example demonstrates how to retrieve the ticket for the position currently at index 0 in the list. This is often, but not guaranteed to be, the oldest among the currently open positions.

void SelectPositionAtIndexZero()
{
    if(PositionsTotal() > 0)
    {
        long ticket = PositionGetTicket(0); // Attempt to get ticket for the position at index 0
        if(ticket > 0)
        {
            if(PositionSelectByTicket(ticket))
            {
                PrintFormat("Selected position at index 0. Symbol: %s, Ticket: %I64d, Volume: %.2f",
                            PositionGetString(POSITION_SYMBOL),
                            PositionGetInteger(POSITION_TICKET), // POSITION_TICKET is equivalent to the ticket itself
                            PositionGetDouble(POSITION_VOLUME));
                // Implement further logic for this position
            }
            else
            {
                PrintFormat("Failed to select position with ticket %I64d (from index 0). Error: %d", ticket, GetLastError());
            }
        }
        else
        {
            PrintFormat("Failed to get ticket for position at index 0. Error: %d", GetLastError());
        }
    }
    else
    {
        Print("No open positions available to select from.");
    }
}

Selecting the Position at the Last Index

To access the position at the end of the current list (often, but not guaranteed to be, the most recently opened one), you would use index PositionsTotal() - 1.

void SelectPositionAtLastIndex()
{
    int totalPositions = PositionsTotal();
    if(totalPositions > 0)
    {
        int lastIndex = totalPositions - 1;
        long ticket = PositionGetTicket(lastIndex); // Attempt to get ticket for the position at the last index

        if(ticket > 0)
        {
            if(PositionSelectByTicket(ticket))
            {
                PrintFormat("Selected position at last index (%d). Symbol: %s, Ticket: %I64d, Volume: %.2f",
                            lastIndex,
                            PositionGetString(POSITION_SYMBOL),
                            PositionGetInteger(POSITION_TICKET),
                            PositionGetDouble(POSITION_VOLUME));
                // Implement further logic for this position
            }
            else
            {
                PrintFormat("Failed to select position with ticket %I64d (from index %d). Error: %d", ticket, lastIndex, GetLastError());
            }
        }
        else
        {
            PrintFormat("Failed to get ticket for position at index %d. Error: %d", lastIndex, GetLastError());
        }
    }
    else
    {
        Print("No open positions available to select from.");
    }
}

Selecting a Specific Position Based on a Chosen Index

This example shows how to target a position at an arbitrary, valid index, for instance, the second position (index 1) if it exists.

void SelectPositionAtSpecificIndex(int targetIndex)
{
    int totalPositions = PositionsTotal();
    if(targetIndex >= 0 && targetIndex < totalPositions)
    {
        long ticket = PositionGetTicket(targetIndex);
        if(ticket > 0)
        {
            if(PositionSelectByTicket(ticket))
            {
                PrintFormat("Selected position at index %d. Symbol: %s, Ticket: %I64d, Profit: %.2f",
                            targetIndex,
                            PositionGetString(POSITION_SYMBOL),
                            PositionGetInteger(POSITION_TICKET),
                            PositionGetDouble(POSITION_PROFIT));
                // Implement further logic for this specific position
            }
            else
            {
                PrintFormat("Failed to select position with ticket %I64d (from index %d). Error: %d", ticket, targetIndex, GetLastError());
            }
        }
        else
        {
            PrintFormat("Failed to get ticket for position at index %d. Error: %d", targetIndex, GetLastError());
        }
    }
    else
    {
        PrintFormat("Invalid target index %d. Total positions: %d. Index must be between 0 and %d.", 
                    targetIndex, totalPositions, (totalPositions > 0 ? totalPositions - 1 : 0));
    }
}

Considerations and Limitations

Working with position indices requires careful consideration of the dynamic nature of the open positions list and robust error handling.

Index-Based Selection and Position Deletion/Addition

A critical aspect to remember is that the list of open positions is dynamic. When a position is closed, or a new one is opened, the indices of the remaining or existing positions can shift. This has significant implications for loops that modify positions.

If you iterate through positions and perform actions that might change the total number of positions (e.g., closing a position), iterating forwards (from index 0 to PositionsTotal() - 1) can lead to skipping positions or attempting to access an index that has become invalid. A more robust approach for such scenarios is to iterate backwards:

#include <Trade\Trade.mqh> // Required for CTrade
CTrade trade; // Trading object

void CloseProfitableEURUSDPositionsSafely()
{
    // Iterate backwards to safely handle position closing
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        long ticket = PositionGetTicket(i);
        if(ticket > 0)
        {
            if(PositionSelectByTicket(ticket))
            {
                if(PositionGetString(POSITION_SYMBOL) == "EURUSD" && PositionGetDouble(POSITION_PROFIT) > 0.0)
                {
                    PrintFormat("Attempting to close profitable EURUSD position #%I64d at index %d", ticket, i);
                    if(!trade.PositionClose(ticket))
                    {
                        PrintFormat("Failed to close position #%I64d. Error: %d", ticket, GetLastError());
                    }
                    else
                    {
                        PrintFormat("Position #%I64d closed successfully.", ticket);
                        // After closing, PositionsTotal() will decrease, and indices might shift.
                        // Iterating backwards ensures the next 'i' is still valid relative to the new list.
                    }
                }
            }
        }
    }
}

By iterating from PositionsTotal() - 1 down to 0, if a position is closed, the removal of that item from the list does not affect the indices of positions that have not yet been processed in the loop.

Handling Errors and Invalid Indices

Robust error handling is paramount when working with position indices.

  • Check PositionGetTicket() Return Value: Always verify that PositionGetTicket(index) returns a value greater than 0. A return value of 0 signifies that no ticket was retrieved for the given index, either because the index is out of bounds or due to an internal terminal error.
  • Validate Index Range: Before calling PositionGetTicket(index), ensure that the index is within the valid range: 0 <= index < PositionsTotal().
  • Use GetLastError(): Immediately after a failed call to PositionGetTicket() or PositionSelectByTicket(), invoke GetLastError() to obtain a specific error code. This can provide valuable diagnostic information. Common errors include ERR_TRADE_NO_POSITIONS_FOUND (if the index was invalid or list changed) or other trade-related error codes.
  • Check PositionSelectByTicket() Success: Similarly, after obtaining a valid ticket, confirm that PositionSelectByTicket(ticket) returns true before attempting to retrieve position properties. A false return indicates that the position, even if the ticket was valid moments before, could not be selected (e.g., it might have been closed by another part of the program or manually).

Conclusion

Selecting positions by index in MQL5 is a powerful technique for managing and analyzing open trades, especially when dealing with multiple positions or requiring sequential access. By understanding the core functions and potential pitfalls, developers can create more sophisticated and reliable trading algorithms.

Best Practices for Position Selection by Index

To ensure robust and error-free position management when using index-based selection:

  • Always Fetch Current Count: Before initiating a loop or attempting to access a specific index, retrieve the current number of open positions using PositionsTotal().
  • Validate Indices: Explicitly check if the target index is within the valid range (0 to PositionsTotal() - 1) before calling PositionGetTicket().
  • Verify Ticket Retrieval: Ensure PositionGetTicket() returns a non-zero ticket before proceeding to select the position.
  • Confirm Position Selection: Check the return value of PositionSelectByTicket() (or PositionSelect()) before accessing position properties.
  • Iterate Backwards When Modifying: If your loop involves operations that change the number of open positions (e.g., closing positions), iterate backwards (from PositionsTotal() - 1 down to 0) to prevent issues caused by re-indexing.
  • Do Not Assume Order: Remember that the order of positions by index is not strictly guaranteed by open time or any other specific criterion. If order-dependent logic is required (e.g., FIFO), retrieve the relevant property (like POSITION_TIME_OPEN or POSITION_TIME_MSC_OPEN) after selecting the position and sort/filter based on that data if necessary.

Further Exploration of MQL5 Position Functions

Once a position has been successfully selected using its ticket (obtained via PositionGetTicket() and then used with PositionSelectByTicket() or PositionSelect()), MQL5 provides a comprehensive suite of functions to retrieve detailed information about that position. These include:

  • Symbol Information: PositionGetSymbol()
  • Integer Properties: PositionGetInteger() (e.g., POSITION_TICKET, POSITION_MAGIC, POSITION_TYPE, POSITION_TIME, POSITION_TIME_MSC, POSITION_TIME_UPDATE, POSITION_TIME_UPDATE_MSC)
  • Floating-Point Properties: PositionGetDouble() (e.g., POSITION_VOLUME, POSITION_PRICE_OPEN, POSITION_SL, POSITION_TP, POSITION_PRICE_CURRENT, POSITION_PROFIT, POSITION_SWAP, POSITION_COMMISSION)
  • String Properties: PositionGetString() (e.g., POSITION_COMMENT, POSITION_EXTERNAL_ID)

Mastering these PositionGet...() functions in conjunction with indexed selection will significantly enhance your ability to develop sophisticated position management logic within your MQL5 Expert Advisors, scripts, and indicators.


Leave a Reply