在 MetaTrader 5 中实施多货币模式·进阶篇
(2/3)·传统 OnTick 轮询让慢速市场里的多货币 EA 半睡半醒,本篇拆解 MQL5 原生多币模式如何破局
不少开发者把多货币机器人跑在单一图表 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 周期噪声大,这种多品种监视方案只解决「信号送达」问题,入场与否仍需结合自己的价格行为判断。
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)才认定有效,否则噪音事件会刷屏。外汇与贵金属杠杆高,这类跨周期信号只作辅助,触发后仍需人工确认。
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 属性,行情屏里勾掉某品种,对应事件就停发,调参路径短很多。
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 即可看到推送。
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 一路扩到了月线。
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