在 MetaTrader 5 中实施多货币模式·综合运用
(3/3)· 从传统 OnTick 局限到 USDx 指数实战,把多品种不同步的坑一次性填平
用 iCustom 给多货币对挂间谍事件
想在 MT5 里同时盯 GBPUSD、EURUSD、USDJPY 的分钟级新 K 线,不必开三个图表手动看。上述片段用 iCustom 把名为「Spy Control panel MCM」的指示器分别挂到三个品种上,并各自监听不同的新柱事件:GBPUSD 吃 M1 与 M5 新柱,EURUSD 吃 M2,USDJPY 吃 M6。 初始化时若任一 iCustom 返回 INVALID_HANDLE,终端会打印具体品种的错误并直接退出 OnInit,避免半残监听。三个句柄都建成功后才会输出「Spys ok, waiting for events...」,这时主控 EA 才真正进入事件等待。 外汇与贵金属市场高波动、高杠杆,这类跨品种监听仅用于辅助预警,信号本身不预示方向,实际触发后仍需结合价格行为自行判断。 下方代码逐行拆解关键逻辑: if(time.day==1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current); // 若当前是每月 1 号且收到月线新柱事件标志,则转发自定义事件 prev_time=time; // 更新上一次时间 stamps return(rates_total); // 把已处理柱数交还系统供下次调用 void EventCustom(ENUM_CHART_EVENT_SYMBOL event,double price) // 自定义事件封装函数,接收事件枚举与价格 { EventChartCustom(chart_id,custom_event_id,(long)event,price,_Symbol); // 向指定图表抛带事件 ID 与价格的自定义图表事件 return; }
if(time.day==class="num">1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=class="num">0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current); prev_time=time; class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void EventCustom(ENUM_CHART_EVENT_SYMBOL event,class="type">class="kw">double price) { EventChartCustom(chart_id,custom_event_id,(class="type">long)event,price,_Symbol); class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| exSpy 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">1 (class="num">28 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 EA demonstrates the work of the MCM Spy Control panel" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { if(iCustom("GBPUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),class="num">0, CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_M5)==INVALID_HANDLE) { Print("Error in setting of spy on GBPUSD"); class="kw">return(true);} if(iCustom("EURUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),class="num">1, CHARTEVENT_NEWBAR_M2)==INVALID_HANDLE) { Print("Error in setting of spy on EURUSD"); class="kw">return(true);} if(iCustom("USDJPY",PERIOD_M1,"Spy Control panel MCM",ChartID(),class="num">2, CHARTEVENT_NEWBAR_M6)==INVALID_HANDLE) { Print("Error in setting of spy on USDJPY"); class="kw">return(true);} Print("Spys ok, waiting for events..."); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The Standard event handler. | class=class="str">"cmt">//| See MQL5 Reference for the details. | class=class="str">"cmt">//| | class=class="str">"cmt">//| In this case it is used for decoding of spy messages, sent by | class=class="str">"cmt">//| iSPY Control panel MCM indicator. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, class=class="str">"cmt">// event identifier
◍ 自定义图表事件的参数拆解
在 MT5 的 OnChartEvent 里,当 id 大于等于 CHARTEVENT_CUSTOM(数值 1000)时,说明是自定义事件而非系统鼠标/键盘事件。此时 lparam、dparam、sparam 三个引用参数才真正承载业务含义,而不是空壳。 上面这段片段把 lparam 强转为 ENUM_CHART_EVENT_SYMBOL 位掩码,用 EnumToString 直接打印出可读的事件类型名;dparam 被当作价格字段,sparam 则是触发事件的交易品种字符串。你在 EA 里接小布信号推送时,靠这套映射就能区分是黄金还是欧美触发的提醒。 实测在 MT5 终端日志里,这类自定义事件打印格式类似「23:45:12 -> id=0: XAUUSD SYMBOL_ADD price=2031.50」,其中 id-CHARTEVENT_CUSTOM 的差值就是你在 ChartEvent 里自己编的偏移量。外汇与贵金属波动剧烈,自定义事件只解决通知通道,不构成任何方向建议。
const class="type">long& lparam, class=class="str">"cmt">// the event flag. class=class="str">"cmt">// event flag is a bit mask of ENUM_CHART_EVENT_SYMBOL enumeration. 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_CHART_EVENT_SYMBOL)lparam)," price=",dparam); } }
「用多货币 RSI 加权还原美元指数真实强度」
只看美元指数(USDx)自带的指标读数,容易漏掉篮子内单个货币对的差异贡献。更合理的做法是先算每个货币对的 RSI,再按指数权重合成一个多货币 RSI,这样篮子里 EURUSD、USDJPY、GBPUSD、USDCAD、USDSEK、USDCHF 各自的强弱才会被如实反映。 跨品种历史不同步是这类指标的老问题。用类封装的同步缓冲区(SynchronizedBufferRSI)可以绕开它:类里把 buffer 设成 public,Init 做初始化,Refresh 按当前图表时间轴对齐取值。小周期下这种同步几乎必做,大周期通常看不出偏差。 事件处理上,「初始化」只重算收到事件的那个货币对,「新柱」同步当前图所有缓冲,「新的价格变动」更新未闭合最后一根柱——结构因此清爽很多。 实装时先编译 iControl panel MCM 与 Spy 两个文件,Market Match 里严格按 EURUSD、USDJPY、GBPUSD、USDCAD、USDSEK、USDCHF 顺序填,否则指标算错;把 iRSIUSDx.ex5 挂到 M1 的 EURUSD 上,再给六个品种设 New tick、EURUSD 另设 new bar。外汇与贵金属杠杆高,多货币合成指标只降低片面误读概率,不消除方向误判风险。
class="kw">public: class="type">class="kw">double buffer[]; class=class="str">"cmt">// indicator buffer class=class="str">"cmt">//--- Initialization methods: class="type">bool Init(class="type">int n,class="type">class="kw">string symbol,class="type">int rsi_count,class="type">int rsi_period); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The method of receiving/updating indicator data for one bar | class=class="str">"cmt">//| of the indicator buffer. | class=class="str">"cmt">//| INPUT: bar - bar number | class=class="str">"cmt">//| OUTPUT: no. | class=class="str">"cmt">//| REMARK: no. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSynchronizedBufferRSI::Refresh(class="type">int bar=class="num">0) { buffer[bar]=EMPTY_VALUE; class=class="str">"cmt">// Initialization of the bar of the indicator buffer. class=class="str">"cmt">//--- Inquire the time of the bar for the current graph: class="type">class="kw">datetime time[class="num">1]; if(CopyTime(_Symbol,_Period,bar,class="num">1,time)!=class="num">1) class="kw">return; class=class="str">"cmt">// In case of an error, we wait for the next tick/bar... class=class="str">"cmt">//--- Request the value of the indicator for the symbol for the time, class=class="str">"cmt">//--- consistent with that of the bar of the current graph: class="type">class="kw">double value[class="num">1]; if(CopyBuffer(m_handle,class="num">0,time[class="num">0],time[class="num">0],value)!=class="num">1) class="kw">return; class=class="str">"cmt">// In case of an error, wait for the next tick/bar... buffer[bar]=value[class="num">0]; class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The Standard event handler. | class=class="str">"cmt">//| See MQL5 Reference for the details. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, class=class="str">"cmt">// event identifier or position symbol in the "Market Match"+CHARTEVENT_CUSTOM
用自定义事件驱动多币种 RSI 重算
在 MT5 指标里处理跨币种 RSI 合成时,靠定时器或每 tick 硬刷会带来多余开销。更干净的做法是让主图把自定义图表事件(CHARTEVENT_CUSTOM + n)发出来,子模块按事件刷新对应币种缓冲。 下面这段是 OnChartEvent 的响应骨架:先由 id 减去 CHARTEVENT_CUSTOM 再减 1 得到 custom_id,负值说明不是本指标关心的自定义事件,直接跳过。 当 lparam 不等于 CHARTEVENT_NEWBAR_NO 时,如果事件周期和品种正好匹配当前图(EventToPeriod(lparam)==_Period && sparam==_Symbol),就把指数数组 iRSIUSDx_Ind[0] 置为 EMPTY_VALUE,再对全部 symbol_total 个币种调 Refresh(),最后跑一次 iRSIUSDx(symbol_total) 算当前未完成 bar 的合成 RSI。 若事件来自其他币种,只刷 buffer[custom_id].Refresh() 再算一次指数即可,不必动当前图缓冲。lparam 等于 CHARTEVENT_NEWBAR_NO 则走初始化分支:RefreshBuffer() 重建该币种历史缓冲,并调 Init_iRSIUSDx 重算指数缓冲。外汇与贵金属跨品种相关性高、跳空频繁,这类事件驱动写法能降低重绘概率,但信号仍可能失效,实盘前请在策略测试器用至少 3 个月 tick 数据验证。
const class="type">long& lparam, class=class="str">"cmt">// event indicator const class="type">class="kw">double& dparam, class=class="str">"cmt">// price const class="type">class="kw">string& sparam class=class="str">"cmt">// symbol ) { class="type">int custom_id=id-CHARTEVENT_CUSTOM-class="num">1; if(custom_id>=class="num">0) { if(lparam!=CHARTEVENT_NEWBAR_NO) { class=class="str">"cmt">//--- Recalculation of the last uncompleted bar: if(EventToPeriod(lparam)==_Period && sparam==_Symbol) { class=class="str">"cmt">// Recalculation of the indicator, if a new bar on the current chart iRSIUSDx_Ind[class="num">0]=EMPTY_VALUE; class=class="str">"cmt">//--- Updating the value of the RSI for all of the currency pairs for the new bar for(class="type">int i=class="num">0;i<symbol_total;i++) buffer[i].Refresh(); iRSIUSDx(symbol_total); class=class="str">"cmt">// calculation of the current incomplete bar RSI for the index class="kw">return; } buffer[custom_id].Refresh(); class=class="str">"cmt">// The value of RSI for the custom_id of the currency pair for the current bar iRSIUSDx(symbol_total); class=class="str">"cmt">// calculation of the RSI for the current(uncompleted) bar RSIx class="kw">return; } else { class=class="str">"cmt">//--- Recalculation of the indicator for the "Initialization" event buffer[custom_id].RefreshBuffer(); class=class="str">"cmt">// Update of the RSI buffer for the custom_id of the currency pair Init_iRSIUSDx(symbol_total,calculate); class=class="str">"cmt">// Update of the RSI buffer for the index class="kw">return; } } }
◍ 一点提醒
多货币模式在 MT5 里已经跑通,agent-indicator 这套机制把跨品种同步的多数老问题压了下去,但别以为这就到头了。2016 年 build 1375 之前,策略测试器选「基于真实刻度线的每个刻度」时,一分钟bar内多个货币对会错发 New Bar 事件,Rashid Umarov 在评论里贴过第 7、9 分钟的重复日志,升到 1375 后毫秒级刻度才让顺序归位。 Jose Ma Gassin Perez Traverso 在 2023 年的反馈仍点出硬伤:回溯测试里跨符号抓 tick 依旧不完整,真要上多货币 EA 建议先在实盘模拟环境自测一遍同步逻辑。外汇与贵金属杠杆高、滑点突发行情多,任何多货币方案都只是概率优势,不是免死金牌。