Trading automation relies heavily on the ability of an Expert Advisor (EA) to understand the current state of the market and the trading account. A fundamental part of this understanding is knowing which trading positions are currently open. Unlike MQL4, which primarily dealt with ‘orders’ encompassing both pending and open market positions, MQL5 introduces a more structured approach by distinguishing between ‘orders’ (requests to perform a transaction), ‘deals’ (records of transactions), and ‘positions’ (the aggregated result of deals for a specific instrument).
This article delves into the specifics of accessing and managing open positions within the MQL5 environment, focusing on the essential functions and best practices for developers targeting middle to senior proficiency levels.
Introduction to Working with Open Positions in MQL5
In MQL5, a ‘position’ represents the net result of all deals for a particular financial instrument on an account. For instance, if you buy 1 lot of EURUSD and later buy another 0.5 lots, you will have a single position of 1.5 lots for EURUSD. This consolidation simplifies tracking overall exposure per symbol. Accessing this position data is crucial for EAs to make informed decisions, manage risk, calculate profit/loss, and implement complex trading strategies.
Understanding Position Types and Properties in MQL5
A position in MQL5 is uniquely identified by a ticket number. It holds various properties providing comprehensive details about the open trade. Key properties include:
- Ticket: Unique identifier of the position.
- Symbol: The financial instrument the position is on.
- Type: Direction of the position (BUY or SELL).
- Volume: The total volume of the position.
- Open Time: Time the position was opened.
- Open Price: The average weighted price at which the position was opened.
- Current Price: The current market price.
- Stop Loss: The Stop Loss level.
- Take Profit: The Take Profit level.
- Magic Number: A user-defined identifier, essential for an EA to track its own positions.
- Comment: A text comment associated with the position.
- Profit: The current floating profit/loss of the position.
- Swap: The accrued swap for the position.
These properties are accessed using dedicated PositionGet* functions, which we will explore shortly.
Why Accessing Open Positions is Crucial for Trading Robots
For automated trading systems, knowledge of current open positions is not merely informational; it’s foundational for execution logic. EAs need this data to:
- Manage Risk: Implement trailing stops, partial closures, or hedge positions based on current P/L or market movement.
- Determine Entry/Exit: Avoid opening redundant positions or decide when to close a trade based on strategy conditions interacting with existing exposure.
- Calculate Performance: Track individual position performance or aggregate account-level metrics.
- Handle Specific Strategies: Manage grids, hedging, or correlation-based strategies that depend on knowing existing positions across multiple symbols.
- Recover from Downtime: Re-sync the EA’s internal state with the broker’s server state after a terminal restart or connection issue.
Without reliable access to open position data, an EA operates blindly, potentially leading to incorrect trade decisions or unintended risk exposure.
Functions for Retrieving Open Positions
MQL5 provides a set of functions specifically designed for iterating through and retrieving details about open positions on the current account.
Using PositionsTotal() to Determine the Number of Open Positions
The first step in accessing open positions is knowing how many there are. The PositionsTotal() function provides this count:
int total_positions = PositionsTotal();
if (total_positions > 0)
{
// Proceed to iterate through positions
}
else
{
// No open positions
}
This function returns the total number of open positions available for the account the MQL5 program is attached to. If the return value is 0, there are no open positions, and any attempt to access position details by index will fail.
Iterating Through Open Positions with PositionGetTicket()
To access the details of each position, you typically iterate through the position pool using a loop from 0 to PositionsTotal() - 1. Inside the loop, you select the current position by its index using PositionGetTicket():
int total_positions = PositionsTotal();
for (int i = 0; i < total_positions; i++)
{
// Select the position by index
ulong position_ticket = PositionGetTicket(i);
// Check if selection was successful
if (position_ticket > 0)
{
// Now you can access properties using PositionGet* functions
// ...
}
else
{
// Handle error: Position selection failed
Print("Error selecting position by index ", i, ": ", GetLastError());
}
}
PositionGetTicket(index) selects a position by its index in the position list and returns its ticket number. This selection is crucial because subsequent PositionGet* calls retrieve properties of the currently selected position.
Accessing Position Properties with PositionGetInteger(), PositionGetDouble(), and PositionGetString()
Once a position is selected (either by index using PositionGetTicket() or directly by ticket using PositionSelect()), you can retrieve its specific properties using type-specific functions:
PositionGetInteger(property_id): Retrieves integer properties (e.g.,POSITION_TYPE,POSITION_MAGIC). Returnslong.PositionGetDouble(property_id): Retrieves double properties (e.g.,POSITION_VOLUME,POSITION_PRICE_OPEN,POSITION_PROFIT). Returnsdouble.PositionGetString(property_id): Retrieves string properties (e.g.,POSITION_SYMBOL,POSITION_COMMENT). Returnsstring.
These functions take a property_id (an enumeration value like POSITION_TYPE, POSITION_SYMBOL, etc.) and return the corresponding value for the selected position. It is essential to use the correct function (GetInteger, GetDouble, GetString) for the specific property ID you are querying.
Important Note: After selecting a position, its properties are available until another position is selected or the MQL5 program finishes execution.
Practical Examples of Getting Open Positions
Let’s illustrate the usage of these functions with practical code examples.
Example 1: Retrieving and Printing All Open Position Details
This example iterates through all open positions and prints key details for each to the terminal’s Experts tab.
void PrintAllOpenPositions()
{
int total_positions = PositionsTotal();
Print("Total open positions: ", total_positions);
for (int i = 0; i < total_positions; i++)
{
// Select position by index
ulong ticket = PositionGetTicket(i);
if (ticket > 0)
{
// Retrieve properties for the selected position
string symbol = PositionGetString(POSITION_SYMBOL);
long type = PositionGetInteger(POSITION_TYPE);
double volume = PositionGetDouble(POSITION_VOLUME);
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
double profit = PositionGetDouble(POSITION_PROFIT);
long magic = PositionGetInteger(POSITION_MAGIC);
string type_str = (type == POSITION_TYPE_BUY) ? "BUY" : "SELL";
PrintFormat("Ticket: %lu, Symbol: %s, Type: %s, Volume: %G, Open Price: %G, Current Price: %G, Profit: %.2f, Magic: %lu",
ticket, symbol, type_str, volume, open_price, current_price, profit, magic);
}
else
{
Print("Failed to select position by index ", i, ", Error: ", GetLastError());
}
}
}
// Example usage in OnInit or OnTick
// PrintAllOpenPositions();
Example 2: Calculating Total Profit/Loss of All Open Positions
This code snippet demonstrates how to sum the floating profit of all open positions on the account.
double CalculateTotalOpenProfit()
{
double total_profit = 0.0;
int total_positions = PositionsTotal();
for (int i = 0; i < total_positions; i++)
{
// Select position by index
ulong ticket = PositionGetTicket(i);
if (ticket > 0)
{
// Add position's profit to the total
total_profit += PositionGetDouble(POSITION_PROFIT);
}
else
{
Print("Failed to select position by index ", i, ", Error: ", GetLastError());
}
}
return total_profit;
}
// Example usage:
// double current_total_profit = CalculateTotalOpenProfit();
// Print("Total floating profit: ", current_total_profit);
Example 3: Filtering Open Positions Based on Symbol or Magic Number
Often, an EA only cares about positions related to its own symbol or opened with a specific magic number. This example shows how to filter positions during iteration.
void ManageMyPositions(long my_magic_number)
{
int total_positions = PositionsTotal();
for (int i = 0; i < total_positions; i++)
{
// Select position by index
ulong ticket = PositionGetTicket(i);
if (ticket > 0)
{
// Retrieve filtering criteria
string symbol = PositionGetString(POSITION_SYMBOL);
long magic = PositionGetInteger(POSITION_MAGIC);
// Check if the position matches our criteria
if (symbol == _Symbol && magic == my_magic_number)
{
// This position belongs to this EA and symbol
// You can now access other properties and perform actions
double profit = PositionGetDouble(POSITION_PROFIT);
PrintFormat("Found my position: Ticket=%lu, Symbol=%s, Magic=%lu, Profit=%.2f", ticket, symbol, magic, profit);
// Example action: If profit is > $100, close partially
// if (profit > 100.0)
// {
// // Implement partial closure logic here (using PositionClosePartial)
// }
}
}
else
{
Print("Failed to select position by index ", i, ", Error: ", GetLastError());
}
}
}
// Example usage (assuming MY_EA_MAGIC is defined):
// ManageMyPositions(MY_EA_MAGIC);
Filtering is a common pattern to ensure an EA only interacts with trades it is responsible for, preventing interference with manual trades or positions opened by other EAs.
Handling Errors and Edge Cases
Robust EAs anticipate potential issues when accessing market or account data.
Checking for Errors When Accessing Position Data
Functions like PositionGetTicket() and the PositionGet* functions can fail. After calling them, it’s good practice to check for errors using GetLastError().
ulong ticket = PositionGetTicket(index);
if (ticket == 0) // PositionGetTicket returns 0 on failure
{
Print("Failed to get position ticket for index ", index, ", Error: ", GetLastError());
// Handle the error, maybe break the loop or skip this index
}
// After selecting or getting ticket, check PositionGet* calls if necessary
// Note: Most PositionGet* functions return a default value (0 for numbers, "" for strings) on failure
// and set GetLastError(). Explicit checks might be needed for critical properties.
double volume = PositionGetDouble(POSITION_VOLUME);
if (GetLastError() != 0)
{
Print("Failed to get volume for ticket ", ticket, ", Error: ", GetLastError());
// Handle the error
}
Checking GetLastError() after critical calls ensures you are aware if data retrieval was successful.
Dealing with the Absence of Open Positions
As shown before, always check PositionsTotal() before attempting to iterate. If it’s zero, your iteration loop won’t run, which is the desired behavior. However, ensure your EA logic correctly handles the state where no positions exist, as this might be a trigger for opening new trades.
Considerations for Multi-Symbol and Multi-Account Trading
When developing EAs for multi-symbol portfolios or potentially operating across different accounts (less common for retail but relevant for prop firms or fund managers), remember:
PositionsTotal()and thePositionGet*functions operate only on the current account the MQL5 program is running on.- Filtering by
POSITION_SYMBOL(using_Symbolvariable) is crucial for single-symbol EAs attached to multiple charts. - Using
POSITION_MAGICis paramount for distinguishing positions opened by different EAs or even different strategies within the same EA on the same account and symbol.
Careful filtering based on Magic Number and Symbol is essential to avoid unintended interactions between different trading components.
Best Practices and Optimization
Working efficiently with position data is important, especially in high-frequency strategies.
Efficiently Storing and Caching Position Data
Iterating through all positions using PositionsTotal() and PositionGetTicket() on every OnTick can be inefficient if the number of positions is large. Consider caching position data in arrays or classes, updating this cache only when necessary (e.g., on OnTrade or OnTimer events, or periodically). You can detect changes by comparing the current PositionsTotal() with the previous count or by monitoring trade events.
Avoiding Common Pitfalls When Working with Open Positions
- Assuming Index Stability: The index of a position (
0toPositionsTotal()-1) is not stable. It can change as positions are opened or closed. Always select a position by its index within the current loop iteration usingPositionGetTicket(i)before accessing its properties. - Incorrect Property ID: Using
PositionGetIntegerfor a string property orPositionGetDoublefor an integer property will result in errors or incorrect values. - Ignoring Magic Number: Failing to filter by magic number is a common cause of EAs interfering with each other or with manual trades.
- Not Checking for Errors: Skipping
GetLastError()checks can hide underlying issues with data retrieval.
Performance Considerations for High-Frequency Trading
For HFT EAs that execute complex logic on every tick, minimizing the overhead of accessing position data is vital. Caching is highly recommended. Alternatively, track your EA’s positions internally using containers (arrays, lists) populated and updated based on trade events (OnTrade). This allows O(1) or O(log n) access to your specific positions without iterating through the entire position pool repeatedly on fast-moving markets.
By understanding the structure of positions in MQL5, utilizing the correct retrieval functions, implementing robust error handling, and applying efficient coding practices, you can build sophisticated and reliable trading robots.