MQL5 细则手册:在 MQL5 中开发多交易品种波动指标(基础篇)
◍ 多品种波动率指标的开发起点
在 MT5 里做跨品种波动监控,核心是把多个 Symbol 的报价流拉进同一个自定义指标缓冲区。原生 iATR 或 iStdDev 只认当前图表品种,要横向比黄金、欧美、美日的波动,得用 CopyRates 或 iBarShift 逐个拉取。 Anatoli Kazharski 在 2014-05-27 发布的示例被查阅 3695 次、评论 8 条,说明这类多品种指标需求一直存在。开 MT5 新建一个 Custom Indicator,把需要监控的品种写进字符串数组,就是第一步可验证动作。 外汇与贵金属杠杆高、跳空频繁,跨品种波动对比只能作为概率参考,不能直接当成方向信号。先跑通一个双品种波动率差值的画线,比一次性挂七个品种更实际。
多品种指标开发的三个坑
做跨品种波动指标,新手最容易卡在三个地方:一是别的品种数据和当前图表时间对不齐,二是有些品种的指标缓冲拿不到,三是搞不清指定周期里“真实”K线从哪根开始算。本文要盯死的,就是这三件事。 具体实现上,我们会用 iATR 句柄把六个交易品种的 ATR 值拉出来。品种名写在外部参数里,最多六个,程序会先校验:要是总表(SymbolList)里没有你填的名字,这个品种直接跳过不计算;能用的品种如果没在“市场报价”窗口,也会被自动加进去。 顺带一提,上一篇文章里我们用画布画过滚动条控件,这次不画图,只拿画布当进度显示器——让你在加载六个品种数据时,能实时看到跑到第几个了,不至于以为 MT5 卡死。外汇与贵金属跨品种统计存在滑点与时区错位风险,结论仅作技术验证参考。
「多品种 ATR 指标的骨架怎么搭」
用 MQL5 向导生成自定义指标模板后,核心是把跨品种波幅计算拆成可维护的函数块。先在指标属性后加一个 LEVELS_COUNT 常数,控制用 OBJ_HLINE 水平线对象画出的层数,层数数值在外部参数里直接指定,方便后期不碰代码就能调。 外部参数要暴露三样东西:iATR 平均周期、要显示波幅的其余交易品种、以及水平线价格。品种从 2 开始编号,因为挂指标的当前图表的品种算第 1 个。这样设计后,换品种观察只需改参数,不用改源码。 OnInit() 里不要堆逻辑,拆成 SetIndicatorProperties()、InitArrays()、InitSymbolNames()、InitLevels()、GetIndicatorHandles() 几个函数。CheckInputParameters() 只卡一个值:ATR 周期上限 500,超了就打印日志并终止,避免历史数据量爆炸把终端拖死。 首次 OnCalculate() 时 prev_calculated 为 0,终端补历史也会把它归零触发全量重算。麻烦的是市场关闭后没 tick 就不会再触发,所以模板里要挂 OnTimer() 每秒扫一次,用 CopyDataOnCalculate() 把参数存进 OC_ 前缀全局变量,配合 ResizeCalculatedArrays() 和 ZeroCalculatedArrays() 在算之前把缓冲区和中间数组归零。 数据完备性检查是硬骨头。LoadAndFormData() 比对各品种数据长度、不够就从服务器拽;CheckAvailableData() 挨个看句柄和指标值是否就绪;CheckEventLoadHistory() 发现历史加载就强制全重算;CheckSymbolIsSynchronized() 确认连上服务器才放行。DetermineFirstTrueBar() 找出每个品种在当前时间表的“真实”柱——比如 MetaQuotes-Demo 上 NZDUSD 从 2000 年才有分钟数据,其它货币对从 1999 年,用 CreateVerticalLine() 标出来,避免用日线拼的柱污染指标。 两个主循环分工明确:第一个按“倾尽所能获取数值”原则用 PrepareData() 填时间和 ATR 到临时数组,ProgressPercentage() 算进度推到画布;第二个用其它品种时间数组和主图时间对齐,FillIndicatorBuffers() 同步写缓冲。结尾删画布、SetIndicatorLevels() 摆水平线、变量归零,返回 rates_total,后续只补最后一根。 实测在 Charts 设置里把单窗口最大柱数调小能明显提速;改完要重启终端才生效。20 分钟图上能看见 1999 年前的柱距更密,那是每日数据冒充的,真实柱垂线一标就露馅。外汇和贵金属跨品种波幅监控杠杆高、滑点猛,上真仓前务必在策略测试器用历史数据跑一遍句柄稳定性。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MultiSymbolATR.mq5 |
◍ 指标骨架先搭好
写一个 MT5 自定义指标,最先要定的是它在终端里的存在形态。上面这段初始化头部声明了六个绘图缓冲区和六个绘图系列,并强制指标显示在独立子窗口,最低值锁死为 0——这意味着你后面塞进去的任意计算值都不能跌破坐标轴底线。
#property indicator_separate_window 和 indicator_minimum 0 的组合,常见于把负值剔除的振荡类工具(比如某些能量潮变体)。如果你打算做波动率带,却忘了设 separate_window,图表会硬塞进主图把 K 线挤变形。
OnInit 里只返回 INIT_SUCCEEDED,OnDeinit 留空,说明这还只是壳。真正算数的是 OnCalculate:它吃进 rates_total(总柱数)、prev_calculated(上次算到第几根)、以及 open/high/low/close 四个价格引用数组。外汇与贵金属杠杆高,这类未完工指标直接挂实盘有滑点误读风险,先在策略测试器跑历史数据验证缓冲写入再说。
class=class="str">"cmt">//| | class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Indicator properties class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_separate_window class=class="str">"cmt">// Indicator is in a separate subwindow class="macro">#class="kw">property indicator_minimum class="num">0 class=class="str">"cmt">// Minimum value of the indicator class="macro">#class="kw">property indicator_buffers class="num">6 class=class="str">"cmt">// Number of buffers for indicator calculation class="macro">#class="kw">property indicator_plots class="num">6 class=class="str">"cmt">// Number of plotting series class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Initialization completed successfully class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Deinitialization | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class=class="str">"cmt">// Size of class="kw">input time series class="kw">const class="type">int prev_calculated, class=class="str">"cmt">// Bars processed at the previous call class="kw">const class="type">class="kw">datetime &time[], class=class="str">"cmt">// Opening time class="kw">const class="type">class="kw">double &open[], class=class="str">"cmt">// Open prices class="kw">const class="type">class="kw">double &high[], class=class="str">"cmt">// High prices class="kw">const class="type">class="kw">double &low[], class=class="str">"cmt">// Low prices class="kw">const class="type">class="kw">double &close[] class=class="str">"cmt">// Close prices
多品种面板的参数骨架与空回调
这段声明把指标同时挂 6 个品种和 6 条参考线的基础结构定死了:RESET 宏置 0 用于向终端发重算指令,LEVELS_COUNT 与 SYMBOLS_COUNT 都写死为 6,意味着后续数组和绘制循环默认按这个量级跑。 OnCalculate 的尾部直接 return(rates_total),把当前品种的数据数组长度交还给终端;OnTimer 则是空函数,说明计时器逻辑还没接内容,先留了接口。 外部参数里 IndicatorPeriod 默认 14,是后续做均线的平滑窗口;Symbol02~06 分别预填 GBPUSD、AUDUSD、NZDUSD、USDCAD、USDCHF,Level01~04 给的是 10/50/100/200 整数档。外汇与贵金属波动剧烈、杠杆风险高,这些默认值只是骨架,实盘前需在 MT5 里按品种特性重调。 把下面这段代码贴进 MQ5 文件,能直接看到参数面板长什么样,再决定哪些线要加密。
class="kw">const class="type">long &tick_volume[], class=class="str">"cmt">// Tick volumes class="kw">const class="type">long &volume[], class=class="str">"cmt">// Real volumes class="kw">const class="type">int &spread[]) class=class="str">"cmt">// Spread { class=class="str">"cmt">//--- Return the size of the data array of the current symbol class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Constants class="macro">#define RESET class="num">0 class=class="str">"cmt">// Returning the indicator recalculation command to the terminal class="macro">#define LEVELS_COUNT class="num">6 class=class="str">"cmt">// Number of levels class="macro">#define SYMBOLS_COUNT class="num">6 class=class="str">"cmt">// Number of symbols class=class="str">"cmt">//--- Include the class for working with the canvas class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//--- External parameters class="kw">input class="type">int IndicatorPeriod=class="num">14; class=class="str">"cmt">// Averaging period sinput class="type">class="kw">string dlm01=""; class=class="str">"cmt">//- - - - - - - - - - - - - - - - - - - - - - - - - - - class="kw">input class="type">class="kw">string Symbol02 ="GBPUSD"; class=class="str">"cmt">// Symbol class="num">2 class="kw">input class="type">class="kw">string Symbol03 ="AUDUSD"; class=class="str">"cmt">// Symbol class="num">3 class="kw">input class="type">class="kw">string Symbol04 ="NZDUSD"; class=class="str">"cmt">// Symbol class="num">4 class="kw">input class="type">class="kw">string Symbol05 ="USDCAD"; class=class="str">"cmt">// Symbol class="num">5 class="kw">input class="type">class="kw">string Symbol06 ="USDCHF"; class=class="str">"cmt">// Symbol class="num">6 sinput class="type">class="kw">string dlm02=""; class=class="str">"cmt">//- - - - - - - - - - - - - - - - - - - - - - - - - - - class="kw">input class="type">int Level01 =class="num">10; class=class="str">"cmt">// Level class="num">1 class="kw">input class="type">int Level02 =class="num">50; class=class="str">"cmt">// Level class="num">2 class="kw">input class="type">int Level03 =class="num">100; class=class="str">"cmt">// Level class="num">3 class="kw">input class="type">int Level04 =class="num">200; class=class="str">"cmt">// Level class="num">4
「把多品种 ATR 缓冲区和画布先搭起来」
多品种波动监控指标要跑起来,第一步是把全局容器和输入参数定清楚。下面这段声明决定了后续能同时画几个品种、ATR 阈值分几档,以及画布对象由谁接管。 输入参数里 Level05=400、Level06=600 是第六、第七档 ATR 阈值(单位依品种点数缩放),配合前面未列出的 Level01~04 构成 LEVELS_COUNT 档位数组,用来在画布上标不同强度的波动带。 CCanvas canvas 直接载入图形类,所有跨品种线都靠它往图表层绘;OC_ 前缀的全局数组(open/high/low/close/time/tick_volume/volume/spread)用于在 OnCalculate 外暂存最近一次传入的序列,避免重复拷贝。 三个结构体把复杂度压平:buffers 存每个品种 ATR 线数值,temp_time 与 temp_atr 做数据准备期的临时对齐,series_first_date 与 limit_time 负责核对终端首根 K 线时间并从指定 bar 起笔。symbol_handles 与 line_colors 写死 6 个品种红蓝绿金青紫配色,开 MT5 把 SYMBOLS_COUNT 改成你盯的货币对数量即可直接编译验证。
class="kw">input class="type">int Level05 =class="num">400; class=class="str">"cmt">// Level class="num">5 class="kw">input class="type">int Level06 =class="num">600; class=class="str">"cmt">// Level class="num">6 class=class="str">"cmt">//--- Global variables and arrays CCanvas canvas; class=class="str">"cmt">// Loading the class class=class="str">"cmt">//--- Variables/arrays for copying data from OnCalculate() class="type">int OC_rates_total =class="num">0; class=class="str">"cmt">// Size of class="kw">input time series class="type">int OC_prev_calculated =class="num">0; class=class="str">"cmt">// Bars processed at the previous call class="type">class="kw">datetime OC_time[]; class=class="str">"cmt">// Opening time class="type">class="kw">double OC_open[]; class=class="str">"cmt">// Open prices class="type">class="kw">double OC_high[]; class=class="str">"cmt">// High prices class="type">class="kw">double OC_low[]; class=class="str">"cmt">// Low prices class="type">class="kw">double OC_close[]; class=class="str">"cmt">// Close prices class="type">long OC_tick_volume[]; class=class="str">"cmt">// Tick volumes class="type">long OC_volume[]; class=class="str">"cmt">// Real volumes class="type">int OC_spread[]; class=class="str">"cmt">// Spread class=class="str">"cmt">//--- Structure of buffers for drawing indicator values class="kw">struct buffers {class="type">class="kw">double data[];}; buffers atr_buffers[SYMBOLS_COUNT]; class=class="str">"cmt">//--- Structure of time arrays for data preparation class="kw">struct temp_time {class="type">class="kw">datetime time[];}; temp_time tmp_symbol_time[SYMBOLS_COUNT]; class=class="str">"cmt">//--- Structure of arrays of the ATR indicator values for data preparation class="kw">struct temp_atr {class="type">class="kw">double value[];}; temp_atr tmp_atr_values[SYMBOLS_COUNT]; class=class="str">"cmt">//--- For the purpose of storing and checking the time of the first bar in the terminal class="type">class="kw">datetime series_first_date[SYMBOLS_COUNT]; class="type">class="kw">datetime series_first_date_last[SYMBOLS_COUNT]; class=class="str">"cmt">//--- Time of the bar from which we will start drawing class="type">class="kw">datetime limit_time[SYMBOLS_COUNT]; class=class="str">"cmt">//--- Indicator levels class="type">int indicator_levels[LEVELS_COUNT]; class=class="str">"cmt">//--- Symbol names class="type">class="kw">string symbol_names[SYMBOLS_COUNT]; class=class="str">"cmt">//--- Symbol handles class="type">int symbol_handles[SYMBOLS_COUNT]; class=class="str">"cmt">//--- Colors of indicator lines class="type">class="kw">color line_colors[SYMBOLS_COUNT]={clrRed,clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta}; class=class="str">"cmt">//--- String representing the lack of the symbol