将指标从 MQL4 迁移到 MQL5·进阶篇
(2/3)· 从准备代码到调试缓存,手把手把 MACD、RSI 搬进 MQL5 而不重写逻辑
把 MQL4 的 MACD 逻辑搬进 OnCalculate
在 MT5 自定义指标里复刻经典 MACD,核心是把 MQL4 的 IndicatorCounted/Bars 心智模型映射到 OnCalculate 的 prev_calculated/rates_total。上面这段直接给出了映射对照:IndicatorCounted() 对应 prev_calculated,Bars 对应 rates_total,iMA 换成 iMAMql4,iMAOnArray 换成 iMAOnArrayMql4。 OnCalculate 里先调 MQL4Run 算出一个 bars 值,再把它和 CountedMQL4、两个缓存数组以及快慢 EMA 周期丢进 Start 函数。Start 内部先用 counted_bars=prev_calculated,若大于 0 就减 1,确保上一根 K 线被重算,避免信号线在跳空时错位。 limit 定为 rates_total-counted_bars,只遍历未计算过的柱。MacdBuffer[i] 用快 EMA 减慢 EMA 填充,SignalBuffer 随后用 SignalSMA 周期做平滑。外汇与贵金属波动剧烈,这种重算机制能降低信号滞后,但参数不当仍可能频繁假突破,实盘前务必在策略测试器跑至少 3 个月 tick 数据。 让小布替你跑这套 把 FastEMA=12、SlowEMA=26、SignalSMA=9 填进输入参数,加载到 XAUUSD 的 M15,观察 MacdBuffer 与 SignalBuffer 金叉死叉是否和原版 MT4 指标重合,不一致就查 iMAMql4 的 shift 偏移。
ArraySetAsSeries(MacdBuffer,true); ArraySetAsSeries(SignalBuffer,true); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime &time[], class="kw">const class="type">class="kw">double &open[], class="kw">const class="type">class="kw">double &high[], class="kw">const class="type">class="kw">double &low[], class="kw">const class="type">class="kw">double &close[], class="kw">const class="type">long &tick_volume[], class="kw">const class="type">long &volume[], class="kw">const class="type">int &spread[]) { class=class="str">"cmt">//--- class="type">int bars=MQL4Run(rates_total,prev_calculated); class=class="str">"cmt">// bars - MQL4程序可用的柱形数量 Start(bars, CountedMQL4, MacdBuffer, SignalBuffer, FastEMA, SlowEMA, SignalSMA); class=class="str">"cmt">//--- 返回prev_calculated的值用于下一次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 移动平均的收敛/发散指标(MACD) | class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//+--------------------+------------------+ class=class="str">"cmt">//| MQL4 | MQL5 | class=class="str">"cmt">//+--------------------+------------------+ class=class="str">"cmt">//|IndicatorCounted() | prev_calculated | class=class="str">"cmt">//| Bars | rates_total | class=class="str">"cmt">//| iMA( | iMAMql4( | class=class="str">"cmt">//| iMAOnArray( | iMAOnArrayMql4( | class=class="str">"cmt">//+--------------------+------------------+ class="type">int Start(class="type">int rates_total, class="type">int prev_calculated, class="type">class="kw">double &MacdBuffer[], class="type">class="kw">double &SignalBuffer[], class="type">int FastEMA, class="type">int SlowEMA, class="type">int SignalSMA) { class="type">int limit; class="type">int counted_bars=prev_calculated; class=class="str">"cmt">//---- 最近一次计算的柱形将被重算 if(counted_bars>class="num">0) counted_bars--; limit=rates_total-counted_bars; class=class="str">"cmt">//---- macd的计算结果在第一个指标缓存中 for(class="type">int i=class="num">0; i<limit; i++) MacdBuffer[i]=iMAMql4(NULL,class="num">0,FastEMA,class="num">0,MODE_EMA,PRICE_CLOSE,i) -iMAMql4(NULL,class="num">0,SlowEMA,class="num">0,MODE_EMA,PRICE_CLOSE,i); class=class="str">"cmt">//---- 信号线的计算结果在第二个指标缓存中 for(class="type">int i=class="num">0; i<limit; i++)
◍ 信号线在数组上的 SMA 落地
这段收尾代码把信号线直接算在 MacdBuffer 数组上,而不是再去读行情序列。iMAOnArrayMql4 的入参依次是:数据源数组、总柱数 rates_total、平滑周期 SignalSMA、偏移 0、模式 MODE_SMA、当前索引 i。 注意偏移写死为 0,意味着信号线不参与未来偏移,只是对已有 MACD 值做同周期简单平均。若你把 SignalSMA 从默认 9 改成 5,信号线会更贴 MACD 主线,假交叉会增多;改成 15 则滞后更明显但过滤更狠。 最后 return(0) 表示指标计算正常结束,MT5 会在每次新 tick 重算时走到这里。开 MT5 把 SignalSMA 参数拉出来调一遍,能直接看到信号线灵敏度变化,外汇与贵金属波动大,参数误用可能放大回撤,属高风险验证。
SignalBuffer[i]=iMAOnArrayMql4(MacdBuffer,rates_total,SignalSMA,class="num">0,MODE_SMA,i); class=class="str">"cmt">//---- 完成 class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------
「把 MQL4 随机动量搬进 MT5 的实操改动」
在 MetaEditor 5 里新建指标模板后,核心动作是把原 MQL4 的 OnInit 里那部分计算逻辑,靠直接复制挪进 OnCalculate(即原 Start 等价函数)内,否则 MT5 的事件模型下缓存不会按根线刷新。 原 MQL4 程序用了 2 个缓存做内部极值计算、另外 2 个缓存绘图;迁移时必须把 4 个缓冲区的用途在 OnInit 里重排:0 和 1 挂 INDICATOR_DATA 出图,2 和 3 挂 INDICATOR_CALCULATIONS 跑 HighesBuffer / LowesBuffer 的中间值。 绘图起点要按周期算:draw_begin1 = KPeriod + Slowing,draw_begin2 = draw_begin1 + DPeriod。默认参数 Kperiod=14、Dperiod=5、Slowing=5,所以主线和信号线分别在第 19、第 24 根柱之后才开始有值,图表上前面一段是空的,属正常现象。 外汇与贵金属波动剧烈、杠杆风险高,迁移后指标仅作动量参考,信号失效概率不低,实盘前务必在策略测试器用历史数据跑一遍验证缓冲对齐。
class="type">int draw_begin1=KPeriod+Slowing; class="type">int draw_begin2=draw_begin1+DPeriod; class="macro">#class="kw">property indicator_plots class="num">2 SetIndexBuffer(class="num">2,HighesBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">3,LowesBuffer,INDICATOR_CALCULATIONS); class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| Stochastic_MQL4.mq5 | class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------ 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="macro">#class="kw">property indicator_minimum class="num">0 class="macro">#class="kw">property indicator_maximum class="num">100 class="macro">#class="kw">property indicator_buffers class="num">4 class="macro">#class="kw">property indicator_plots class="num">2 class=class="str">"cmt">//--- 绘制 MainBuffer class="macro">#class="kw">property indicator_label1 "MainBuffer" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 Red class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- 绘制 SignalBuffer class="macro">#class="kw">property indicator_label2 "SignalBuffer" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 Red class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">1 class="macro">#include <mql4_2_mql5.mqh> class=class="str">"cmt">//--- 输入参数 class="kw">input class="type">int Kperiod=class="num">14; class="kw">input class="type">int Dperiod=class="num">5; class="kw">input class="type">int Slowing=class="num">5; class=class="str">"cmt">//--- 指标缓存 class="type">class="kw">double MainBuffer[]; class="type">class="kw">double SignalBuffer[]; class="type">class="kw">double HighesBuffer[]; class="type">class="kw">double LowesBuffer[]; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓存映射 SetIndexBuffer(class="num">0,MainBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,SignalBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">2,HighesBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">3,LowesBuffer,INDICATOR_CALCULATIONS); class=class="str">"cmt">//--- InitMql4(); class=class="str">"cmt">//--- ArraySetAsSeries(MainBuffer,true);
把 MQL4 指标逻辑搬进 OnCalculate 的桥接写法
在 MT5 自定义指标里跑老 MQL4 逻辑,核心不是重写算法,而是把 OnCalculate 的入参映射成 MQL4 习惯的 bars 与 counted_bars。下面这段先用 MQL4Run 把 rates_total 和 prev_calculated 折算成旧环境可用的柱数,再丢给 start() 继续算 Stochastic 的 Main、Signal、高低缓冲。 ArraySetAsSeries 那三行把 SignalBuffer、HighesBuffer、LowesBuffer 全部倒序,保证索引 0 对应最新柱——这是 MQL4 老代码直接移植时最容易漏的一步,漏了曲线会整条反着画。 draw_begin1 等于 KPeriod+Slowing,draw_begin2 再叠一层 DPeriod;当 rates_total<=draw_begin2 时直接 return(0),说明前几十根柱因窗口不足根本不出值。以默认 K=5、Slowing=3、D=3 为例,draw_begin2=11,前 11 根 K 线图上必然空白。 返回值也有讲究:初始化段 return(0),正式计算段 return(rates_total)。MT5 靠这个返回值判断下次从哪根柱续算,错填成 counted_bars 会让指标反复重算拖慢终端。
ArraySetAsSeries(SignalBuffer,true); ArraySetAsSeries(HighesBuffer,true); ArraySetAsSeries(LowesBuffer,true); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime &time[], class="kw">const class="type">class="kw">double &open[], class="kw">const class="type">class="kw">double &high[], class="kw">const class="type">class="kw">double &low[], class="kw">const class="type">class="kw">double &close[], class="kw">const class="type">long &tick_volume[], class="kw">const class="type">long &volume[], class="kw">const class="type">int &spread[]) { class=class="str">"cmt">//--- class="type">int bars=MQL4Run(rates_total,prev_calculated); class=class="str">"cmt">// bars - MQL4程序可用的柱形数量 start(bars, CountedMQL4, MainBuffer, SignalBuffer, HighesBuffer, LowesBuffer, Kperiod, Dperiod, Slowing); class=class="str">"cmt">//--- 返回prev_calculated的值用于下一次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+--------------------+------------------+ class=class="str">"cmt">//| MQL4 | MQL5 | class=class="str">"cmt">//+--------------------+------------------+ class=class="str">"cmt">//|IndicatorCounted() | prev_calculated | class=class="str">"cmt">//| Bars | rates_total | class=class="str">"cmt">//| iMA( | iMAMql4( | class=class="str">"cmt">//| iMAOnArray( | iMAOnArrayMql4(| class=class="str">"cmt">//+--------------------+------------------+ class="type">int start(class="type">int rates_total, class="type">int prev_calculated, class="type">class="kw">double &MainBuffer[], class="type">class="kw">double &SignalBuffer[], class="type">class="kw">double &HighesBuffer[], class="type">class="kw">double &LowesBuffer[], class="type">int KPeriod, class="type">int DPeriod, class="type">int Slowing) { class="type">int draw_begin1=KPeriod+Slowing; class="type">int draw_begin2=draw_begin1+DPeriod; class="type">int i,k; class="type">int counted_bars=prev_calculated; class="type">class="kw">double price; class=class="str">"cmt">//---- if(rates_total<=draw_begin2) class="kw">return(class="num">0); class=class="str">"cmt">//---- 用0初始化 if(counted_bars<class="num">1)
◍ K线高低与%K的逐根回填逻辑
这段自定义指标的尾部计算,核心是把最近 N 根 K 线的最低、最高以及慢速 %K 逐根算出来并写进缓冲区。先清掉图表最右侧 draw_begin1、draw_begin2 根的历史显示,避免旧值在重绘时残留。 最低价扫描从 rates_total-KPeriod 起步,若已计数柱多于 KPeriod 则只扫新增部分;内层循环在 i 到 i+KPeriod-1 区间内取 Low 最小值,初值给 1000000 作哨兵。最高价对称处理,初值 -1000000,结果分别落 LowesBuffer、HighesBuffer。 %K 段引入 Slowing 平滑:对 Close-LowesBuffer 与 HighesBuffer-LowesBuffer 做 Slowing 根求和,sumhigh 为 0 时直接写 100.0,否则算 sumlow/sumhigh*100。注意分母为 0 只会在区间内全部极值相等时发生,实盘贵金属跳空周一时概率略高。 最后 SignalBuffer 用 iMAOnArrayMql4 对 MainBuffer 做 DPeriod 周期 SMA,counted_bars 先减 1 保证末根重算,limit 控制只刷未计数柱。开 MT5 把这段粘进自定义指标,改 KPeriod 或 Slowing 能直接看到信号线滞后变化。
{
for(i=class="num">1;i<=draw_begin1;i++) MainBuffer[rates_total-i]=class="num">0;
for(i=class="num">1;i<=draw_begin2;i++) SignalBuffer[rates_total-i]=class="num">0;
}
class=class="str">"cmt">//---- 最少的计数
i=rates_total-KPeriod;
if(counted_bars>KPeriod) i=rates_total-counted_bars-class="num">1;
class="kw">while(i>=class="num">0)
{
class="type">class="kw">double min=class="num">1000000;
k=i+KPeriod-class="num">1;
class="kw">while(k>=i)
{
price=Low[k];
if(min>price) min=price;
k--;
}
LowesBuffer[i]=min;
i--;
}
class=class="str">"cmt">//---- 最大计数
i=rates_total-KPeriod;
if(counted_bars>KPeriod) i=rates_total-counted_bars-class="num">1;
class="kw">while(i>=class="num">0)
{
class="type">class="kw">double max=-class="num">1000000;
k=i+KPeriod-class="num">1;
class="kw">while(k>=i)
{
price=High[k];
if(max<price) max=price;
k--;
}
HighesBuffer[i]=max;
i--;
}
class=class="str">"cmt">//---- %K线
i=rates_total-draw_begin1;
if(counted_bars>draw_begin1) i=rates_total-counted_bars-class="num">1;
class="kw">while(i>=class="num">0)
{
class="type">class="kw">double sumlow=class="num">0.0;
class="type">class="kw">double sumhigh=class="num">0.0;
for(k=(i+Slowing-class="num">1);k>=i;k--)
{
sumlow+=Close[k]-LowesBuffer[k];
sumhigh+=HighesBuffer[k]-LowesBuffer[k];
}
if(sumhigh==class="num">0.0) MainBuffer[i]=class="num">100.0;
else MainBuffer[i]=sumlow/sumhigh*class="num">100;
i--;
}
class=class="str">"cmt">//---- 最近一次计算的柱形将被重算
if(counted_bars>class="num">0) counted_bars--;
class="type">int limit=rates_total-counted_bars;
class=class="str">"cmt">//---- 信号线是移动平均
for(i=class="num">0; i<limit; i++)
SignalBuffer[i]=iMAOnArrayMql4(MainBuffer,rates_total,DPeriod,class="num">0,MODE_SMA,i);
class=class="str">"cmt">//----
class="kw">return(class="num">0);
}
class=class="str">"cmt">//+------------------------------------------------------------------「把 RSI 从 MQL4 搬进 MT5 的缓存改法」
在 MetaEditor 5 里给 RSI 建模板时,先确认输入参数只有一项:RSIPeriod 默认 14。这和老版 MQL4 逻辑一致,但缓存结构得重排——总共 3 个缓存,其中只有 1 个用于绘图,其余 2 个留给正负向计算。 和前一个迁移案例不同,这里要把 MQL4 里惯用的 int start() 函数整个换掉,改成 MQL5 的 int OnInit() 与 OnCalculate 调用体系,函数名和触发位置都变了。图 9 的对比里能看到,用 MQL4 思路重写的 RSIc 和标准 MQL5 RSI 曲线基本重合,说明迁移没伤到数值。 别把 int start() 当万能入口 MQL5 里若还写 start() 编译器不认,必须走 OnInit 初始化缓存、OnCalculate 跑循环。直接抄老代码会编译失败。 下方代码里 #property indicator_buffers 3 与 indicator_plots 1 是硬约束:绘图缓存绑 RSIBuffer,PosBuffer 和 NegBuffer 标成 INDICATOR_CALCULATIONS 不画出来。ArraySetAsSeries 设成 true 才和 K 线右对齐。
class=class="str">"cmt">//---- 输入参数 class="kw">extern class="type">int RSIPeriod=class="num">14; class=class="str">"cmt">//---- 缓存 class="type">class="kw">double RSIBuffer[]; class="type">class="kw">double PosBuffer[]; class="type">class="kw">double NegBuffer[]; class="macro">#class="kw">property indicator_buffers class="num">3 class="macro">#class="kw">property indicator_plots class="num">1 SetIndexBuffer(class="num">1,PosBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">2,NegBuffer,INDICATOR_CALCULATIONS) class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| RSI_MQL4.mq5 | class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------ 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="macro">#class="kw">property indicator_buffers class="num">3 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//--- 绘制 RSIBuffer class="macro">#class="kw">property indicator_label1 "RSIBuffer" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 Green class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- 绘制 PosBuffer class="macro">#class="kw">property indicator_label2 "PosBuffer" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 Red class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">1 class=class="str">"cmt">//--- 绘制 NegBuffer class="macro">#class="kw">property indicator_label3 "NegBuffer" class="macro">#class="kw">property indicator_type3 DRAW_LINE class="macro">#class="kw">property indicator_color3 Red class="macro">#class="kw">property indicator_style3 STYLE_SOLID class="macro">#class="kw">property indicator_width3 class="num">1 class="macro">#include <mql4_2_mql5.mqh> class=class="str">"cmt">//--- 输入参数 class="kw">input class="type">int RSIPeriod=class="num">14; class=class="str">"cmt">//--- 指标缓存 class="type">class="kw">double RSIBuffer[]; class="type">class="kw">double PosBuffer[]; class="type">class="kw">double NegBuffer[]; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓存映射 SetIndexBuffer(class="num">0,RSIBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,PosBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">2,NegBuffer,INDICATOR_CALCULATIONS); class=class="str">"cmt">//--- InitMql4(class="num">3); ArraySetAsSeries(RSIBuffer,true); ArraySetAsSeries(PosBuffer,true);