在 MetaTrader 5 中实施多货币模式(基础篇)
「在 MT5 里搭多货币回测框架」
MT5 的策略测试器原生支持多货币模式,但多数交易者只在单一品种上跑 EA,浪费了跨市场对冲与相关性统计的能力。要在测试器里启用,需在 EA 的 OnInit 中显式请求其他货币对的句柄,而不是默认只加载当前图表品种。
一个实在的数据点:Konstantin Gruzdev 在 2014 年 1 月 8 日发布的示例文章,至今在源站累计浏览 4079、评论 51,说明多货币模式虽冷门却有稳定受众。外汇与贵金属跨品种组合波动叠加,属于高风险操作,实盘前必须用历史数据验证相关性假设。
具体做法上,先用 SymbolSelect 把目标品种加进市场观察窗口,再用 iClose 或 CopyClose 跨品种取价。若忽略这一步,回测里取到的外来品种数据会是空序列,EA 逻辑直接失效。
为什么多货币系统仍卡在开发端
做多货币策略的人不少,MT5 市场上能直接用的跨品种 EA、指标也一堆,但真到自己写一套完备的多货币框架时,开发者还是会撞上具体坑:品种同步、持仓联动、信号聚合都不是单货币逻辑能直接套的。 MetaTrader 5 客户端配上 MQL5 之后,语言层和运行时给了更完整的跨品种访问能力,之前在 MT4 里要绕很大弯子的多货币模式,现在能用更直的方式落地。 本文要拆的,就是这些新机会具体落在哪些接口和写法上,以及怎么把多货币机器人从‘能跑’推到‘结构清楚’。
◍ 传统多币种系统的三个硬伤
传统做法大多基于 OnTick() 与 OnCalculate()(MQL4 里对应 start())去驱动多货币策略:当前图表一有报价或新柱,就顺序轮询其他品种做分析和下单。逻辑直白,但陷阱在底层。 最明显的是系统被当前图表品种绑死。快行情时本品种 tick 密集,轮询还算跟手;到了夜间慢市,本品种可能半分钟才动一次甚至更少,这段时间整个多货币系统就像睡着,别的品种哪怕上下翻飞也顾不上。时间表越大这毛病越淡,MQL5 用户现在偏爱小周期甚至逐笔,于是拖累被放大。 第二个坑是历史柱不同步。MT5 沿用了 MT4 的绘制逻辑:某分钟没报价就不出柱。所以两个品种图表柱数相同,第 100 根的开盘时间却可能不一样。构建多货币指标时若不清对齐,算出来就是错位信号;连当前未闭合柱的时间也对不齐——当前图出新柱,别的品种可能早出了,用 CopyXXXX() 去抓就容易拿到上一根或报错。 还有个隐蔽点:非当前图表的品种在历史数据悄悄更新时,你怎么察觉?单货币指标靠 OnCalculate() 的 prev_calculated 归零就能重算,多货币里远端品种随时变,不监听就漏算。光这三个例子已够让 EA 代码膨胀且漏洞难堵,外汇与贵金属杠杆高、跳空频发,这类同步失效可能直接放大滑点风险。
「OnTimer 给多货币系统开了道口子」
MQL5 里新增的 Timer 事件与 OnTimer() 处理函数,让多货币 EA 和指标不用再死绑定当前图表品种的价格跳动,也能按固定节奏去轮询别的货币对状态。这直接补上了早先那种只能靠 NewTick 被动触发的短板,理论上多品种监控不再受图表品种限制。 但代价是实打实的:要察觉某个货币对的变化,程序仍得挨个品种顺序查询。若计时器设得密,比如每 100 毫秒一次,十几个品种扫下来 CPU 占用会明显抬升,资源消耗随品种数和轮询频率线性走。 OnTimer 自己还带一堆麻烦事:初始化和注销得手动管,周末市场停了它照常跑空转,容易写出无效请求。它的好处不假,可单靠这套机制依然拼不出真正完备的多货币架构,外汇贵金属本就高波动高杠杆,这类资源与边界坑得先在 MT5 策略测试器里跑通再谈。
用自定义图表事件绕开多币种追踪死角
标准 OnTick() 和 OnCalculate() 只能盯着当前图表品种,OnTimer() 又被固定间隔绑死,做多货币系统时会处处别扭。OnChartEvent() 这类自定义事件处理机制,允许你往别的品种图表挂一个轻量指标,把价格变动以事件形式发回主 EA,不必把几十个品种塞进一个循环里轮询。 所谓“间谍”指标,就是挂在目标图表上的 iSpy:它在每次新报价时调用 EventChartCustom() 把期段、价格、品种名打包发出。主程序在 OnChartEvent() 里按约定解包——id 减 CHARTEVENT_CUSTOM 等于 0 表示对方刚初始化(prev_calculated 归零);lparam 是图表期段;dparam 默认末根收盘价,也可在间谍端改用 ENUM_APPLIED_PRICE 里的任意类型;sparam 直接带品种名。 实测在任意图表挂上 exSpy.mq5 后,日志里能连续收到各品种的价格变动与“初始化”事件,后者在 history 更新或重传时也会触发。相比传统写法,你不再依赖单一品种 tick,也不受定时器粒度限制,历史与服务器同步由各图表自行负责,主代码省掉了大量 SymbolInfo 类查询。 下方的 iSpy 片段展示了初始化分支:prev_calculated 为 0 时,把 _Period 和当前价随 _Symbol 发往 chart_id。复制进 MT5 编译挂到 EURUSD 之外图表,再在主 EA 的 OnChartEvent 打印 sparam,就能验证跨图事件通道是否打通,外汇与贵金属杠杆高,验证时先用模拟盘。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| iSpy.mq5 | class=class="str">"cmt">//| Copyright class="num">2010, Lizar | class=class="str">"cmt">//| lizar-class="num">2010@mail.ru | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define VERSION "class="num">1.00 Build class="num">2 (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 "iSpy agent-indicator. If you want to get ticks, attach it to the chart" class="macro">#class="kw">property indicator_chart_window input class="type">long chart_id=class="num">0; class=class="str">"cmt">// chart id input class="type">class="kw">ushort custom_event_id=class="num">0; class=class="str">"cmt">// event id 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 price[] array const class="type">int prev_calculated, class=class="str">"cmt">// bars, calculated at the previous call const class="type">int begin, class=class="str">"cmt">// starting index of data const class="type">class="kw">double& price[] class=class="str">"cmt">// array for the calculation ) { class="type">class="kw">double price_current=price[rates_total-class="num">1]; class=class="str">"cmt">//--- Initialization: if(prev_calculated==class="num">0) { class=class="str">"cmt">// Generate and send "Initialization" event EventChartCustom(chart_id,class="num">0,(class="type">long)_Period,price_current,_Symbol); class="kw">return(rates_total); } class=class="str">"cmt">// When the new tick, let&class="macro">#x27;s generate the "New tick" custom event