在单一工具上使用不同的 EA 交易进行交易时 ORDER_MAGIC 的使用·进阶篇
📘

在单一工具上使用不同的 EA 交易进行交易时 ORDER_MAGIC 的使用·进阶篇

第 2/3 篇

EA 虚拟持仓的接口与回算逻辑

在 MT5 里做多策略并行时,真实账户持仓和 EA 自己记的「虚拟仓位」常常要分开。下面这组接口把虚拟仓位的量、方向和外挂计算方法暴露出来,方便策略层直接读,不必每次去翻交易池。 cVOLUME() 返回 pvm.volume,也就是该 EA 虚拟仓位的持仓手数;cTYPE() 返回 pvm.type,给出 ENUM_POSITION_TYPE 枚举值,区分虚拟多空。PositionVirtualMagic() 则按 Magic、品种和当前时间三类参数回算虚拟仓位是否存在,返回布尔值。 计算函数里先按 SYMBOL_VOLUME_STEP 用 -log10 推出手数精度 DIGITS,若为负则归零;随后用 do-while 反复 HistorySelect(0,TimeCurrent()) 直到 HistoryDealsTotal 稳定,避免历史缓存未刷新导致漏单。循环遍历每笔成交,按 DEAL_TIME 是否晚于 CurrentTime、DEAL_MAGIC 是否匹配来累加 BUY/SELL 体积。外汇与贵金属杠杆高,虚拟仓位仅作策略内部记账,实盘误判可能放大敞口风险。 别把 HistorySelect 当一次性调用 MT5 历史交易池在高频成交下可能延迟写入,单次 HistorySelect 后 HistoryDealsTotal 可能比实际少 1~3 笔。用 do-while 卡相等再跑统计,是低成本防抖,省掉就是埋雷。

MQL5 / C++
class="type">class="kw">double cVOLUME(){class="kw">return(pvm.volume);}
class=class="str">"cmt">// 返回 EA 的虚拟仓位持仓量
class="type">ENUM_POSITION_TYPE cTYPE(){class="kw">return(pvm.type);}
class=class="str">"cmt">// 返回 EA 的虚拟仓位类型
class="type">bool PositionVirtualMagic(class="type">class="kw">ulong Magic,
                         class="type">class="kw">string symbol,
                         class="type">class="kw">datetime CurrentTime
                         );
class=class="str">"cmt">// 虚拟仓位的计算方法 返回虚拟仓位存在或不存在.
class="kw">private:
  class="type">void prHistory_Deals(class="type">class="kw">ulong &buf[],class="type">int HTD);
class=class="str">"cmt">// 填充单号数组
};
class=class="str">"cmt">//+-------------------------------------------------------------------------------------+
class=class="str">"cmt">//| 虚拟仓位计算方法, 返回 true 如果有虚拟仓位.                                          |
class=class="str">"cmt">//+-------------------------------------------------------------------------------------+
class="type">bool CPositionVirtualMagic::PositionVirtualMagic(class="type">class="kw">ulong Magic,
                                                 class="type">class="kw">string symbol,
                                                 class="type">class="kw">datetime CurrentTime
                                                 )
  {
   class="type">int DIGITS=(class="type">int)-log10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP));
   if(DIGITS<class="num">0)DIGITS=class="num">0;
   class="type">class="kw">ulong Dticket=class="num">0;
   class="type">int History_Total_Deals=-class="num">1;
   class="type">class="kw">double volume=class="num">0,volume_BUY=class="num">0,volume_SELL=class="num">0;
   class="type">class="kw">ulong DTicketbuf[];
   do
     {
      if(HistorySelect(class="num">0,TimeCurrent()))
        {
         History_Total_Deals=HistoryDealsTotal();
         prHistory_Deals(DTicketbuf,History_Total_Deals);
        }
      HistorySelect(class="num">0,TimeCurrent());
     }
   class="kw">while(History_Total_Deals!=HistoryDealsTotal());
   for(class="type">int t=class="num">0;t<History_Total_Deals;t++)
     {
      Dticket=DTicketbuf[t];
      if(HistoryDealSelect(Dticket))
        {
         if(HistoryDealGetInteger(Dticket,DEAL_TIME)>=CurrentTime)
           {
            if(HistoryDealGetInteger(Dticket,DEAL_MAGIC)==Magic)
              {

◍ 用历史成交反推虚拟净仓位

EA 在无真实持仓时,也能靠历史成交记录算出自己当前的净方向与暴露手数。核心思路是遍历 HistoryDeal,把 BUY 和 SELL 的 DEAL_VOLUME 分别累加,再做差。 下面这段是净手数归集的关键片段:先按 ticket 取每笔成交类型,BUY 进 volume_BUY,SELL 进 volume_SELL,其余类型忽略。 [CODE] if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_BUY) { volume_BUY+=HistoryDealGetDouble(Dticket,DEAL_VOLUME); } else { if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_SELL) { volume_SELL+=HistoryDealGetDouble(Dticket,DEAL_VOLUME); } } [/CODE] 差量归一化时用 DIGITS 控制精度,DIGITS 来自 SYMBOL_VOLUME_STEP 的对数取负,若小于 0 则置 0,避免黄金这类最小步长 0.01 手时出浮点尾巴。 volume = NormalizeDouble(volume_BUY - volume_SELL, DIGITS); 若结果为负,虚拟仓标记为 SELL,为正则标记 BUY,绝对值即 pvm.volume。当 volume 恰好为 0,函数直接 return(false),说明该 Magic 在 symbol 上无净敞口。 历史读取有坑:HistorySelect 可能返回旧快照,原文用 do-while 比对 HistoryDealsTotal 前后是否一致来强制刷新。你在 MT5 里跑类似逻辑时,若发现净仓偶尔少算一笔,优先怀疑这里没做一致性校验。外汇与贵金属杠杆高,虚拟仓仅作风控参考,不代表真实账户权益。

MQL5 / C++
if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_BUY)
  {
   volume_BUY+=HistoryDealGetDouble(Dticket,DEAL_VOLUME);
  }
else
  {
   if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_SELL)
    {
     volume_SELL+=HistoryDealGetDouble(Dticket,DEAL_VOLUME);
    }
  }

「EA 输入参数与初始化骨架」

这段 EA 把可调项全部摊在 input 区:magic=1 用作订单标识,SL=300 点、TP=1000 点构成 1:3.33 的风险回报比,MA_Period=25 搭配 MODE_SMA 与 PRICE_OPEN,仓位固定 0.1 手。 OnInit 里只做两件事——记下 EA 启动时间 CurrentTime=TimeCurrent(),再用 iMA() 拿到均线句柄存进 MA_handle。句柄创建失败会在后续 OnTick 的 CopyBuffer 检查里暴露,不会立刻崩。 实盘前建议把 MA_Period 从 25 改成你常用的周期,比如 20 或 50,并在 MT5 策略测试器里跑一轮看平仓频率是否过分。外汇与贵金属杠杆高,参数未经验证前勿直接上真仓。

MQL5 / C++
class="kw">input class="type">class="kw">ulong      magic     =class="num">1;       class=class="str">"cmt">// 标识号
class="kw">input class="type">int         SL        =class="num">300;      class=class="str">"cmt">// 止损
class="kw">input class="type">int         TP        =class="num">1000;     class=class="str">"cmt">// 获利
class="kw">input class="type">int         MA_Period =class="num">25;       class=class="str">"cmt">// MA 周期
class="kw">input class="type">class="kw">double      lot       =class="num">0.1;      class=class="str">"cmt">// 仓位持仓量
class="kw">input class="type">int         MA_shift  =class="num">0;        class=class="str">"cmt">// 指标偏移
class="kw">input ENUM_MA_METHOD MA_smooth =MODE_SMA;   class=class="str">"cmt">// 平滑类型
class="kw">input ENUM_APPLIED_PRICE price    =PRICE_OPEN;  class=class="str">"cmt">// 价格类型
class="type">int MA_handle,type_MA,rezult;
class="type">class="kw">double v[class="num">2];
class="type">class="kw">datetime CurrentTime;
class="type">MqlTradeResult res;
class="type">MqlTick tick;
CPositionVirtualMagic cpvm;
CProvision prov;
class="type">int OnInit()
  {
   CurrentTime=TimeCurrent();
   MA_handle=iMA(Symbol(),class="num">0,MA_Period,MA_shift,MA_smooth,price);
   class="kw">return(class="num">0);
  }

反手与首仓的下单分支

这段逻辑处理的是虚拟仓位和实际下单的衔接:当检测到已有仓位且类型不等于均线信号给出的 type_MA 时,视为反向信号,执行反手加仓。 if((int)cpvm.cTYPE()!=type_MA) 先强转虚拟仓类型做比对,不等于就进反向分支。Print 把 magic 号、原持仓量、反向类型、依据值全打出来,方便在 MT5 专家日志里对照。rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,cpvm.cVOLUME()+lot) 用原虚拟量加新 lot 发反向单;返回非 -1 就打印结果代码和 res.volume,否则 GetLastError 后直接 return。 else 分支是无仓情形,直接以 lot 手数 SendOrder 开首仓,同样用 rezult 判断是否成功。外汇与贵金属杠杆高,这类反手叠加会在同向波动中快速放大敞口,回测时建议把 lot 和 cpvm.cVOLUME() 的求和上限先锁死再上真金。

MQL5 / C++
 if((class="type">int)cpvm.cTYPE()!=type_MA)class=class="str">"cmt">// 检查是否是平仓时机
    {
     Print("#",magic,"标识号对应的仓位持仓量 ",cpvm.cVOLUME(),
         " 反向仓位类型 ",(class="type">int)cpvm.cTYPE()," 依据 ",type_MA);
     class=class="str">"cmt">//cpvm.cVOLUME() - 虚拟仓位持仓量
     rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,cpvm.cVOLUME()+lot);class=class="str">"cmt">// 反向仓位
     if(rezult!=-class="num">1)Print("#",magic," 操作结果代码 ",rezult," 交易量 ",res.volume);
     else{Print("#",magic,"错误",GetLastError()); class="kw">return;}
    }
   }
  else class=class="str">"cmt">// 如果没有仓位则开仓
   {
    Print("#",magic,"标识号对应的仓位持仓量 ",cpvm.cVOLUME()," 仓位类型 ",type_MA);
    rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,lot);class=class="str">"cmt">// 开仓 
    if(rezult!=-class="num">1)Print("#",magic," 操作结果代码 ",rezult," 交易量 ",res.volume);
    else{Print("#",magic,"错误",GetLastError()); class="kw">return;}
   }
 }

◍ 把幻数拆成可读的三段式

想在 MT5 里同时跑多个同策略 EA 又不互相抢单,核心是把 OrderMagicNumber 从一团乱码改成结构化编码。这里用 Emagic 枚举 + Smagic 结构 + Cmagic 类把幻数切成「数字名交互码品种码」三段,字段从右往左编号,例如 123456789 可拆成123456789,取字段 1 得到 789。

枚举只放三个成员:ENUM_DIGITAL_NAME(EA 数字名)、ENUM_CODE_INTERACTION(交互码)、ENUM_EXPERT_SYMBOL(启动品种)。编译期就能卡掉类型错填,比直接写 0 更不容易看走眼。结构 Smagic 里除了拆出来的三个 int,还存一份组装好的 ulong magicnumber,类里所有编解码都围着它转。 编码时 SetMagic_request 接收 digital_name 和 code_interaction,品种码走私有方法从一张表里查——EURUSD 对应 1,XAUUSD 可能对应 2,表在附件里,没必要动态扫市场观察窗口。最后拼出的 ulong 就是挂单用的幻数。解码侧 SetMagic_result 把幻数转 string 后按三位切段写回结构;GetMagic_result 返回 ulong 供计算,sGetMagic_result 返回 string 如 EURUSD 供日志看。 实测在同一图表加载三次 EA、各给不同幻数,消息框显示三个实例无冲突成交(图 6、图 7 现象)。外汇与贵金属杠杆高、滑点跳空频繁,这种隔离仅降低代码层混淆概率,不改动品种本身风险。 别把正态当圣经 表函数虽好,但写状态表耗时若明显大于写 switch 程序逻辑,就别硬上表。每个 EA 复杂度自己掂量,能在十分钟写完的就别引入类继承。

MQL5 / C++
enum Emagic
  {
   ENUM_DIGITAL_NAME,      class=class="str">"cmt">// EA 数字名称
   ENUM_CODE_INTERACTION, class=class="str">"cmt">// 交互代码
   ENUM_EXPERT_SYMBOL     class=class="str">"cmt">// EA 启动时的交易品种
   };
class="kw">struct Smagic
  {
   class="type">class="kw">ulong                magicnumber;      class=class="str">"cmt">// 幻数是一种组装形式 - 它依顺序写
   class="type">int                  digital_name;    class=class="str">"cmt">// 数字名
   class="type">int                  code_interaction; class=class="str">"cmt">// 交互代码
   class="type">int                  expert_symbol;    class=class="str">"cmt">// EA 启动时的交易品种
   };
class Cmagic
  {
class="kw">protected:
   Smagic               mag;
   SPositionVirtualMagic pvm;
class="kw">public:
class=class="str">"cmt">// 函数返回组装的幻数, 依输入数据组装
   class="type">class="kw">ulong                SetMagic_request(class="type">int digital_name=class="num">0,class="type">int code_interaction=class="num">0);
class=class="str">"cmt">// 函数获取组装的幻数并按组装逻辑拆分
   class="type">class="kw">ulong                SetMagic_result(class="type">class="kw">ulong magicnumber); 

class=class="str">"cmt">// 函数获取返回识别并返回组装幻数的请求部分
   class="type">class="kw">ulong                GetMagic_result(Emagic enum_); 

class=class="str">"cmt">// 函数获取返回的标识符 并返回组装幻数的请求部分文本解释
   class="type">class="kw">string               sGetMagic_result(Emagic enum_);

class=class="str">"cmt">// 返回 EA 的虚拟仓位持仓量
   class="type">class="kw">double               cVOLUME(){class="kw">return(pvm.volume);}

class=class="str">"cmt">// 返回 EA 的虚拟仓位类型
   class="type">ENUM_POSITION_TYPE   cTYPE(){class="kw">return(pvm.type);}
class=class="str">"cmt">// 虚拟仓位计算方法, 返回虚拟仓位存在或不存在

「把幻数拆成三段来用」

在 MT5 里给 EA 挂幻数,最怕多个策略在同一品种上打架。把幻数按 1000 进制拆成三段——EA 数字名、交互识别码、品种解释器代码——就能在持仓里直接反查来源。 组装时数字名和交互码都被限制不能 ≥1000,超了只在日志打印提示而不会中断。最终 magicnumber = digital_name×1000² + code_interaction×1000¹ + expert_symbol,三段各占三位十进制,互不溢出。 下面这段是类里声明与组装函数的核心。注意 symbolexpert() 有两个重载,一个无参取当前品种代码,一个带 int 参数做反向解释,别在调用时传错。 外汇和贵金属杠杆高,幻数只解决订单归属,不预示任何方向;用前请在策略测试器跑一遍多 EA 同品种回测确认不串单。

MQL5 / C++
class="type">bool PositionVirtualMagic(Emagic enum_,
                         class="type">class="kw">string symbol,
                         class="type">class="kw">datetime CurrentTime);
class="kw">private:
class=class="str">"cmt">// 函数拆分幻数为三部分  并返回指定类别的部分
  class="type">int decodeMagic_result(class="type">int category);

class=class="str">"cmt">// 金融工具交易品种的解释器对应数字代码
  class="type">int symbolexpert();

class=class="str">"cmt">// 数字代码的解释器对应的预描述文本 (EA)
  class="type">class="kw">string expertcode(class="type">int code);

class=class="str">"cmt">// 数字代码的解释器对应的预描述文本 (交互)
  class="type">class="kw">string codeinterdescript(class="type">int code);
class=class="str">"cmt">// 数字代码的解释器对应的金融工具交易品种
  class="type">class="kw">string symbolexpert(class="type">int code);

class=class="str">"cmt">// 记录单号周期至缓存区
  class="type">void prHistory_Deals(class="type">class="kw">ulong &buf[],class="type">int HTD);
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 函数返回组装的幻数, 依输入数据组装                                |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">class="kw">ulong Cmagic::SetMagic_request(class="type">int digital_name=class="num">0,class="type">int code_interaction=class="num">0)
  {
  if(digital_name>=class="num">1000)Print("指定 EA 数字名称不正确 (超过 class="num">1000)");
  if(code_interaction>=class="num">1000)Print("指定识别代码不正确 (超过 class="num">1000)");
  mag.digital_name     =digital_name;
  mag.code_interaction =code_interaction;
  mag.expert_symbol    =symbolexpert();
  mag.magicnumber      =mag.digital_name*(class="type">int)pow(class="num">1000,class="num">2)+
mag.code_interaction*(class="type">int)pow(class="num">1000,class="num">1)+
mag.expert_symbol;
  class="kw">return(mag.magicnumber);
  }

常见问题

给每个 EA 分配独立 ORDER_MAGIC,下单时写进 magic 字段,查持仓时按 magic 过滤,互不干扰。
用历史成交按自己幻数反推净仓位,初始化时跑一遍回算,就能接续之前的虚拟持仓状态。
小布可扫描当前品种上各 EA 的幻数占用,提示冲突并给出分段命名建议,省去手动对账。
把幻数拆成三段式(EA ID+方向+批次),分支里直接拼段写入,代码可读性高且不易错。
可能重复开仓或漏平,放大滑点与敞口;回算前务必校验成交时间与魔术段,先模拟再实盘。