在 MetaTrader 5 中实施多货币模式·进阶篇
🔀

在 MetaTrader 5 中实施多货币模式·进阶篇

(2/3)·传统 OnTick 轮询让慢速市场里的多货币 EA 半睡半醒,本篇拆解 MQL5 原生多币模式如何破局

含代码示例偏理论 第 2/3 篇

不少开发者把多货币机器人跑在单一图表 OnTick 上,以为其他品种会跟着动。夜盘流动差时,主图半分钟不跳价,整套系统就躺着,别的货币对再热闹也管不到。这种隐性休眠往往要等漏单才被发现。

◍ EA 里同时挂多个币种侦察器的初始化写法

想在 MT5 里用一个 EA 同时盯 GBPUSD、EURUSD、USDJPY 三根 M1 柱的异动,可以在 OnInit 里连续调 iCustom 把自写指标 iSpy 挂上去,每个品种配不同自定义事件号(0/1/2)以便后续区分来源。 初始化时若某个 iCustom 返回 INVALID_HANDLE,就 Print 报错并直接 return(true) 退出初始化,避免半吊子挂载导致后续事件乱窜。三根都成功才打印 "Spys ok, waiting for events...",说明侦察链路已通。 外汇与贵金属属高风险品种,M1 周期噪声大,这种多品种监视方案只解决「信号送达」问题,入场与否仍需结合自己的价格行为判断。

MQL5 / C++
class="macro">#define VERSION    "class="num">1.00 Build class="num">1 (class="num">26 Dec class="num">2010)"
class="macro">#class="kw">property copyright  "Copyright class="num">2010, Lizar"
class="macro">#class="kw">property link       "[MQL5官方文档]
class="macro">#class="kw">property version    VERSION
class="macro">#class="kw">property description "The Expert Advisor shows the work of iSPY indicator"

class="type">int OnInit()
  {  
   if(iCustom("GBPUSD",PERIOD_M1,"iSpy",ChartID(),class="num">0)==INVALID_HANDLE)
      { Print("Error in setting of spy on GBPUSD"); class="kw">return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"iSpy",ChartID(),class="num">1)==INVALID_HANDLE)
      { Print("Error in setting of spy on EURUSD"); class="kw">return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"iSpy",ChartID(),class="num">2)==INVALID_HANDLE)
      { Print("Error in setting of spy on USDJPY"); class="kw">return(true);}
     
   Print("Spys ok, waiting for events...");
   class="kw">return(class="num">0);
  }

用图表事件抓取跨周期异动信号

MT5 的 OnChartEvent 不只处理鼠标键盘,自定义事件(id >= CHARTEVENT_CUSTOM)常被用来在指标间传递跨周期警报。上面这段把自定义事件号减去基准值,还原成「第几号信号」,同时把 lparam 强转回 ENUM_TIMEFRAMES 读出周期、dparam 读出触发价。 实盘里你可以让一个 M1 上的 iSPY 指标在价格触碰关键位时,用 ChartEvent 往主图丢自定义事件,主图 EA 收到后再统一写日志或弹窗。下面代码逐行拆完,直接开 MT5 建个空 EA 粘进去,切到 EURUSD 的 H1,用脚本发个 ChartEvent(CHARTEVENT_CUSTOM+1, PERIOD_H1, 1.0850, "EURUSD"),专家日志就会打出带秒级时间、周期和价格的行。 别把日志当监控终点 接完事件建议加一层过滤:dparam 与当前 Bid 差超过 3 个点(点值随品种变,XAUUSD 约 0.03)才认定有效,否则噪音事件会刷屏。外汇与贵金属杠杆高,这类跨周期信号只作辅助,触发后仍需人工确认。

MQL5 / C++
class=class="str">"cmt">//| iSPY spy indicator                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,    class=class="str">"cmt">// event id:
                 class=class="str">"cmt">// if id-CHARTEVENT_CUSTOM=class="num">0-"initialization" event
                 const class="type">long&  lparam, class=class="str">"cmt">// chart period
                 const class="type">class="kw">double& dparam, class=class="str">"cmt">// price
                 const class="type">class="kw">string& sparam  class=class="str">"cmt">// symbol
   )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",
            id-CHARTEVENT_CUSTOM,":  ",sparam," ",
            EnumToString((ENUM_TIMEFRAMES)lparam)," price=",dparam);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「用代理指标给多货币事件减负」

直接在 EA 里监听所有交易品种的 NewTick,遇到快速行情会撞上 MT5 事件队列的墙。队列满时旧事件被无声丢弃,多货币指标可能漏掉关键 tick——外汇和贵金属跳空时段这种风险尤其高,实盘前必须在策略测试器压一遍。 原版“间谍”把所有价格变动都往回传,其实大多数跨品种逻辑只关心某几个周期的新柱。把事件收敛成可配置的位标志,既能降负载也方便复用。下面这个枚举就是代理指标的基石,覆盖 M1 到月线共 21 个新柱位加上 tick 与初始化,合计至少 23 种事件可派发。 [CODE] enum ENUM_CHART_EVENT_SYMBOL { CHARTEVENT_INIT =0, // "Initialization" event CHARTEVENT_NO =0, // No events CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on M1 chart CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on M2 chart CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on M3 chart CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on M4 chart CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on M5 chart CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on M6 chart CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on M10 chart CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on M12 chart CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on M15 chart CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on M20 chart CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on M30 chart CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on H1 chart CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart CHARTEVENT_TICK =0x00200000, // "New tick" event CHARTEVENT_ALL =0xFFFFFFFF, // All events }; //+------------------------------------------------------------------+ 逐行拆解:enum 定义事件标志集;INIT 与 NO 同值为 0,分别代表初始化与无事件;M1–MN1 每行占用一个独立 bit(0x1 到 0x100000),用十六进制移位区分周期;TICK 为 0x200000 单独捕获报价;ALL 用全 F 掩码表示订阅全部。写输入参数时拿 OR 拼,比如 CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_H1 就只收这两周期新柱。 代理指标靠 OnChartEvent() 向外抛自定义事件:id 是标识符,lparam 带上面枚举值,dparam 给新柱开盘价或变动价,sparam 是品种名。EA 侧按 sparam 分流品种即可,不必自己挂间谍。 别把事件标识符当摆设 id 字段看似冗余,因为 sparam 已经能定位品种,但它空着也是浪费。可以塞进你自己的子命令码,比如区分“重连”和“正常新柱”,让一个事件通道承载更多控制信息。 代理不必寄生在 EA 里 它生成的全是外部事件,直接挂独立图表指标就行,EA 通过事件接收,互不阻塞。这样改品种订阅不用动 EA 属性,行情屏里勾掉某品种,对应事件就停发,调参路径短很多。

MQL5 / C++
enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_INIT       =class="num">0,        class=class="str">"cmt">// "Initialization" event
   CHARTEVENT_NO         =class="num">0,        class=class="str">"cmt">// No events
   CHARTEVENT_NEWBAR_M1 =0x00000001, class=class="str">"cmt">// "New bar" event on M1 chart
   CHARTEVENT_NEWBAR_M2 =0x00000002, class=class="str">"cmt">// "New bar" event on M2 chart
   CHARTEVENT_NEWBAR_M3 =0x00000004, class=class="str">"cmt">// "New bar" event on M3 chart
   CHARTEVENT_NEWBAR_M4 =0x00000008, class=class="str">"cmt">// "New bar" event on M4 chart

   CHARTEVENT_NEWBAR_M5 =0x00000010, class=class="str">"cmt">// "New bar" event on M5 chart
   CHARTEVENT_NEWBAR_M6 =0x00000020, class=class="str">"cmt">// "New bar" event on M6 chart
   CHARTEVENT_NEWBAR_M10=0x00000040, class=class="str">"cmt">// "New bar" event on M10 chart
   CHARTEVENT_NEWBAR_M12=0x00000080, class=class="str">"cmt">// "New bar" event on M12 chart

   CHARTEVENT_NEWBAR_M15=0x00000100, class=class="str">"cmt">// "New bar" event on M15 chart
   CHARTEVENT_NEWBAR_M20=0x00000200, class=class="str">"cmt">// "New bar" event on M20 chart
   CHARTEVENT_NEWBAR_M30=0x00000400, class=class="str">"cmt">// "New bar" event on M30 chart
   CHARTEVENT_NEWBAR_H1 =0x00000800, class=class="str">"cmt">// "New bar" event on H1 chart

   CHARTEVENT_NEWBAR_H2 =0x00001000, class=class="str">"cmt">// "New bar" event on H2 chart
   CHARTEVENT_NEWBAR_H3 =0x00002000, class=class="str">"cmt">// "New bar" event on H3 chart
   CHARTEVENT_NEWBAR_H4 =0x00004000, class=class="str">"cmt">// "New bar" event on H4 chart
   CHARTEVENT_NEWBAR_H6 =0x00008000, class=class="str">"cmt">// "New bar" event on H6 chart

   CHARTEVENT_NEWBAR_H8 =0x00010000, class=class="str">"cmt">// "New bar" event on H8 chart
   CHARTEVENT_NEWBAR_H12=0x00020000, class=class="str">"cmt">// "New bar" event on H12 chart
   CHARTEVENT_NEWBAR_D1 =0x00040000, class=class="str">"cmt">// "New bar" event on D1 chart
   CHARTEVENT_NEWBAR_W1 =0x00080000, class=class="str">"cmt">// "New bar" event on W1 chart

   CHARTEVENT_NEWBAR_MN1=0x00100000, class=class="str">"cmt">// "New bar" event on MN1 chart
   CHARTEVENT_TICK        =0x00200000, class=class="str">"cmt">// "New tick" event

   CHARTEVENT_ALL         =0xFFFFFFFF, class=class="str">"cmt">// All events
  };
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用自定义事件把多图联动跑起来

在 MT5 里想让一个品种图表去驱动另一张图上的 EA 或指标,靠的是自定义图表事件。下面这段代理指标的核心,就是把自身所在图表的新 tick 或新 bar 信号,以 EventCustom 推送到指定 chart_id 的窗口。 输入参数有三个关键点:chart_id 是接收事件的图表句柄,custom_event_id 是事件编号,flag_event 用 ENUM_CHART_EVENT_SYMBOL 决定只发 tick、只发 bar 还是都发。初载时 prev_calculated 为 0,代码会先丢一个 CHARTEVENT_INIT 出去,把 prev_time 对齐到当前时间。 OnCalculate 里取 price[rates_total-1] 作为最新价,每次调用比对 time 与 prev_time 的分钟、小时、日、月。若都没变就直接 return,只有跨越时间边界才触发后续逻辑。外汇与贵金属杠杆高,这类跨图事件若用于实盘信号,先在策略测试器用 2020 年 3 月黄金跳空段验证延迟。 [CODE] 里的代码可直接存成 .mq5 挂到任意周期。想验证联动,开两个 EURUSD 图表,一个挂此指标填另一个 chart_id,接收端用 OnChartEvent 打印 custom_event_id 即可看到推送。

MQL5 / C++
class=class="str">"cmt">//|                                                                 Spy Control panel MCM.mq5 |
class=class="str">"cmt">//|                                                                 Copyright class="num">2010, Lizar |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define VERSION    "class="num">1.00 Build class="num">3 (class="num">26 Dec class="num">2010)"
class="macro">#class="kw">property copyright  "Copyright class="num">2010, Lizar"
class="macro">#class="kw">property link       "[MQL5官方文档]
class="macro">#class="kw">property version    VERSION
class="macro">#class="kw">property description "This is the MCM Control Panel agent-indicator."
class="macro">#class="kw">property description "Is launched on the required symbol on any time-frame"
class="macro">#class="kw">property description "and generates the custom NewBar event and/or NewTick "
class="macro">#class="kw">property description "for the chart which receives the event"
class="macro">#class="kw">property indicator_chart_window

input class="type">long                 chart_id;                class=class="str">"cmt">// identifier of the chart which receives the event
input class="type">class="kw">ushort               custom_event_id;        class=class="str">"cmt">// event identifier
input ENUM_CHART_EVENT_SYMBOL flag_event=CHARTEVENT_NO;class=class="str">"cmt">// indicator, which determines the event type.
class="type">MqlDateTime time, prev_time;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,     class=class="str">"cmt">// size of the price[] array
                 const class="type">int prev_calculated, class=class="str">"cmt">// bars processed at the previous call
                 const class="type">int begin,           class=class="str">"cmt">// where the data begins
                 const class="type">class="kw">double& price[]      class=class="str">"cmt">// calculations array
  )
  {
   class="type">class="kw">double price_current=price[rates_total-class="num">1];
   TimeCurrent(time);

   if(prev_calculated==class="num">0)
     {
      EventCustom(CHARTEVENT_INIT,price_current);
      prev_time=time;
      class="kw">return(rates_total);
     }

class=class="str">"cmt">//--- new tick
   if((flag_event & CHARTEVENT_TICK)!=class="num">0) EventCustom(CHARTEVENT_TICK,price_current);
class=class="str">"cmt">//--- check change time
   if(time.min==prev_time.min &&
      time.hour==prev_time.hour &&
      time.day==prev_time.day &&
      time.mon==prev_time.mon) class="kw">return(rates_total);

把非标周期事件塞进自定义回调

这段逻辑干的事很直接:在 OnCalculate 里抓到新柱信号后,按分钟、小时、日、周逐级用取模判断,把 2 分、3 分、15 分乃至 8 小时这类 MT5 原生没有的周期,转成自定义图表事件抛出去。 注意 time.min%2==0 只在偶数分钟触发 M2,%3 对应 M3,一直到 %30 的 M30;小时侧同理,hour%8==0 才发 H8,%12 发 H12。周线比较粗暴,day_of_week==1(周一)就当新周起点。 代码里每一档都先用 flag_event 位运算过滤,没开的周期直接跳过,所以实盘占用取决于你订阅了哪几个。外汇和贵金属杠杆高,这类自定义事件若驱动 EA 下单,回测前务必在策略测试器跑一遍不同点差环境。 最后几行在非整点/非整日时更新 prev_time 并 return,避免重复发事件;真正跨日、跨周、跨月的分支落在后面,逻辑上把‘新柱’概念从 M1 一路扩到了月线。

MQL5 / C++
class=class="str">"cmt">//--- new minute
   if((flag_event & CHARTEVENT_NEWBAR_M1)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current);
   if(time.min%class="num">2 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M2)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_M2,price_current);
   if(time.min%class="num">3 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M3)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_M3,price_current);
   if(time.min%class="num">4 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M4)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_M4,price_current);
   if(time.min%class="num">5 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M5)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_M5,price_current);
   if(time.min%class="num">6 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M6)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_M6,price_current);
   if(time.min%class="num">10==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M10)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_M10,price_current);
   if(time.min%class="num">12==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M12)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_M12,price_current);
   if(time.min%class="num">15==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M15)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_M15,price_current);
   if(time.min%class="num">20==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M20)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_M20,price_current);
   if(time.min%class="num">30==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_M30)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_M30,price_current);
   if(time.min!=class="num">0) {prev_time=time; class="kw">return(rates_total);}
class=class="str">"cmt">//--- new hour
   if((flag_event & CHARTEVENT_NEWBAR_H1)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_H1,price_current);
   if(time.hour%class="num">2 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_H2)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_H2,price_current);
   if(time.hour%class="num">3 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_H3)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_H3,price_current);
   if(time.hour%class="num">4 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_H4)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_H4,price_current);
   if(time.hour%class="num">6 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_H6)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_H6,price_current);
   if(time.hour%class="num">8 ==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_H8)!=class="num">0)   EventCustom(CHARTEVENT_NEWBAR_H8,price_current);
   if(time.hour%class="num">12==class="num">0 && (flag_event & CHARTEVENT_NEWBAR_H12)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_H12,price_current);
   if(time.hour!=class="num">0) {prev_time=time; class="kw">return(rates_total);}
class=class="str">"cmt">//--- new day
   if((flag_event & CHARTEVENT_NEWBAR_D1)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_D1,price_current);
class=class="str">"cmt">//--- new week
   if(time.day_of_week==class="num">1 && (flag_event & CHARTEVENT_NEWBAR_W1)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_W1,price_current);
class=class="str">"cmt">//--- new month
把跨品种同步检查交给小布
多交易品种柱时间对齐和未完成柱复制错误的排查很耗神,小布盯盘的 AIGC 已内置品种间节奏诊断,打开对应品种页即可看到哪些关联对不同步。把重复劳动交给小布,你专注决策。

常见问题

MT5 沿用了仅在有价变时才绘柱的机制,各品种缺失柱概率不同,同序号柱不代表时间同步,多币指标重算时必须按时间对齐而非按序号。
周期拉长会显著降低柱缺失概率,睡眠与同步问题变弱,但当前未完成柱的新柱形成时刻差异仍在,评估准确性仍可能受影响。
主图出新柱不意味着其他品种也出新柱,过早用 Copy 系列函数拉取可能命中旧柱或触发复制错误,需先确认目标品种新柱已成型。
可以,小布盯盘内置了关联品种节奏与柱对齐诊断,能直观察看哪些货币对处于非同步状态,减少手动逐一对表的时间。
它脱离对当前图表价变的单一依赖,改为独立调度各品种数据获取与计算,使系统在任意品种活跃时都能响应,而非随主图休眠。