MQL5 细则手册:在 MQL5 中开发多交易品种波动指标(基础篇)
📘

MQL5 细则手册:在 MQL5 中开发多交易品种波动指标(基础篇)

第 1/3 篇

◍ 多品种波动率指标的开发起点

在 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 年前的柱距更密,那是每日数据冒充的,真实柱垂线一标就露馅。外汇和贵金属跨品种波幅监控杠杆高、滑点猛,上真仓前务必在策略测试器用历史数据跑一遍句柄稳定性。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                MultiSymbolATR.mq5 |

◍ 指标骨架先搭好

写一个 MT5 自定义指标,最先要定的是它在终端里的存在形态。上面这段初始化头部声明了六个绘图缓冲区和六个绘图系列,并强制指标显示在独立子窗口,最低值锁死为 0——这意味着你后面塞进去的任意计算值都不能跌破坐标轴底线。 #property indicator_separate_windowindicator_minimum 0 的组合,常见于把负值剔除的振荡类工具(比如某些能量潮变体)。如果你打算做波动率带,却忘了设 separate_window,图表会硬塞进主图把 K 线挤变形。 OnInit 里只返回 INIT_SUCCEEDEDOnDeinit 留空,说明这还只是壳。真正算数的是 OnCalculate:它吃进 rates_total(总柱数)、prev_calculated(上次算到第几根)、以及 open/high/low/close 四个价格引用数组。外汇与贵金属杠杆高,这类未完工指标直接挂实盘有滑点误读风险,先在策略测试器跑历史数据验证缓冲写入再说。

MQL5 / C++
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 文件,能直接看到参数面板长什么样,再决定哪些线要加密。

MQL5 / C++
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 改成你盯的货币对数量即可直接编译验证。

MQL5 / C++
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

常见问题

先确定主图品种之外要监控的副品种列表,再给每个品种单独开一条 ATR 缓冲区,别共用主图缓冲。
坑主要是跨品种句柄没释放导致内存漏、回调里重复算历史数据拖慢、以及副品种休市时缓冲区写空值画图错位。
可以,小布能按你给的品种列表生成面板骨架并标出空回调位置,你只需补计算函数就能在盘口直接测。
至少留周期参数、ATR 长度、副品种字符串数组、以及画布起始坐标,空回调先写好不报错再填逻辑。
在 OnCalculate 里判断副品种最后一根时间,取不到就写 EMPTY_VALUE 并跳过画线,避免连到主图坐标。