MQL5多品种价格偏离指标开发实战
从架构拆分到双色渲染与图表事件处理
一、什么是指标分析的价格偏离
价格偏离(Price Divergence)在本指标语境中,并非传统技术指标里的背离信号,而是指多个品种的价格序列在某一参考点被“汇聚到同一水平”,从而直观对比它们在此后一段时间的相对强弱与走势差异。例如把 EURUSD、GBPUSD、USDCHF 等以美元为基准或反向基准的货币对,在每周开盘时刻对齐开盘价,随后在图表上并排绘制各自真实价格走势,交易者可一眼看出哪个货币对涨得多、哪个跌得狠。
与之前文章开发的“多货币波动指标”不同,本次指标直接输出原始价格数据(折线、柱线或蜡烛条),而非波动率数值。它支持五种汇聚起点模式:垂直线拖拽、月、周、日、小时,并允许对单个品种做上下翻转,以解决基准货币不一致导致的视觉颠倒问题。
二、项目架构与多文件拆分
原文指标代码约1500行,作者将其拆分为主文件与三个 Include 头文件,这是大型 MQL5 指标必备的工程习惯:Checks.mqh 负责数据可用性检查与下载;Objects.mqh 管理图形对象(如垂直线);Chart.mqh 管理图表属性与可视区域最大最小值修正。主文件仅保留核心逻辑、全局变量与事件函数。
在 MT5 的 MQL5/Indicators 目录下建立与指标同名的文件夹,Include 子目录存放上述 .mqh 文件。主文件通过 #include "Include/Checks.mqh" 等相对路径引入。这种结构提升可读性,也方便多人协作或后续复用函数。
#define RESET 0 #define SYMBOLS_COUNT 5 #include <Canvas\Canvas.mqh> #include "Include/Checks.mqh" #include "Include/Chart.mqh" #include "Include/Objects.mqh"
三、缓冲区和双色渲染机制
指标属性中 #property indicator_buffers 25 表示共25个缓冲区,#property indicator_plots 5 表示5个绘图序列(对应当前品种+5个外部品种)。单色折线模式仅需每个品种1个 close 缓冲;但柱线/蜡烛条模式需要 open/high/low/close 四个数据缓冲,外加一个 icolor 颜色索引缓冲来实现双色(阳线/阴线不同色)。因此 5 品种 × 5 = 25。
颜色在 #property indicator_colorN 中定义,逗号前为单色模式或阳线色,逗号后为阴线色。例如 #property indicator_color1 clrDodgerBlue,C'0,50,100'。在 SetIndicatorProperties() 中根据 DrawType 调用 SetIndexBuffer 绑定缓冲,并用 PLOT_DRAW_TYPE 设为 DRAW_LINE / DRAW_COLOR_BARS / DRAW_COLOR_CANDLES。
#property indicator_chart_window #property indicator_buffers 25 #property indicator_plots 5 #property indicator_color1 clrDodgerBlue,C'0,50,100' #property indicator_color2 clrLimeGreen,C'20,80,20' #property indicator_color3 clrGold,C'160,140,0' #property indicator_color4 clrAqua,C'0,140,140' #property indicator_color5 clrMagenta,C'130,0,130'
SetBufferColorIndex() 函数在填充缓冲时依据 close>open 将 icolor 写为0或1,终端据此从颜色列表取色。若 TwoColor=false,则全部写0,使用第一种颜色。
void SetBufferColorIndex(int i,int symbol_number,double close,double open)
{
if(TwoColor)
{
if(close>open) buffer_data[symbol_number].icolor[i]=0;
else buffer_data[symbol_number].icolor[i]=1;
}
else buffer_data[symbol_number].icolor[i]=0;
}
四、外部参数与汇聚起点模式
通过两个枚举 ENUM_DRAWTYPE(LINE/BARS/CANDLES)和 ENUM_START_POINT(VERTICAL_LINE/MONTH/WEEK/DAY/HOUR)构建下拉输入。外部参数输入5个品种名及对应的 Inverse 翻转开关。品种数量从2计起,因为索引0预留给当前图表品种。
enum ENUM_DRAWTYPE { LINE=0, BARS=1, CANDLES=2 };
enum ENUM_START_POINT { VERTICAL_LINE=0, MONTH=1, WEEK=2, DAY=3, HOUR=4 };
input ENUM_DRAWTYPE DrawType=CANDLES;
input ENUM_START_POINT StartPriceDivergence=VERTICAL_LINE;
input bool TwoColor=false;
input string Symbol02="GBPUSD";
input bool Inverse02=false;
翻转(Inverse)的意义:若当前图是 EURUSD(美元为报价币),而 USDCHF 中美元是基础币,直接同图对比价格走向会视觉相反。将 USDCHF 翻转后,其走势方向与其他以美元为基准的货币对一致,便于分析。InitInverse() 将输入布尔值读入 inverse[] 数组,在后续数据填充时对价格取负向偏移或倒置处理。
VERTICAL_LINE 模式在 OnInit 时创建一条可拖拽绿色垂直线,其时间位置即所有品种汇聚的参考点,取该处当前品种开盘价为基准。其他周期模式则在 InitStartPointTF() 中把 timeframe_start_point 设为 MN1/W1/D1/H1,并在数据中找到对应周期起始棒时间作为汇聚点。
void InitStartPointTF()
{
if(StartPriceDivergence==VERTICAL_LINE) return;
switch(StartPriceDivergence)
{
case MONTH : timeframe_start_point=PERIOD_MN1; break;
case WEEK : timeframe_start_point=PERIOD_W1; break;
case DAY : timeframe_start_point=PERIOD_D1; break;
case HOUR : timeframe_start_point=PERIOD_H1; break;
}
}
五、初始化函数与策略测试器限制
OnInit() 依次调用:CheckTesterMode 禁止在策略测试器中使用(因图表属性控制未完全支持);SetBarsColors 设置当前图表的蜡烛颜色;InitStartPointTF;CheckInputParameters;EventSetMillisecondTimer 开定时器;InitArrays/InitSymbolNames/InitInverse 初始化数组;SetIndicatorProperties 绑定缓冲;SetDivergenceLine 建线;ChartRedraw 刷新。
bool CheckTesterMode()
{
if(MQLInfoInteger(MQL_TESTER)||MQLInfoInteger(MQL_VISUAL_MODE)||MQLInfoInteger(MQL_OPTIMIZATION))
{
Comment("Currently, the indicator is not intended to be used in Strategy Tester!");
return(false);
}
return(true);
}
SetBarsColors() 利用 ChartSetInteger 修改 CHART_COLOR_CHART_UP 等属性,使主图蜡烛色与指标逻辑协调。若 TwoColor 关闭,阴线也用阳线色,避免花哨。
void SetBarsColors()
{
ChartSetInteger(0,CHART_COLOR_CHART_UP,color_bar_up);
ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,color_bar_up);
if(TwoColor)
{
ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_down);
ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_down);
}
else
{
ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_up);
ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_up);
}
}
六、数据加载与缓冲填充逻辑
OnCalculate 中首先 CheckAvailableData() 严格校验各品种及高周期数据是否就绪,采用最多100次 CopyTime/CopyRates 重试,避免因网络或缓存延迟拿到空数组。随后 FillIndicatorBuffers() 在单循环里完成:取当前棒数据、判定汇聚时间、计算相对偏移、写缓冲、置颜色。对于非垂直线模式,还需通过高周期(如 W1)确定每周起始棒时间,作为 limit_time 对齐基准。
bool CheckAvailableData()
{
int attempts=100;
for(int s=0;s<SYMBOLS_COUNT;s++)
{
if(symbol_names[s]!=empty_symbol)
{
datetime time[]; int total_period_bars=0;
datetime terminal_first_date=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE);
total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent());
for(int i=0;i<attempts;i++)
if(CopyTime(symbol_names[s],Period(),0,total_period_bars,time) && ArraySize(time)>=total_period_bars) break;
if(ArraySize(time)==0||ArraySize(time)<total_period_bars) return(false);
}
}
return(true);
}
FillIndicatorBuffers 对每根可见棒,用 CopyRates 取指定品种同时间棒,若时间不匹配或含 EMPTY_VALUE 则写空值。汇聚点价格作为基准,后续棒价格减去基准并叠加到主图坐标。若 Inverse 为真,则做反向位移。此函数还调用 SetBufferColorIndex 决定阴阳色。
七、图表事件与可视区域自适应
指标响应两类图表事件:CHARTEVENT_OBJECT_DRAG(拖拽垂直线)时,若处于 VERTICAL_LINE 模式,手动调用 OnCalculate 重算缓冲,使所有品种重新以新参考点汇聚;CHARTEVENT_CHART_CHANGE(缩放或属性修改)时,调用 CorrectChartMaxMin() 重算所有品种在可视区域内的真实最高/最低,并通过 ChartSetInteger 设置图表上下界,防止某些品种价格超出视图被裁剪。
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
if(id==CHARTEVENT_OBJECT_DRAG)
{
if(StartPriceDivergence==VERTICAL_LINE)
OnCalculate(OC_rates_total,0,OC_time,OC_open,OC_high,OC_low,OC_close,OC_tick_volume,OC_volume,OC_spread);
}
if(id==CHARTEVENT_CHART_CHANGE) CorrectChartMaxMin();
}
CorrectChartMaxMin 先用 ChartGetInteger 取 CHART_VISIBLE_BARS 与 CHART_FIRST_VISIBLE_BAR,反推最后可见棒索引,再用 CopyHigh/CopyLow 收集当前品种及所有外部品种在可视区间的极值,取全局最大最小并加偏移写回图表属性。这样多品种叠加时,视图总能完整容纳全部线条。
八、与其他多品种方案的对比与增强思路
对比“多货币波动指标”:本文直接绘价格,更直观但占纵向空间大;波动指标用比率,适合副图叠加。对比手动开多个窗口:本指标同图对齐时间轴,避免肉眼错频。若想增强,可加入品种相关性计算、自动识别基准币、或支持超过5个品种(需动态申请缓冲,突破25限制需用指标句柄或离线计算)。
由于指标禁用了测试器,无法用于回测信号研究;若需历史偏离统计,应抽取 FillIndicatorBuffers 核心算法为脚本或 EA,脱离图表属性依赖。整体而言,该案例是学习 MQL5 多品种、多缓冲、图表事件与工程拆分的优质范本。