Summary: This article exposes a critical flaw in MQL4 EA development: order comments can be altered by brokers, breaking logic. It offers a robust tracking system using OrderTicket() and a custom associative array to replace fragile comment-based filtering.




I spent a week chasing a ghost. My EA was supposed to manage trades in groups. Strategy A would open a buy, Strategy B would open a sell, and they were distinguished by a simple string in the OrderComment(). The logic was clean:

``cpp
if(StringFind(OrderComment(), "STRAT_A") >= 0) {
// Manage Strategy A trade
}
`

It worked flawlessly in backtests. Then I went live. Chaos. Trades from Strategy A were being managed by Strategy B's logic. Orders were being closed prematurely. The EA was effectively schizophrenic. The culprit? The broker's trade server was appending a string to my comments. An order placed with the comment "STRAT_A" was being stored as "STRAT_A[tp]" after it hit take profit, or "STRAT_A[sl]" after a stop loss. The next time the EA looped through the history to check closed orders, the
OrderComment() function returned the mutated string, causing the StringFind() logic to fail catastrophically.

The Unspoken Rule of Order Comments



This is the dirty secret of MQL4 development that no one talks about. The official MQL4 documentation simply states that the
comment parameter in OrderSend() is a "string comment. Last part of the comment may be changed by server". That "may" is doing a lot of heavy lifting. It's not a bug; it's a feature of the broker's backend.

Reference: As noted in discussions on the official MQL5 community forum, this is "broker specific modification" and "not a good idea to use comments as filters". The forum discussion highlights that while the comment remains intact for open orders, once an order is closed, the broker can and will append tags like
[tp], [sl], or [br] to indicate the exit reason.

The common advice I see in books like 零基础学MQL (Zero-Based Learning MQL) and blog posts is to use comments for grouping. This is dangerous. It introduces a single point of failure that is entirely outside your control.

The "Sub-Strategy" Myth



Many developers, especially those writing complex EAs with multiple entry signals, use the
OrderComment() as a "sub-strategy ID". They'll have a single MagicNumber for the EA and then use OrderComment to differentiate between, say, a "Trend" signal and a "Counter-Trend" signal. The logic is:

  • Open order with comment "Trend_Entry".

  • In OnTick(), loop through orders.

  • If OrderMagicNumber() == MagicNumber and OrderComment() == "Trend_Entry", apply Trend management.


  • This seems logical until a Trend order is closed by a stop loss. Now that order in history has the comment "Trend_Entry[sl]". If your EA checks closed orders to track its performance (e.g., for a daily P&L calculation), it will fail to recognize the order, skewing your statistics. Worse, if you have a "Counter-Trend" signal that uses a comment like "CTrend_Entry" that contains the substring "Trend", your
    StringFind might falsely identify it as a Trend order.

    A Robust Solution: The Ticket Number Manifest



    The solution is brutally simple and heavily documented in the official MQL4 reference, yet it's consistently ignored: Use the OrderTicket() .

    The
    OrderTicket() is a unique, immutable integer assigned by the trade server. It is the only true, unmodifiable identifier for an order. No broker can change it.

    Instead of encoding strategy logic into a fragile string, use the ticket number as the key. This requires a shift in how you think about order management.

    You can't store complex data in the ticket, but you can store it in an array or a file. The simplest method is to create an associative array in your EA that maps a ticket number to a strategy ID or any other metadata.

    Here's the implementation:

    `cpp
    //+------------------------------------------------------------------+
    //| RobustOrderManager |
    //+------------------------------------------------------------------+
    #property copyright "FXEAR.com"
    #property link "https://www.fxear.com"
    #property version "1.00"

    // Define strategy IDs
    #define STRATEGY_A 1
    #define STRATEGY_B 2

    // Global arrays to store our mapping
    // Since MQL4 doesn't have a built-in map, we use parallel arrays.
    int g_tickets[];
    int g_strategy_ids[];
    int g_array_size = 0;

    //+------------------------------------------------------------------+
    //| Add a mapping between ticket and strategy ID |
    //+------------------------------------------------------------------+
    void AddOrderMapping(int ticket, int strategy_id) {
    ArrayResize(g_tickets, g_array_size + 1);
    ArrayResize(g_strategy_ids, g_array_size + 1);
    g_tickets[g_array_size] = ticket;
    g_strategy_ids[g_array_size] = strategy_id;
    g_array_size++;
    }

    //+------------------------------------------------------------------+
    //| Get strategy ID from ticket |
    //+------------------------------------------------------------------+
    int GetStrategyFromTicket(int ticket) {
    for(int i = 0; i < g_array_size; i++) {
    if(g_tickets[i] == ticket) {
    return g_strategy_ids[i];
    }
    }
    return -1; // Not found
    }

    //+------------------------------------------------------------------+
    //| Function to send order with persistent tracking |
    //+------------------------------------------------------------------+
    int SendOrderWithTracking(int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, int strategy_id) {
    // Note: We still use a comment, but only for human readability, not logic.
    string comment = "EA_Trade";
    int ticket = OrderSend(Symbol(), cmd, volume, price, slippage, stoploss, takeprofit, comment, MagicNumber);

    if(ticket > 0) {
    // Add the mapping to our internal tracker.
    AddOrderMapping(ticket, strategy_id);
    Print("Order opened: Ticket ", ticket, " assigned to Strategy ", strategy_id);
    } else {
    Print("OrderSend failed. Error: ", GetLastError());
    }
    return ticket;
    }

    //+------------------------------------------------------------------+
    //| OnTick function |
    //+------------------------------------------------------------------+
    void OnTick() {
    // We don't rely on comments at all.
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
    if(OrderSelect(i, SELECT_BY_POS)) {
    if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()) {
    int ticket = OrderTicket();
    int strategy_id = GetStrategyFromTicket(ticket);

    if(strategy_id == STRATEGY_A) {
    // Apply Strategy A management logic
    // Trailing stop, partial close, etc.
    } else if(strategy_id == STRATEGY_B) {
    // Apply Strategy B management logic
    }
    }
    }
    }
    }
    `

    The "Immutability" Problem and a Unique Perspective



    A common argument I hear from developers is: "But I need to change the grouping of an order mid-flight. I want to move a trade from Strategy A to Strategy B without closing and reopening it."

    This is a legitimate use case. Some developers have even tried to find a function like
    OrderModifyComment() to achieve this. It does not exist. You cannot modify the comment of an open order in MQL4.

    Here is my unique perspective on this problem: Treat the "strategy" assignment as a state machine that is independent of the trade server. Instead of trying to force the broker to store your data, store it locally.

    If you need to "reassign" a trade, just update your local mapping arrays.

    `cpp
    // Function to reassign a trade from Strategy A to Strategy B
    void ReassignTrade(int ticket, int new_strategy_id) {
    for(int i = 0; i < g_array_size; i++) {
    if(g_tickets[i] == ticket) {
    g_strategy_ids[i] = new_strategy_id;
    Print("Trade ", ticket, " reassigned to Strategy ", new_strategy_id);
    return;
    }
    }
    Print("Error: Ticket not found for reassignment.");
    }
    `

    This method is immediate, doesn't require a server-side request (which can fail due to connectivity issues or "invalid stops" errors), and bypasses the comment mutation problem entirely. Most developers don't do this because they are too locked into the idea that the data must live on the server. But for logic execution, the client-side data is often more reliable and flexible. This is a meta-structural layer that sits on top of the EA, managing the meaning of trades, not the trades themselves.

    Reference: This approach aligns with the principles of robust system design discussed in publications like the CFA Institute's work on operational risk and system architecture, where data immutability and source-of-truth are key principles. The server is the source of truth for execution (price, volume, ticket), but your EA is the source of truth for interpretation (strategy ID, entry reason).

    Beyond the Comment: The "Future Function" in Your Own Code



    One final thought: the over-reliance on
    OrderComment() is a form of "future function" in your own codebase. You are assuming that the string you wrote will remain the same forever. This is an assumption about future state that is not guaranteed by the platform. When you use OrderTicket()`, you are referencing an immutable fact. You are forcing your code to be robust and reactive, rather than fragile and assumptive.

    In the end, the ghost in my machine wasn't a bug; it was a design flaw. And it was corrected not by writing better code, but by writing more robust architecture that respects the limitations of the platform.

    本文首发于FXEAR.com,原创内容,未经授权禁止转载。