今天我们来聊聊
OrderComment()。它看起来人畜无害。你给 OrderSend() 传个字符串,看着历史记录里 "策略A_入场_01" 这样的注释,感觉自己像个编程大神。打住。你刚搭好了一座纸牌屋。
来自经纪商的静默劫持
你知道吗,当这个订单被止盈平仓后会发生什么?来,我演示给你看。你用
OrderSend() 发送一个注释:"策略A"。EA 开了单,触发了止盈。然后你在历史记录查询逻辑里写个 Print(OrderComment()),你期望得到什么?我得到的是
"策略A[tp]"。而且我不是一个人。论坛里另一个人得到的是
"0[tp]"。有些经纪商加 [sl],有些加 [br](保本?),甚至还有些经纪商可能直接删掉你的注释。重点是:只要你的订单到了经纪商的服务器,你的注释就不再是你的注释了。"用注释不是什么好主意,经纪商可以改注释,甚至完全替换掉。"
这是个非常非常严重的问题,因为大量所谓的"进阶"EA都拿
OrderComment 作为多策略管理的主要过滤器。它们循环检查订单,来一句 if(OrderComment() == "策略A")。这纯粹是在赌经纪商不会改你的文本。虚幻的"策略唯一标识"
我以前跑过一个EA,里面有三个子策略:趋势跟踪、均值回归剥头皮、网格对冲。我用注释来区分它们。回测里一切完美。实盘呢?灾难。
均值回归策略的订单触发了止盈,经纪商加了
[tp]。注释变成 "Scalper[tp]"。我的主循环里写的却是 if(OrderComment() == "Scalper")。条件为假。EA 认不出这个订单了。它没法再针对这个特定的票据号去管理移动止损了,因为它根本不知道这个单子属于哪一组。"票据号(Ticket)"的陷阱
你可能想说,"那我用票据号不就行了。" 没错,它能识别订单,但解决不了分组问题。如果注释已经乱套了,你怎么知道某个票据属于哪个策略?
MagicNumber vs. Comment 之争
有些纯粹主义者会说:"所有东西都放
MagicNumber 里。" 你可以给每个策略分配一个独立的 MagicNumber。比如趋势策略 = 1001,剥头皮 = 1002。从技术角度讲,这是最正确、最安全的过滤方法。既然如此,我们为什么还要用
Comment?因为
MagicNumber 对人类阅读来说简直是场灾难。 想象一下你在 3 个品种上跑了 5 个 EA。你的 MagicNumber 列表看起来就像一张excel大表。你盯着 MT4 的交易标签页,看到 1001 和 1002。不查代码你根本不知道那代表什么。而 OrderComment 是给 我们 —— 人类 —— 用的,让我们能瞟一眼交易历史就知道发生了什么,不用再去翻代码。参考来源: 正如 MQL4 社区指出的,"使用'magic number'来标识一组交易" 是基础最佳实践。注释只是辅助数据。
解决方案:UUID 映射层
你需要停止把
OrderComment 当做可靠数据。把它当成一个易变的、只读的、给人看的注解。我开发了一个叫 "UUID 映射层" 的系统。
核心代码如下:
``
cpp
//+------------------------------------------------------------------+
//| UUID_Manager.mq4 |
//| 解决注释被篡改的方案 |
//+------------------------------------------------------------------+
#property strict
// 我们将唯一ID(票据)映射到策略组。
struct StrategyMapping {
int ticket;
int groupID; // 我们内部的策略组ID
string originalComment; // 仅用于日志记录,别依赖它!
};
StrategyMapping map[];
//+------------------------------------------------------------------+
//| 使用映射系统开仓 |
//+------------------------------------------------------------------+
int OpenTradeWithUUID(string symbol, int cmd, double volume, double price, int slippage, double sl, double tp, int groupID, string comment) {
// 1. 发送订单。我们会写入一个基本注释,但我们不在乎它之后变成什么样。
int ticket = OrderSend(symbol, cmd, volume, price, slippage, sl, tp, comment, 1000 + groupID, 0, clrNONE);
// 现在 MagicNumber 是 1000 + groupID。它就是我们的主键(KEY)。
// 注释纯粹是为了给人看。
if(ticket > 0) {
// 2. 把映射关系存到我们的数组里。
ArrayResize(map, ArraySize(map) + 1);
map[ArraySize(map)-1].ticket = ticket;
map[ArraySize(map)-1].groupID = groupID;
map[ArraySize(map)-1].originalComment = comment;
Print("订单已开: ", ticket, " | 分组: ", groupID);
}
return ticket;
}
//+------------------------------------------------------------------+
//| 根据票据号获取策略组ID |
//+------------------------------------------------------------------+
int GetGroupIDByTicket(int ticket) {
for(int i = 0; i < ArraySize(map); i++) {
if(map[i].ticket == ticket) return map[i].groupID;
}
return -1; // 没找到
}
//+------------------------------------------------------------------+
//| 主循环:检查订单 |
//+------------------------------------------------------------------+
void CheckOrders() {
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
// 第一步:用 MagicNumber 过滤,只拿我们EA的订单。
// 记住,MagicNumber 是安全的,经纪商改不了它!
if(OrderMagicNumber() >= 1000 && OrderMagicNumber() < 2000) {
int groupID = OrderMagicNumber() - 1000;
// 第二步:逻辑里完全不碰 OrderComment()。
// 我们用 MagicNumber 来确定分组。
switch(groupID) {
case 1: ManageTrendStrategy(OrderTicket()); break;
case 2: ManageScalperStrategy(OrderTicket()); break;
}
}
}
}
}
`
为什么有效:
<strong> MagicNumber 是纯整数。</strong> 经纪商改不了它。永远改不了。这是识别订单来源或"分组"唯一可靠的途径。
<strong>我们把 MagicNumber 当主要分组键。</strong> 不再依赖会被经纪商加上 [tp] 或 [sl] 而失效的字符串比对。
<strong> Comment 纯粹用于显示。</strong> 经纪商爱改不改,我不在乎。我的EA逻辑直接无视它。
进阶:"四维"识别模型
对于高频或高密度EA(比如网格系统),我把这个模型升级成了所谓"四维识别模型":
维度一:MagicNumber。 绝对的、主要的主键。
维度二:OrderOpenTime()。 用于隔离特定时段或批次。
维度三:OrderType()。 用于在同一组内区分买单和卖单。
维度四:自定义哈希(可选)。 如果你非得用注释,就用 StringFind(OrderComment(), "MyPrefix") != -1 而不是 ==,这样还能处理经纪商塞进来的垃圾字符。
我的务实结论
别再给你的EA过度设计了。别再试图用 OrderComment 当逻辑标识符了。那是个给人看的文本字段,不是给你的代码逻辑用的。机器过滤用 MagicNumber,人看历史用 OrderComment。
我曾浪费数周时间调试经纪商修改 OrderComment 导致的bug。解决方案很简单:把注释从我的逻辑中彻底剥离,改用分层的 MagicNumber 系统。无聊、简单、好用。
参考来源: MQL4 官方文档和开发者社区共识很明确:OrderMagicNumber() 才是负责标识功能的设计函数。用 OrderComment()` 当主键来偏离这个设计,是一种反模式。本文首发于FXEAR.com,原创内容,未经授权禁止转载。