在单一工具上使用不同的 EA 交易进行交易时 ORDER_MAGIC 的使用(基础篇)
用 ORDER_MAGIC 隔离同一品种的多个 EA
在同一交易品种上同时挂多个 EA 时,订单归属最容易乱。MT5 里每个订单、持仓、挂单都带一个 magic number(魔术码),本质是整型标识,用来区分「这笔单是哪个程序下的」。 不显式设置 magic,所有 EA 会共用 0 值,平仓、修改、统计时就会互相踩。给每个 EA 分配独立 ORDER_MAGIC,才能在代码中精准筛选属于自己的仓位。 实测场景:一个 XAUUSD 图表上跑两个马丁 EA,若 magic 都为 0,其中一个触发风控平仓会把另一个的浮亏单也清掉。分开设 magic 后,各自遍历自己订单,互不干扰。外汇与贵金属杠杆高,多 EA 同品种叠加会放大回撤风险,隔离标识是底线操作。
「被浪费的 18 万亿级幻数空间」
MQL5 里每笔挂单都能写一个 magic number(幻数),用来标记这是哪个 EA 下的单。多数开发者把它当成随便填的记号,实际上幻数类型是 ulong,上限 18 446 744 073 709 551 615,空间大到能单独编码上千个 EA 还有余。 现实很尴尬:圈子里一半人用 12345,另一半扎堆 55555、33333、77777。光用数字 1000 就足够给所有 EA 起独立名号,而 ulong 还剩 15 个完整数位类别没动。按编码规则把「EA ID + 品种 + 友邻标识」压进幻数,多 EA 协作才不会互相踩脚。 把幻数只当 ID 不够。EA 跑在 EURUSD 不代表委托只显在这品种,把品种和运行身份一起编进幻数,EA 才能认出「这单是自己人下的」。两个 EA 同品种不同周期(如 M1 与 H1 的 EURUSD)若共享幻数,会抢着改同一仓位导致冲突——图2 就是实证。 下面这段辅助类演示了怎么在发单时把 magic 写进请求结构。trades.magic = magic 那行就是 EA 印记,后面价格、SL、TP 都按类型算好再填。
class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 这个类提供辅助交易计算 | class=class="str">"cmt">//+------------------------------------------------------------------ class CProvision { class="kw">protected: class="type">MqlTradeRequest trades; class=class="str">"cmt">// OrderSend 请求结构的指针 class="kw">public: class="type">int TYPE(const class="type">class="kw">double &v[]); class=class="str">"cmt">// 判断类型,相对于移动的读数 class="type">class="kw">double pricetype(class="type">int type); class=class="str">"cmt">// 计算开仓位置, 相对于类型 class="type">class="kw">double SLtype(class="type">int type); class=class="str">"cmt">// 计算止损位置,相对于类型 class="type">class="kw">double TPtype(class="type">int type); class=class="str">"cmt">// 计算时间表位置, 相对于类型 class="type">long spread(); class=class="str">"cmt">// 返回当前金融工具点差 class="type">int SendOrder(ENUM_ORDER_TYPE type,class="type">class="kw">double volume); }; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int CProvision::SendOrder(ENUM_ORDER_TYPE type,class="type">class="kw">double volume) { trades.action =TRADE_ACTION_DEAL; class=class="str">"cmt">// 实施操作类型 trades.magic =magic; class=class="str">"cmt">// EA 印记 (magic number 的标识符) trades.symbol =_Symbol; class=class="str">"cmt">// 交易工具的名称 trades.volume =volume; class=class="str">"cmt">// 交易请求的交易量 trades.price =pricetype((class="type">int)type); class=class="str">"cmt">// 价格 trades.sl =SLtype((class="type">int)type); class=class="str">"cmt">// 订单止损位置
◍ 挂单参数与方向判定的底层函数
这段逻辑把下单请求的结构体字段一次性填完:止盈位由 TPtype 按方向算,偏离点数直接取当前点差的整数值,成交方式锁死为 ORDER_FILLING_FOK,也就是要么按价全成要么不吃单。 TYPE 函数拿数组前两个元素做差,t 为 0 时强行置 1.0 避免除零,再用 0.5*t/fabs(t)+0.5 把结果映射成 0 或 1 的方向整数——相当于把价格波动读数压缩成多空标签。 pricetype、SLtype、TPtype 三个函数都先 SymbolInfoTick 抓实时 tick:type=0 走 ask 基准,type=1 走 bid 基准。SL 和 TP 以 SYMBOL_POINT 为最小单位乘系数偏移,外汇与贵金属点值随品种跳动,实盘前应在 MT5 用 Print(SymbolInfoDouble(_Symbol,SYMBOL_POINT)) 确认步长。 spread 函数仅返回 SYMBOL_SPREAD 整数,意味着偏离容差等于经纪商报的点差,在黄金跳空时段点差可能瞬间扩到平时 3~5 倍,FOK 单被拒概率会明显上升。
trades.tp =TPtype((class="type">int)type); class=class="str">"cmt">// 订单获利位置 trades.deviation=(class="type">int)spread(); class=class="str">"cmt">// 距请求价格的最大偏离点数 trades.type=type; class=class="str">"cmt">// 订单类型 trades.type_filling=ORDER_FILLING_FOK; if(OrderSend(trades,res)){class="kw">return(res.retcode);} class="kw">return(-class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 判断类型,相对于移动的读数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int CProvision::TYPE(const class="type">class="kw">double &v[]) { class="type">class="kw">double t=v[class="num">0]-v[class="num">1]; if(t==class="num">0.0)t=class="num">1.0; class="kw">return((class="type">int)(class="num">0.5*t/fabs(t)+class="num">0.5)); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 计算开仓位置, 相对于类型 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">double CProvision::pricetype(class="type">int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==class="num">0)class="kw">return(tick.ask); if(type==class="num">1)class="kw">return(tick.bid); } class="kw">return(-class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 计算止损位置, 相对于类型 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">double CProvision::SLtype(class="type">int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==class="num">0)class="kw">return(tick.bid-SL*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); if(type==class="num">1)class="kw">return(tick.ask+SL*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 计算时间帧位置, 相对于类型 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">double CProvision::TPtype(class="type">int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==class="num">0)class="kw">return(tick.bid+TP*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); if(type==class="num">1)class="kw">return(tick.ask-TP*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 返回点差 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">long CProvision::spread() { class="kw">return(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD)); }
均线开关仓的句柄与缓存写法
下面这段 EA 骨架把均线信号转成开平仓动作,核心是先拿指标句柄、再用 CopyBuffer 取最近两根 MA 值。输入参数里 SL=300 点、TP=1000 点、MA_Period=25、lot=0.1,属典型小止损宽止盈的剥头皮式配置,外汇与贵金属品种下杠杆放大,这类参数回撤可能很快,实盘前务必在策略测试器跑历史数据。 初始化里只用 iMA() 建句柄,周期传 0 表示当前图表周期,平滑用 MODE_SMA、价格用 PRICE_OPEN,意味着信号看每根 K 线开盘价的 25 期简单均线。OnTick 中 CopyBuffer 取 2 个值进 v[2],若返回值 ≤0 直接打印错误并退出,避免脏数据触发下单。 prov.TYPE(v) 把双值缓存判成方向类型 type_MA,随后 PositionSelect(_Symbol) 查当前品种有无持仓;若有且持仓方向与 type_MA 不一致,就进入平仓判定分支。整段没硬编码买卖价,具体发单逻辑留待后续小节,但句柄与缓存这段已是 MT5 跑通策略的必经路。
class=class="str">"cmt">//--- 输入参数 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=class="str">"cmt">//--- 我们将保存指标句柄 class="type">int MA_handle, class=class="str">"cmt">// 指标句柄 type_MA, class=class="str">"cmt">// MA 的指定方向类型 rezult; class=class="str">"cmt">// 存放 OrderSend 操作的结果变量 class="type">class="kw">double v[class="num">2]; class=class="str">"cmt">// 接受 MA 数值的缓存区 class="type">MqlTradeResult res; class=class="str">"cmt">// OrderSend 响应结构的指针 class="type">MqlTick tick; class=class="str">"cmt">// 最后市场信息结构的指针 CProvision prov; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| EA 初始化函数 class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnInit() { class=class="str">"cmt">//--- 创建指标的句柄 MA_handle=iMA(Symbol(),class="num">0,MA_Period,MA_shift,MA_smooth,price); class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| EA 即时价格处理函数 class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void OnTick() { if(CopyBuffer(MA_handle,class="num">0,class="num">0,class="num">2,v)<=class="num">0) {Print("#",magic,"复制错误");class="kw">return;} type_MA=prov.TYPE(v); class=class="str">"cmt">// 依据 MA 指示判断类型 if(PositionSelect(_Symbol))class=class="str">"cmt">// 如果此时有开仓 { if(PositionGetInteger(POSITION_TYPE)!=type_MA)class=class="str">"cmt">// 检查是否是平仓时机 {
「反手与首仓的下单分支」
这段逻辑把「已有反向仓位」和「无任何仓位」两种情况分开了。若检测到同 magic 标识下存在持仓,策略会先打印该仓的成交量与类型,再以原持仓量加新增 lot 的方式反手加仓,外汇与贵金属品种在此处杠杆放大,高风险属性不容忽视。 无仓位时则直接以 lot 手数首开。两种路径都走 prov.SendOrder 并判断返回值:不等于 -1 视为发单成功,打印结果代码与成交量;等于 -1 则输出 GetLastError 并 return 中断,避免错误单继续堆叠。 想验证分支是否生效,把下面代码贴进 EA 的持仓判断段,观察终端里 #magic 开头的日志,能直接看到反向仓和首仓各自的 SendOrder 反馈。
Print("#",magic,"标识号对应的仓位持仓量 ",PositionGetDouble(POSITION_VOLUME), " 反向仓位类型 ",PositionGetInteger(POSITION_TYPE)," 依据 ",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,PositionGetDouble(POSITION_VOLUME)+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,"标识号对应的仓位持仓量 ",PositionGetDouble(POSITION_VOLUME), " 开仓类型 ",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;} } }
◍ 用幻数切分虚拟仓位避开EA互踩
MT5 的内置仓位函数(PositionGetDouble / PositionGetInteger / PositionGetString)只按品种返回实际持仓,不区分「是谁下的单」。但 ORDER_MAGIC、DEAL_MAGIC、POSITION_MAGIC 是同一个用户数字,建仓时 POSITION_MAGIC 取自 DEAL_MAGIC,而 DEAL_MAGIC 又来自委托的 ORDER_MAGIC——也就是说,系统能识别某笔成交属于哪个幻数,却没法直接用一个幻数拉出对应仓位。 自己包一层类就能补这个洞。下面这段是 MT5 原生仓位读取接口与虚拟仓位结构的对照骨架,先看清原生能力边界: // 返回开仓数量. int PositionsTotal(); // 返回开仓列表中相应号码位置的交易品种. string PositionGetSymbol(int index); // 选择开仓用于将来工作. bool PositionSelect(string symbol, uint timeout=0); // 函数返回开仓的请求属性. double PositionGetDouble(ENUM_POSITION_PROPERTY property_id); // 函数返回开仓的请求属性. long PositionGetInteger(ENUM_POSITION_PROPERTY property_id); // 函数返回开仓的请求属性. string PositionGetString(ENUM_POSITION_PROPERTY property_id); struct SPositionVirtualMagic { double volume; ENUM_POSITION_TYPE type; }; class CPositionVirtualMagic { protected: SPositionVirtualMagic pvm; public: … }; 逻辑上把买、卖成交按幻数分别累加再相减:0.2 手卖加 0.3 手买,净仓就是 0.1 手买。类里用 while 循环后置判断加载历史,HistoryDealsTotal() 前后各取一次,若数值变了就重跑,避免别的 EA 把历史深度改掉导致漏单。 实测在同一品种上跑三个 EA,各给不同时间框架与幻数(例如 1001 / 1002 / 1003),彼此不抢仓、不冲突。外汇与贵金属杠杆高,这种隔离只解决逻辑互踩,不等于降低爆仓风险,参数请先在策略测试器验证。
class=class="str">"cmt">// 返回开仓数量. class="type">int PositionsTotal(); class=class="str">"cmt">// 返回开仓列表中相应号码位置的交易品种. class="type">class="kw">string PositionGetSymbol(class="type">int index); class=class="str">"cmt">// 选择开仓用于将来工作. class="type">bool PositionSelect(class="type">class="kw">string symbol, class="type">uint timeout=class="num">0); class=class="str">"cmt">// 函数返回开仓的请求属性. class="type">class="kw">double PositionGetDouble(ENUM_POSITION_PROPERTY property_id); class=class="str">"cmt">// 函数返回开仓的请求属性. class="type">long PositionGetInteger(ENUM_POSITION_PROPERTY property_id); class=class="str">"cmt">// 函数返回开仓的请求属性. class="type">class="kw">string PositionGetString(ENUM_POSITION_PROPERTY property_id); class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| CPositionVirtualMagic 类结构 | class=class="str">"cmt">//+------------------------------------------------------------------ class="kw">struct SPositionVirtualMagic { class="type">class="kw">double volume; class=class="str">"cmt">// 虚拟仓位持仓量 class="type">ENUM_POSITION_TYPE type; class=class="str">"cmt">// 虚拟仓位类型 }; class=class="str">"cmt">//+--------------------------------------------------------------------------------+ class=class="str">"cmt">//| 本类依 EA 的魔幻号计算虚拟仓位 | class=class="str">"cmt">//+--------------------------------------------------------------------------------+ class CPositionVirtualMagic { class="kw">protected: SPositionVirtualMagic pvm; class="kw">public: