将指标从 MQL4 迁移到 MQL5·进阶篇
🔧

将指标从 MQL4 迁移到 MQL5·进阶篇

(2/3)· 从准备代码到调试缓存,手把手把 MACD、RSI 搬进 MQL5 而不重写逻辑

案例拆解新手友好 第 2/3 篇
直接把 MQL4 指标源码塞进 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 偏移。

MQL5 / C++
  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 参数拉出来调一遍,能直接看到信号线灵敏度变化,外汇与贵金属波动大,参数误用可能放大回撤,属高风险验证。

MQL5 / C++
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 根柱之后才开始有值,图表上前面一段是空的,属正常现象。 外汇与贵金属波动剧烈、杠杆风险高,迁移后指标仅作动量参考,信号失效概率不低,实盘前务必在策略测试器用历史数据跑一遍验证缓冲对齐。

MQL5 / C++
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 会让指标反复重算拖慢终端。

MQL5 / C++
  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 能直接看到信号线滞后变化。

MQL5 / C++
   {
      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 线右对齐。

MQL5 / C++
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);
交给小布盯盘看盘口
这些迁移后的指标诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到信号与缓存状态,把重复劳动交给小布,你专注决策。

常见问题

MQL5 指标缓存通过引用写入,传值会导致计算层拿不到实时数据;原文要求参数前加 & 正是为了传递数组链接而非副本。
一般是 IndicatorCounted 与 MQL5 的 prev_calculated 没对应好,导致缓存重复计算或越界,需要在包含层做映射。
可以,只要指标按标准模板输出缓存名,小布对应的品种页就能识别并显示,无需额外配置。
MQL5 自定义指标向导生成的输入块顺序和 MQL4 不同,若不改写头部声明,回测时周期参数会错位。
在 MQL5 模板主体加打印已计算柱数,对比 CountedMQL4 变量,若两者同步增长且图线无断层,基本就通了。