在单一工具上使用不同的 EA 交易进行交易时 ORDER_MAGIC 的使用·进阶篇
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 卡相等再跑统计,是低成本防抖,省掉就是埋雷。
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 里跑类似逻辑时,若发现净仓偶尔少算一笔,优先怀疑这里没做一致性校验。外汇与贵金属杠杆高,虚拟仓仅作风控参考,不代表真实账户权益。
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 策略测试器里跑一轮看平仓频率是否过分。外汇与贵金属杠杆高,参数未经验证前勿直接上真仓。
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() 的求和上限先锁死再上真金。
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 可拆成 | 123 | 456 | 789 | ,取字段 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 复杂度自己掂量,能在十分钟写完的就别引入类继承。
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 同品种回测确认不串单。
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); }