Effective position management is a cornerstone of successful algorithmic trading. In MQL5, understanding how to accurately check for open positions is fundamental for Expert Advisors (EAs) to make informed decisions, manage risk, and execute trading logic correctly.
Understanding Positions in MQL5
In MetaTrader 5, a “position” represents the aggregate state of all trades for a specific financial instrument. This is a key distinction from MQL4, where individual orders remained distinct. MQL5’s approach simplifies position accounting, especially in netting accounts where multiple trades in the same direction are consolidated into a single position. For hedging accounts, multiple positions (buy and sell) can exist simultaneously for the same symbol.
Each position has unique characteristics, such as the symbol, volume, direction (buy or sell), open price, and an optional magic number. Accurately querying these properties is vital for an EA’s operational logic.
Importance of Checking Open Positions
EAs often need to verify the existence and state of open positions before taking further actions. Common scenarios include:
- Avoiding Duplicate Trades: Ensuring a new trade is not opened if a similar position already exists.
- Risk Management: Modifying stop-loss or take-profit levels, or calculating overall exposure.
- Trade Modification: Implementing trailing stops or partial closes.
- Closing Conditions: Determining if a position should be closed based on market conditions or EA logic.
- Signal Confirmation: Using the existence of a position as part of a complex trading signal.
Without reliable position checking, an EA can behave erratically, leading to unintended trades, increased risk, and poor performance.
Methods for Checking Open Positions
MQL5 provides a robust set of functions for querying open positions. The primary approach involves iterating through the list of open positions and examining their properties.
Using PositionsTotal() to Determine the Number of Open Positions
The simplest way to get a quick overview is using PositionsTotal(). This function returns the total number of open positions for the current trading account.
void OnTick()
{
int totalPositions = PositionsTotal();
Print("Total open positions: ", totalPositions);
if (totalPositions == 0)
{
// Logic for when no positions are open
}
}
While PositionsTotal() indicates if any positions are open, it doesn’t provide details about individual positions. For that, iteration is required.
Iterating Through Positions with PositionGetTicket() and PositionSelect()
To access individual position details, you must loop through them. The typical process involves:
- Getting the total number of positions using
PositionsTotal(). - Looping from
0toPositionsTotal() - 1. - In each iteration, retrieve the ticket of a position at a specific index using
PositionGetTicket(index). - Select the position using its ticket with
PositionSelect(ticket). This function makes the selected position’s data available for subsequent property retrieval functions.
void CheckAllPositions()
{
for (int i = PositionsTotal() - 1; i >= 0; i--) // Loop backwards if planning to close positions within the loop
{
ulong ticket = PositionGetTicket(i);
if (ticket == 0) // Should not happen in normal circumstances with valid index
continue;
if (PositionSelect(ticket)) // Select the position by its ticket
{
// Position is selected, now retrieve its properties
string symbol = PositionGetString(POSITION_SYMBOL);
long type = PositionGetInteger(POSITION_TYPE);
double volume = PositionGetDouble(POSITION_VOLUME);
PrintFormat("Position #%d: Ticket %d, Symbol %s, Type %s, Volume %.2f",
i,
ticket,
symbol,
(type == POSITION_TYPE_BUY ? "BUY" : "SELL"),
volume);
}
else
{
PrintFormat("Error selecting position with ticket %d. Error code: %d", ticket, GetLastError());
}
}
}
Checking Position Properties with PositionGetInteger(), PositionGetString(), and PositionGetDouble()
Once a position is successfully selected using PositionSelect(), you can retrieve its specific attributes using a family of PositionGet...() functions:
PositionGetInteger(property_id): Retrieves integer properties. Commonproperty_idvalues include:POSITION_TICKET: The unique ticket number of the position.POSITION_TYPE: The type of position (POSITION_TYPE_BUYorPOSITION_TYPE_SELL).POSITION_MAGIC: The magic number of the EA that opened the position.POSITION_REASON: Reason for position appearance.POSITION_TIME_MSC: Position opening time in milliseconds since 01.01.1970.
PositionGetString(property_id): Retrieves string properties. Commonproperty_idvalues include:POSITION_SYMBOL: The symbol of the instrument.POSITION_COMMENT: The comment associated with the position.
PositionGetDouble(property_id): Retrieves double-precision floating-point properties. Commonproperty_idvalues include:POSITION_VOLUME: The volume of the position.POSITION_PRICE_OPEN: The open price of the position.POSITION_SL: The stop-loss level.POSITION_TP: The take-profit level.POSITION_PROFIT: The current profit of the position.POSITION_SWAP: The accumulated swap for the position.
Always ensure PositionSelect() returns true before calling these functions to avoid errors.
Practical Examples: Checking Specific Position Criteria
Here are common scenarios where specific position checks are necessary.
Checking for a Position on a Specific Symbol
An EA often needs to know if it already has a position open for the symbol it’s trading or a specific symbol.
bool IsPositionOpenForSymbol(string targetSymbol)
{
for (int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if (PositionSelect(ticket))
{
if (PositionGetString(POSITION_SYMBOL) == targetSymbol)
{
return true; // Position found for the target symbol
}
}
}
return false; // No position found for the target symbol
}
void OnTick()
{
string currentSymbol = Symbol();
if (IsPositionOpenForSymbol(currentSymbol))
{
PrintFormat("A position is currently open for %s.", currentSymbol);
}
else
{
PrintFormat("No position open for %s.", currentSymbol);
// Potentially open a new position
}
}
Verifying Position Type (Buy or Sell)
You might need to check if an open position for a symbol is a buy or a sell, perhaps to implement logic that only allows one direction at a time or to manage hedging strategies.
// Returns -1 if no position, POSITION_TYPE_BUY if buy, POSITION_TYPE_SELL if sell
long GetPositionTypeForSymbol(string targetSymbol)
{
for (int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if (PositionSelect(ticket))
{
if (PositionGetString(POSITION_SYMBOL) == targetSymbol)
{
return PositionGetInteger(POSITION_TYPE);
}
}
}
return -1; // Indicates no position found for the symbol or an error
}
void OnTick()
{
string currentSymbol = Symbol();
long positionType = GetPositionTypeForSymbol(currentSymbol);
if (positionType == POSITION_TYPE_BUY)
{
PrintFormat("A BUY position is open for %s.", currentSymbol);
}
else if (positionType == POSITION_TYPE_SELL)
{
PrintFormat("A SELL position is open for %s.", currentSymbol);
}
else
{
PrintFormat("No position or an issue checking position for %s.", currentSymbol);
}
}
Checking Position Magic Number
The magic number is crucial for distinguishing positions opened by different EAs or different instances/strategies of the same EA.
bool IsPositionOpenWithMagic(string targetSymbol, int magicNumber)
{
for (int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if (PositionSelect(ticket))
{
if (PositionGetString(POSITION_SYMBOL) == targetSymbol &&
PositionGetInteger(POSITION_MAGIC) == magicNumber)
{
return true;
}
}
}
return false;
}
// In your EA's global scope or OnInit
input int MyMagicNumber = 12345;
void OnTick()
{
string currentSymbol = Symbol();
if (IsPositionOpenWithMagic(currentSymbol, MyMagicNumber))
{
PrintFormat("EA (Magic: %d) has a position open for %s.", MyMagicNumber, currentSymbol);
}
else
{
// Logic to open a new position with MyMagicNumber
}
}
Advanced Techniques and Considerations
Beyond basic checks, robust EAs incorporate advanced techniques for reliability and performance.
Handling Errors and Exceptions
MQL5 functions related to position management can fail (e.g., PositionSelect() if a ticket is invalid). Always check the return values of these functions. If a function returns false (or an equivalent error indicator), use GetLastError() to retrieve the specific error code and handle it appropriately. This might involve logging the error, retrying the operation, or taking corrective action.
ulong ticket = PositionGetTicket(i);
if (ticket > 0) // Basic check, though PositionGetTicket with valid index should return valid ticket
{
if (!PositionSelect(ticket))
{
PrintFormat("Failed to select position with ticket %d. Error: %d", ticket, GetLastError());
// Handle error: continue to next position, log, or halt operations
continue;
}
// ... proceed with getting properties
}
Optimizing Position Checking for Performance
While MQL5’s position functions are generally efficient, in highly active EAs or those managing numerous symbols, frequent iteration through all positions can consume resources.
- Minimize Checks: Only check positions when necessary, typically upon a new tick, a timer event, or a trade event (
OnTrade(),OnTradeTransaction()). - Specific Checks: If you only care about a position for the current symbol and magic number, make your loop exit as soon as that specific position is found and verified.
- Event-Driven Logic: Utilize
OnTradeTransactionto react to specific changes in positions (e.g., position opening, closing, modification) rather than polling constantly.
For most EAs, the standard iteration methods are perfectly adequate. Premature optimization can lead to overly complex code. Profile your EA if you suspect performance bottlenecks related to position checking.
Using Custom Functions for Position Management
Encapsulating position checking logic into reusable functions or, for more complex scenarios, within classes (OOP in MQL5) significantly improves code readability, maintainability, and reduces redundancy.
Consider creating a helper class or a set of utility functions like:
// Forward declaration for a potential utility class
class CPositionInfoUtil
{
public:
static bool IsPositionOpen(string symbol, int magic = -1, ENUM_POSITION_TYPE type = WRONG_VALUE);
static double GetOpenPositionVolume(string symbol, int magic = -1, ENUM_POSITION_TYPE type = WRONG_VALUE);
static ulong GetOpenPositionTicket(string symbol, int magic = -1, ENUM_POSITION_TYPE type = WRONG_VALUE);
// ... other helper methods
};
// Example implementation of one such method
bool CPositionInfoUtil::IsPositionOpen(string symbol, int magic = -1, ENUM_POSITION_TYPE type = WRONG_VALUE)
{
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if (PositionSelect(ticket))
{
if (PositionGetString(POSITION_SYMBOL) == symbol)
{
bool magicMatch = (magic == -1 || PositionGetInteger(POSITION_MAGIC) == magic);
bool typeMatch = (type == WRONG_VALUE || PositionGetInteger(POSITION_TYPE) == type);
if (magicMatch && typeMatch)
return true;
}
}
}
return false;
}
This approach centralizes position-related queries, making the main EA logic cleaner and easier to understand.
Conclusion
Effectively checking for open positions is a non-negotiable skill for MQL5 developers aiming to build reliable and profitable Expert Advisors.
Summary of Key Methods
PositionsTotal(): Quickly determines if any positions are open.PositionGetTicket(index)andPositionSelect(ticket): The core mechanism for iterating through positions and selecting one for detailed inspection.PositionGetInteger(),PositionGetString(),PositionGetDouble(): Used to retrieve specific properties (symbol, type, volume, magic number, etc.) of a selected position.
Best Practices for Robust Position Management
- Specificity: Always filter positions by symbol, and usually by magic number, to ensure your EA is interacting only with its own trades.
- Error Handling: Diligently check return values from position functions and use
GetLastError()to diagnose and manage issues. - Code Structure: Employ custom functions or classes to encapsulate position checking logic, promoting modularity and maintainability.
- Contextual Awareness (MQL4 vs. MQL5): Remember that MQL5’s position system aggregates trades, unlike MQL4’s order-based system. Logic ported from MQL4 needs careful adaptation.
- Event Handling: Leverage
OnTradeTransactionfor event-driven updates on position status, which can be more efficient than constant polling in some EAs.
By mastering these techniques and adhering to best practices, you can build MQL5 EAs that manage positions with precision and robustness, forming a solid foundation for your automated trading strategies.