How Do MQL5 Orders, Deals, and Positions Interact?

MQL5 introduced a fundamental shift in how trading operations are handled compared to MQL4. While MQL4 centered primarily around orders and their states, MQL5 employs a more granular and explicit model involving three distinct entities: Orders, Deals, and Positions. Understanding the interaction between these components is crucial for developing robust and reliable algorithmic trading systems (Expert Advisors, indicators, and scripts) in MetaTrader 5.

This article delves into the lifecycle of a trade in MQL5, explaining the role of each entity and how they relate to one another, providing practical examples for developers.

Introduction to Orders, Deals, and Positions in MQL5

In MetaTrader 5, the trading process is broken down into three stages or entities:

  • Order: A request to perform a trading operation under specific conditions (e.g., buy EURUSD at market price, sell GBPJPY at a future price). An order represents the intention to trade.
  • Deal: The actual execution of an order. A deal occurs when an order is matched and fulfilled in the market. A deal represents a completed transaction.
  • Position: The current market exposure for a specific financial instrument. A position is the aggregated result of one or more deals in the same direction (Buy or Sell) for a single symbol. It represents the total holding.

This model provides much more transparency and control over the trading process compared to MQL4’s consolidated Order entity.

Overview of Order Types in MQL5: Market, Pending, Stop-Limit

MQL5 supports various order types to suit different trading strategies. These are defined using the ENUM_ORDER_TYPE enumeration.

Common types include:

  • Market Orders: Immediate execution at the best available current price.
    • ORDER_TYPE_BUY: Buy at Market.
    • ORDER_TYPE_SELL: Sell at Market.
  • Pending Orders: Orders scheduled to be executed in the future when the price reaches a specified level.
    • ORDER_TYPE_BUY_LIMIT: Buy at or below a specified price.
    • ORDER_TYPE_SELL_LIMIT: Sell at or above a specified price.
    • ORDER_TYPE_BUY_STOP: Buy at or above a specified price (to enter on a break).
    • ORDER_TYPE_SELL_STOP: Sell at or below a specified price (to enter on a break).
    • ORDER_TYPE_BUY_STOP_LIMIT: A combination, placing a Buy Limit order when a Stop price is reached.
    • ORDER_TYPE_SELL_STOP_LIMIT: A combination, placing a Sell Limit order when a Stop price is reached.

Other order types relate to position closing (ORDER_TYPE_CLOSE_BY) or exchange markets.

Understanding Deal Types: Buy, Sell, Balance, Credit, Charge, Correction, Bonus, Commission, Swap, Dividend, Interest, Tax

Deals represent executed transactions and are characterized by their type (ENUM_DEAL_TYPE). The most common are:

  • DEAL_TYPE_BUY: Execution of a buy order, increasing a long position or decreasing a short position.
  • DEAL_TYPE_SELL: Execution of a sell order, increasing a short position or decreasing a long position.

Beyond trading operations, deals also record various financial adjustments to the account balance:

  • DEAL_TYPE_BALANCE: Deposit or withdrawal operations.
  • DEAL_TYPE_CREDIT: Credit granted.
  • DEAL_TYPE_CHARGE: Credit used or withdrawn.
  • DEAL_TYPE_CORRECTION: Balance correction.
  • DEAL_TYPE_BONUS: Bonus accrual.
  • DEAL_TYPE_COMMISSION: Commission charged.
  • DEAL_TYPE_SWAP: Swap charge or credit.
  • DEAL_TYPE_DIVIDEND: Dividend accrual.
  • DEAL_TYPE_INTEREST: Interest accrual.
  • DEAL_TYPE_TAX: Tax charged.

Each deal has a unique ticket number, symbol, type, direction (buy/sell), price, volume, and timestamp.

Defining a Position: Long and Short Positions

A position represents the net exposure to a financial instrument. It consolidates all open deals (buys and sells that have not been offset) for a specific symbol and direction.

  • Long Position: Exists when the total volume of buy deals executed on an instrument exceeds the total volume of sell deals used to close buys. Represents a bet on the price increasing.
  • Short Position: Exists when the total volume of sell deals executed on an instrument exceeds the total volume of buy deals used to close sells. Represents a bet on the price decreasing.

For a given symbol, you can have at most one long position and one short position simultaneously, depending on the account hedging settings. In netting accounts, you can only have one position (either net long or net short) per symbol.

A position has properties like symbol, direction (buy/sell), total volume, open price (weighted average of deals), current price, profit/loss, swap, and commission.

The Order Execution Process: From Order to Deal

The lifecycle of a trade begins with placing an order.

Order Placement and Status: Initial, Accepted, Filled, Rejected, Cancelled

When you send an order request using OrderSend() or OrderSendAsync() via the CTrade class, the order goes through various statuses (ENUM_ORDER_STATE).

Common states include:

  • ORDER_STATE_STARTED: Initial state, order is being processed.
  • ORDER_STATE_PLACED: Order accepted by the broker’s trading system.
  • ORDER_STATE_PARTIAL: Only a portion of the requested volume has been filled (for market depth trading).
  • ORDER_STATE_FILLED: The entire requested volume has been filled.
  • ORDER_STATE_CANCELED: Order canceled by the client.
  • ORDER_STATE_REJECTED: Order rejected by the broker or trading system.
  • ORDER_STATE_EXPIRED: Pending order expired without being triggered.

You can monitor the order’s state using OrderSelect() and accessing properties like OrderGetInteger(ORDER_STATE).

Deal Creation: How Orders Result in Deals

When an order reaches a state where it is executed (e.g., ORDER_STATE_FILLED or ORDER_STATE_PARTIAL for market orders, or triggered and filled for pending orders), one or more deals are generated.

  • A single order execution typically results in one deal, but can result in multiple deals if the order is executed against multiple opposing orders in the market depth.
  • Each resulting deal represents a specific transaction at a specific price and volume.
  • The properties of the order (symbol, type, volume, price limits) directly influence the characteristics of the deal(s) created.

Crucially, the deal is the record of the executed trade, carrying the exact execution price, volume, and time.

Order Properties and Their Influence on Deal Execution

Order properties dictate how, when, and at what price the order can be executed, thereby influencing the resulting deal(s).

  • Symbol: Specifies the instrument for the deal.
  • Volume: Determines the potential volume of the deal(s).
  • Type: Dictates whether it’s a buy/sell market or pending order, influencing the execution logic.
  • Price, StopLimitPrice: Define the price levels that trigger pending orders and set price limits for execution (e.g., for Limit or Stop-Limit orders).
  • Deviation: For market orders, specifies the maximum acceptable price slippage.
  • Expiration: Sets a time limit for pending orders.
  • Filling Policy (e.g., ORDER_FILLING_FOK, ORDER_FILLING_IOC, ORDER_FILLING_RETURN): Determines how partial fills are handled, which affects whether one or multiple deals might be generated for a single order.
  • Position Ticket: For closing/modifying an existing position, links the new order/deal to that specific position.

These properties are set when sending the order request.

Position Management Based on Deals

Positions are not created or modified directly by orders. They are managed automatically by the trading terminal based on the deals that occur.

Position Opening: Aggregating Deals into a Position

When the first deal in a specific direction (Buy or Sell) occurs for a symbol that does not currently have a position in that direction, a new position is opened.

  • The position’s initial volume and open price are derived from this first deal.
  • For example, a DEAL_TYPE_BUY deal when there’s no existing long position on the symbol opens a new long position.

Position Modification: Adding to or Reducing an Existing Position

Subsequent deals for the same symbol and direction modify the existing position:

  • A DEAL_TYPE_BUY deal, when a long position already exists, increases the volume of the long position.
  • A DEAL_TYPE_SELL deal, when a short position already exists, increases the volume of the short position.

When a deal occurs in the opposite direction of an existing position, it reduces the position’s volume:

  • A DEAL_TYPE_SELL deal, when a long position exists, decreases the volume of the long position.
  • A DEAL_TYPE_BUY deal, when a short position exists, decreases the volume of the short position.

In both cases of increasing/decreasing volume, the position’s average open price is recalculated based on the weighted average of all deals contributing to the current net volume.

Position Closing: How Deals Close a Position

A position is closed when the total volume of deals in the opposite direction equals the current volume of the position.

  • If you have a long position of 1 lot on EURUSD, and a DEAL_TYPE_SELL deal of 1 lot occurs on EURUSD, the long position will be closed.
  • If you have a short position of 0.5 lots on GBPJPY, and a DEAL_TYPE_BUY deal of 0.5 lots occurs on GBPJPY, the short position will be closed.

Any remaining volume from an opposing deal will either open a new position in the opposite direction (if allowed by account settings) or partially close the existing position.

Calculating Profit/Loss and Margin Requirements Based on Positions

Profit and loss (P/L) are calculated at the position level, not the deal level (though deals contribute to the P/L of the position). The unrealized P/L of a position is the difference between the current market price and the position’s average open price, multiplied by its volume and the instrument’s profit contract size.

Margin requirements are also calculated based on the total volume of open positions, not individual orders or deals.

Practical Examples of Order, Deal, and Position Interactions

Let’s illustrate the concepts with MQL5 code snippets using the CTrade class, which is the standard way to perform trading operations.

Example 1: Placing a Market Order and Observing the Resulting Deal and Position

// Requires `#include <Trade/Trade.mqh>`
#include <Trade/Trade.mqh>
CTrade trade;

void OnStart()
{
    string symbol = Symbol();
    double volume = 0.1; // Standard lot volume depends on symbol properties
    long   magic  = 12345; // Unique magic number for this EA/script

    Print("Attempting to place BUY market order for ", volume, " lots on ", symbol);

    // Place the market order
    if(trade.Buy(volume, symbol, 0, 0, ORDER_FILLING_IOC, magic))
    {
        Print("Buy order placed successfully.");
        // The order ticket can be retrieved via trade.ResultOrder()
        long order_ticket = trade.ResultOrder();
        Print("Order Ticket: ", order_ticket);

        // Give the system a moment to process and potentially create a deal/position
        Sleep(500);

        // --- Check for Deal --- 
        // Iterate through deals in history (recent ones first)
        int deals_count = HistoryDealsTotal();
        for(int i = deals_count - 1; i >= 0; i--)
        {
            long deal_ticket = HistoryDealGetTicket(i);
            if(deal_ticket > 0)
            {
                long deal_order_ticket = HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
                if(deal_order_ticket == order_ticket)
                {
                    // Found the deal corresponding to our order
                    Print("\nFound matching Deal!");
                    Print("Deal Ticket: ", deal_ticket);
                    Print("Deal Symbol: ", HistoryDealGetString(deal_ticket, DEAL_SYMBOL));
                    Print("Deal Type: ",   ENUM_DEAL_TYPE_ToString((ENUM_DEAL_TYPE)HistoryDealGetInteger(deal_ticket, DEAL_TYPE)));
                    Print("Deal Volume: ", HistoryDealGetDouble(deal_ticket, DEAL_VOLUME));
                    Print("Deal Price: ",  HistoryDealGetDouble(deal_ticket, DEAL_PRICE));
                    break; // Found the deal, exit loop
                }
            }
        }

        // --- Check for Position --- 
        // You might need to check if a new position was opened or an existing one modified
        // This example assumes a netting account or no existing position in this direction
        if(PositionSelect(symbol))
        {
            Print("\nFound Position for ", symbol, "!");
            Print("Position Ticket: ",    PositionGetInteger(POSITION_TICKET));
            Print("Position Type: ",      ENUM_POSITION_TYPE_ToString((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)));
            Print("Position Volume: ",    PositionGetDouble(POSITION_VOLUME));
            Print("Position Avg Price: ", PositionGetDouble(POSITION_PRICE_OPEN));
            Print("Position Current P/L: ", PositionGetDouble(POSITION_PROFIT));
        }
        else
        {
            Print("\nNo position found for ", symbol, ".");
            // This might happen if the volume was too small, or if the account type/state prevented a position
        }

    }
    else
    {
        Print("Buy order failed. Error: ", trade.ResultRetcode(), " (", trade.ResultRetcodeDescription(), ")");
        Print("Dealer Response: ", trade.ResultDealerComment());
    }
}

This example shows placing a market buy order. If successful, it attempts to find the resulting deal in the history (deals appear in history upon execution) and checks the open positions to see the aggregate result.

Example 2: Using a Pending Order and Tracking Its Execution into a Deal and Subsequent Position

// Requires `#include <Trade/Trade.mqh>`
#include <Trade/Trade.mqh>
CTrade trade;

void OnStart()
{
    string symbol = Symbol();
    double volume = 0.1;
    long   magic  = 54321;
    double current_ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
    double entry_price = current_ask - SymbolInfoDouble(symbol, SYMBOL_POINT) * 10; // 10 points below Ask

    Print("Attempting to place BUY LIMIT pending order for ", volume, " lots on ", symbol, " at ", entry_price);

    // Place the pending order
    if(trade.BuyLimit(volume, entry_price, symbol, 0, 0, 0, magic))
    {
        Print("Buy Limit order placed successfully.");
        long order_ticket = trade.ResultOrder();
        Print("Pending Order Ticket: ", order_ticket);

        // This order will remain pending until the price reaches entry_price.
        // Once triggered and filled, it will generate a DEAL_TYPE_BUY deal.
        // This deal will then open or increase a long POSITION for the symbol.

        // You would typically monitor for order state changes (e.g., in OnTradeTransaction) 
        // to detect when the order is filled and then check for the resulting deal and position.
        Print("\nOrder is pending. Monitor OnTradeTransaction for state change to FILLED.");
        Print("Once FILLED, look for a DEAL_TYPE_BUY with ORDER=", order_ticket, " and a Long Position on ", symbol);

    }
    else
    {
        Print("Buy Limit order failed. Error: ", trade.ResultRetcode(), " (", trade.ResultRetcodeDescription(), ")");
        Print("Dealer Response: ", trade.ResultDealerComment());
    }
}

// Example of monitoring for execution (requires running as an EA)
/*
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result)
{
    // Filter for transaction type that indicates order state change
    if (trans.type == TRADE_TRANSACTION_REQUEST || trans.type == TRADE_TRANSACTION_DEAL)
    {
        // Check if the transaction relates to our magic number
        if (trans.magic == 54321)
        {
            PrintFormat("Trade Transaction: Type=%s, Order=%d, Deal=%d, Position=%d, State=%s",
                          ENUM_TRADE_TRANSACTION_TYPE_ToString(trans.type),
                          trans.order,
                          trans.deal,
                          trans.position,
                          ENUM_ORDER_STATE_ToString((ENUM_ORDER_STATE)trans.state));

            // If our pending order was filled
            if (trans.type == TRADE_TRANSACTION_DEAL && trans.order == request.order)
            {
                 PrintFormat("Order %d was filled! Deal %d created. Position %d affected.", trans.order, trans.deal, trans.position);
                 // Now you can confirm the deal exists in history and the position state
                 // ... (code similar to Example 1's check) ...
            }
        }
    }
}
*/

This snippet demonstrates placing a Buy Limit order. It highlights that pending orders don’t immediately create deals or positions but wait for market conditions. The OnTradeTransaction event (used in EAs) is the proper way to asynchronously track when such orders are triggered and result in deals and position changes.

Example 3: Modifying a Position with Additional Orders and Deals

Assume a long position of 0.1 lots exists on EURUSD.

// Requires `#include <Trade/Trade.mqh>`
#include <Trade/Trade.mqh>
CTrade trade;

void OnStart()
{
    string symbol = Symbol();
    long   magic  = 67890;

    // Assume a 0.1 lot Long Position exists for Symbol()
    if(PositionSelect(symbol))
    {
        Print("Existing Long Position on ", symbol, " Volume: ", PositionGetDouble(POSITION_VOLUME));

        double add_volume = 0.05; // Add 0.05 lots

        Print("Attempting to add ", add_volume, " lots to the position via BUY market order.");

        // Place a BUY market order with the position ticket to explicitly link it (optional but good practice for clarity)
        // Note: Even without the position ticket, a buy order on an existing long position will increase it.
        if(trade.Buy(add_volume, symbol, 0, 0, ORDER_FILLING_IOC, magic, PositionGetInteger(POSITION_TICKET)))
        {
             Print("Add-on order placed successfully.");
             long add_order_ticket = trade.ResultOrder();
             Print("Add-on Order Ticket: ", add_order_ticket);

             Sleep(500);

             // Check the updated position state after the deal occurs
             if(PositionSelect(symbol))
             {
                 Print("\nPosition updated!");
                 Print("New Position Volume: ", PositionGetDouble(POSITION_VOLUME)); // Should be original + add_volume (e.1 + 0.05 = 0.15)
                 Print("New Avg Price: ", PositionGetDouble(POSITION_PRICE_OPEN)); // Price will have been recalculated
                 // Look for the DEAL_TYPE_BUY deal with ORDER = add_order_ticket in history
             }
         } else {
             Print("Add-on order failed. Error: ", trade.ResultRetcode());
         }

    }
    else
    {
        Print("No existing position on ", symbol, " to modify.");
    }
}

Placing a BUY order when a Long position exists results in a DEAL_TYPE_BUY deal. This deal’s volume is added to the existing Long position’s volume, and the POSITION_PRICE_OPEN is updated to a weighted average.

Example 4: Closing a Position with a Specific Order

Assume a long position of 0.15 lots exists on EURUSD from the previous example.

// Requires `#include <Trade/Trade.mqh>`
#include <Trade/Trade.mqh>
CTrade trade;

void OnStart()
{
    string symbol = Symbol();
    long   magic  = 13579;

    // Assume a Long Position of 0.15 lots exists for Symbol()
    if(PositionSelect(symbol))
    {
        double current_volume = PositionGetDouble(POSITION_VOLUME);
        long   pos_ticket     = PositionGetInteger(POSITION_TICKET);

        Print("Existing Long Position on ", symbol, " Volume: ", current_volume);
        Print("Attempting to close the position via SELL market order.");

        // Use the PositionClose method, which internally creates a SELL market order
        // with the appropriate volume and links it to the position ticket.
        if(trade.PositionClose(symbol, magic))
        {
             Print("Position close order placed successfully.");
             long close_order_ticket = trade.ResultOrder(); // Order ticket for the closing order
             Print("Close Order Ticket: ", close_order_ticket);

             Sleep(500);

             // Check if the position still exists after the deal occurs
             if(!PositionSelect(symbol))
             {
                 Print("\nPosition closed successfully!");
                 // Look for the DEAL_TYPE_SELL deal with ORDER = close_order_ticket in history.
                 // This deal will also have DEAL_POSITION_ID = pos_ticket.
             }
             else
             {
                 Print("\nPosition still exists. Remaining volume: ", PositionGetDouble(POSITION_VOLUME));
                 // This could happen if the close order was only partially filled (unlikely for Market Close usually)
             }
         } else {
             Print("Position close order failed. Error: ", trade.ResultRetcode());
         }

    }
    else
    {
        Print("No existing position on ", symbol, " to close.");
    }
}

Using trade.PositionClose() or placing a SELL order with volume equal to the Long position volume (and specifying the POSITION_TICKET is recommended) results in a DEAL_TYPE_SELL deal. This deal’s volume offsets the Long position’s volume. When the total opposing deal volume equals the position volume, the position is closed.

Advanced Concepts and Considerations

Working with Orders, Deals, and Positions effectively in complex EAs involves more than just sending requests.

Using History Orders and Deals

Historical orders and deals are accessible using functions like HistoryOrdersTotal(), HistoryOrderGetTicket(), HistoryDealsTotal(), and HistoryDealGetTicket(). You then use HistoryOrderGet... or HistoryDealGet... functions with the ticket to retrieve their properties. This is essential for analyzing past trading activity, verifying executions, and auditing your EA’s performance.

Accessing history requires requesting it first using HistorySelect() or HistorySelectByPosition().

// Example: Iterate through recent deals
long history_from = TimeCurrent() - 24*3600; // Last 24 hours
long history_to   = TimeCurrent();

if(HistorySelect(history_from, history_to))
{
    int total_deals = HistoryDealsTotal();
    Print("Found ", total_deals, " deals in history for the last 24 hours.");

    for(int i = 0; i < total_deals; i++)
    {
        long deal_ticket = HistoryDealGetTicket(i);
        if(deal_ticket > 0)
        {
            string symbol = HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
            ENUM_DEAL_TYPE type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
            double volume = HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
            double price = HistoryDealGetDouble(deal_ticket, DEAL_PRICE);
            datetime time = (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);
            long order_ticket = HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
            long position_id = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);

            PrintFormat("Deal #%d: %s %s %f lots at %f (Order=%d, Pos=%d) Time=%s",
                        deal_ticket,
                        ENUM_DEAL_TYPE_ToString(type),
                        symbol,
                        volume,
                        price,
                        order_ticket,
                        position_id,
                        TimeToString(time));
        }
    }
}

Error Handling and Order Rejection Scenarios

Order placement can fail for numerous reasons (invalid volume, insufficient funds, market closed, incorrect price, broker limitations, network issues, etc.). Always check the return code of OrderSend()/CTrade methods and use GetLastError() or trade.ResultRetcode() and trade.ResultRetcodeDescription() to identify the cause. Implement retry logic or adjust parameters based on the error.

An order might be accepted (ORDER_STATE_PLACED) but later rejected or canceled before execution due to broker rules or changes in market conditions (less common for market orders, more relevant for complex pending types or exchange trading).

Event Handling for Order and Deal Updates

For reactive trading systems (EAs), monitoring trading events is critical. The OnTradeTransaction and OnTrade events are fired when trade-related changes occur:

  • OnTradeTransaction: Provides detailed information about various transaction types (request sent, order state changed, deal created, position modified). This is the primary event to track the lifecycle.
  • OnTrade: A simpler event indicating any change to the account, orders, or positions state. Useful for general state monitoring but less granular than OnTradeTransaction.

Using these events allows your EA to react instantly when an order is filled, a deal is created, or a position is opened/closed, enabling timely updates to stop loss/take profit, placing subsequent orders, or recalculating risk metrics.

By mastering the distinct roles and interactions of Orders, Deals, and Positions, MQL5 developers can build more sophisticated, reliable, and transparent trading algorithms. The MQL5 model provides the necessary tools to track every step of the trading process, from the initial intent to the final market exposure.


Leave a Reply