中测试移动平均线计算的性能·综合运用

中测试移动平均线计算的性能·综合运用

(3/3)·从七种调用方法到结果输出,用实验数据看清 MT4 与 MT5 的均线算力差距

含代码示例偏理论 第 3/3 篇

很多交易者默认 MT5 的 iMA 一定比 MT4 快,却没注意 iMA 只返回句柄、真正耗时在 CopyBuffer。把缓存命中当成算法优势,会让你在 EA 优化时误判瓶颈。

◍ IndicatorCreate 建句柄的耗时实测

用 IndicatorCreate 动态创建 MA 句柄,再拿全数据数组跑计算,这套写法在 MT5 里能直接压测不同周期与价格类型的开销。测试覆盖 M1/M5/M15/M30 四个周期、7 种应用价格、MA 周期 1~100,每种组合重复调用,用 GetTickCount 掐表。 实测四种平滑模式单次平均耗时如下:MODE_SMA 约 0.0030 秒,MODE_EMA 约 0.0029 秒,MODE_SMMA 约 0.0029 秒,MODE_LWMA 约 0.0029 秒。其中 LWMA 在更早的案例 2、5 里也是 0.0029 秒,说明它在这个测试框架里没有额外负担;而 SMA/EMA/SMMA 的最佳结果分别压到 0.000140、0.000121、0.000117 秒(案例 6 数据),说明参数选对能砍掉一个数量级。 外汇与贵金属杠杆高,回测耗时只是工程层面的参考,不代表任何信号胜率,实盘仍可能因滑点和流动性出现显著偏差。 下面这段代码就是上述测试的骨架:先枚举周期和价格,三层循环里反复调 Test8,最后除以组合数算单次时间。Test8 里用 MqlParam 数组装参数,调 IndicatorCreate 拿句柄,等 BarsCalculated 够数再 CopyBuffer 取数组。

MQL5 / C++
<span class="keyword">ENUM_TIMEFRAMES</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;M[<span class="number">class="num">4</span>]=
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">PERIOD_M1</span>,
&nbsp;&nbsp; <span class="keyword">PERIOD_M5</span>,
&nbsp;&nbsp; <span class="keyword">PERIOD_M15</span>,
&nbsp;&nbsp; <span class="keyword">PERIOD_M30</span>
&nbsp;&nbsp;};
<span class="keyword">ENUM_APPLIED_PRICE</span>&nbsp;&nbsp; P[<span class="number">class="num">7</span>]=
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">PRICE_CLOSE</span>,
&nbsp;&nbsp; <span class="keyword">PRICE_OPEN</span>,
&nbsp;&nbsp; <span class="keyword">PRICE_HIGH</span>,
&nbsp;&nbsp; <span class="keyword">PRICE_LOW</span>,
&nbsp;&nbsp; <span class="keyword">PRICE_MEDIAN</span>,
&nbsp;&nbsp; <span class="keyword">PRICE_TYPICAL</span>,
&nbsp;&nbsp; <span class="keyword">PRICE_WEIGHTED</span>
&nbsp;&nbsp;};
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; periodMA;
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time;
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count=<span class="number">class="num">10000</span>;
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; startGTC,endGTC;
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m,p;
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MA[];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 用于存放 iMA 指标数组</span>
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  MA_handle;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// iMA 指标句柄</span>
<span>class="type">MqlParam</span>&nbsp;&nbsp;&nbsp;&nbsp;params[];
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//| 脚本程序初始函数&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;               &nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="keyword">class="type">int</span> <span class="functions">OnStart</span>()
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="functions">ArrayResize</span>(params,<span class="number">class="num">4</span>);
&nbsp;&nbsp; startGTC=<span class="functions">GetTickCount</span>();
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">for</span>(m=<span class="number">class="num">0</span>;m&lt;=<span class="number">class="num">3</span>;m++)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(p=<span class="number">class="num">0</span>;p&lt;=<span class="number">class="num">6</span>;p++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span>(periodMA=<span class="number">class="num">1</span>;periodMA&lt;=<span class="number">class="num">100</span>;periodMA++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Test8();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; endGTC=<span class="functions">GetTickCount</span>();
&nbsp;&nbsp; time=endGTC-startGTC;
&nbsp;&nbsp; time=time/<span class="number">class="num">1000</span>/m/p/periodMA;
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="number">class="num">0</span>);
&nbsp;&nbsp;}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="keyword">class="type">void</span> Test8()
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">//--- 模式: MODE_SMA; MODE_EMA; MODE_SMMA; MODE_LWMA</span>
<span class="comment">class=class="str">"cmt">//--- 设置 ma_period</span>
&nbsp;&nbsp; params[<span class="number">class="num">0</span>].type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =<span class="keyword">TYPE_INT</span>;
&nbsp;&nbsp; params[<span class="number">class="num">0</span>].integer_value=periodMA;
<span class="comment">class=class="str">"cmt">//--- 设置 ma_shift</span>
&nbsp;&nbsp; params[<span class="number">class="num">1</span>].type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =<span class="keyword">TYPE_INT</span>;
&nbsp;&nbsp; params[<span class="number">class="num">1</span>].integer_value=<span class="number">class="num">0</span>;
<span class="comment">class=class="str">"cmt">//--- 设置 ma_method</span>
&nbsp;&nbsp; params[<span class="number">class="num">2</span>].type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =<span class="keyword">TYPE_INT</span>;
&nbsp;&nbsp; params[<span class="number">class="num">2</span>].integer_value=<span class="keyword">MODE_SMA</span>;
<span class="comment">class=class="str">"cmt">//--- 设置 applied_price</span>
&nbsp;&nbsp; params[<span class="number">class="num">3</span>].type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =<span class="keyword">TYPE_INT</span>;
&nbsp;&nbsp; params[<span class="number">class="num">3</span>].integer_value=P[p];
<span class="comment">class=class="str">"cmt">//--- 创建 MA</span>
&nbsp;&nbsp; MA_handle=<span class="functions">IndicatorCreate</span>(<span>NULL</span>,M[m],<span class="keyword">IND_MA</span>,<span class="number">class="num">4</span>,params);
&nbsp;&nbsp; <span class="keyword">class="kw">while</span>(<span class="functions">BarsCalculated</span>(MA_handle)&lt;count){}
&nbsp;&nbsp; <span class="functions">CopyBuffer</span>(MA_handle,<span class="number">class="num">0</span>,<span class="number">class="num">0</span>,count,MA);
&nbsp;&nbsp;}

四种均线算法在同一缓冲上的耗时对照

这一节把 SMA、EMA、SMMA、LWMA 四种均线放在同一套缓冲函数里跑,统一调用 MovingAverages.mqh 里的 OnBuffer 系列,避免各自写循环带来的误差。测试只喂了收盘价数组,作者说明多价格类型不影响本次性能结论,所以先省掉。 实测单根 K 线平均计算耗时:SMA 约 0.000140 秒,EMA 约 0.000121 秒,SMMA 约 0.000117 秒,LWMA 约 0.00350 秒。LWMA 明显慢一个数量级,最佳记录 0.0029 秒出现在更早的案例 2、5 里,说明线性加权对缓冲的遍历成本偏高。 外汇与贵金属行情跳空频繁,均线重算压力大,LWMA 在 M1 这种高频周期上可能拖累 EA 的 tick 响应。开 MT5 把下面代码贴进脚本,改 periodMA 上限就能复测你自己的品种。

MQL5 / C++
class="macro">#include <MovingAverages.mqh>
ENUM_TIMEFRAMES        M[class="num">4]=
  {
   PERIOD_M1,
   PERIOD_M5,
   PERIOD_M15,
   PERIOD_M30
  };
ENUM_APPLIED_PRICE      P[class="num">7]=
  {
   PRICE_CLOSE,
   PRICE_OPEN,
   PRICE_HIGH,
   PRICE_LOW,
   PRICE_MEDIAN,
   PRICE_TYPICAL,
   PRICE_WEIGHTED
  };
class="type">int      periodMA;
class="type">class="kw">double   buf[],arr[];
class="type">class="kw">double   close[];
class="type">class="kw">double   time;
class="type">int      count=class="num">10000,total;
class="type">int      startGTC,endGTC;
class="type">int      m,p;
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 脚本程序初始函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int OnStart()
  {
   CopyClose(_Symbol,_Period,class="num">0,count,close);
   total=ArrayCopy(arr,close);
   if(ArrayResize(buf,total)<class="num">0) class="kw">return(-class="num">1);
class=class="str">"cmt">//---
   ArraySetAsSeries(close,false);
   ArraySetAsSeries(arr,false);
   ArraySetAsSeries(buf,false);
   startGTC=GetTickCount();
class=class="str">"cmt">//---
   for(m=class="num">0;m<=class="num">3;m++)
     {
      for(p=class="num">0;p<=class="num">6;p++)
        {
         CopyClose(_Symbol,M[m],class="num">0,count,close);
         total=ArrayCopy(arr,close);
         for(periodMA=class="num">1;periodMA<=class="num">100;periodMA++)
           {
            Test9();      class=class="str">"cmt">// 测试在此
           }
        }
     }
class=class="str">"cmt">//---
   endGTC=GetTickCount();
   time=endGTC-startGTC;
   time=time/class="num">1000/m/p/periodMA;
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test9()
  {
   SimpleMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test10()
  {
   ExponentialMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test11()
  {
   SmoothedMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test12()
  {
   LinearWeightedMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }

「打印验证与数组方向切换」

把移动平均线的计算结果落到日志里,靠的是 PrintTest 函数。它接收两个参数:柱的位置 position,以及待查的数据数组 price[] 引用。调用时直接传缓冲区 buf 或收盘价 close,就能在 MT5 Experts 日志里看到对应数值。 这里有个容易踩的坑:数组索引在计算前和计算后不是一回事。计算阶段用 ArraySetAsSeries(buf,false) 把序列方向关掉,按普通数组从 0 递增处理;打印前再 ArraySetAsSeries(buf,true) 打开,此时 price[0] 指向当前最新柱。方向不切换,打出来的数会对不上图表。 下面的代码片段顺带做了性能统计:三层循环里 m 跑 0~3、p 跑 0~6、periodMA 跑 1~100,每次调 Test()。用 GetTickCount() 卡起止时间,最后除以 1000 和循环次数,得到单次平均耗时(秒)。外汇与贵金属品种波动剧烈,这类基准测试请在模拟盘跑,真实点差可能让耗时分布偏移。

MQL5 / C++
class="type">void PrintTest(const class="type">int position, const class="type">class="kw">double &price[])
{
   Print("总计时间 [msec] ",(endGTC-startGTC));
   Print("性能 [sec] ",time);
   Print(position," - 数组元素 = ",price[position]);
}
class=class="str">"cmt">//---
   ArraySetAsSeries(buf,false);
   ArraySetAsSeries(close,false);
   startGTC=GetTickCount();
class=class="str">"cmt">//---
   for(m=class="num">0;m<=class="num">3;m++)
     {
      for(p=class="num">0;p<=class="num">6;p++)
        {
         for(periodMA=class="num">1;periodMA<=class="num">100;periodMA++)
           {
            Test();
           }
        }
     }
class=class="str">"cmt">//---
   endGTC=GetTickCount();
   time=endGTC-startGTC;
   time=time/class="num">1000/m/p/periodMA;
class=class="str">"cmt">//--- 输出结果
   ArraySetAsSeries(buf,true);
   ArraySetAsSeries(close,true);
   PrintTest(class="num">0,buf);
   PrintTest(class="num">0,close);
class=class="str">"cmt">//---

◍ 周期参数只拖慢 LWMA 计算

在案例 6 的基准上继续跑了一轮对照:固定 144 周期扫 M1/M5/M15/M30,再固定 M1 把均线周期从 21 拉到 987。12 组配置用带 GUI 的自动测试程序出数,X 轴取对数时间序列。 时间框架对计算耗时基本没影响,M1 和 M30 跑出来在同一量级。SMA、EMA、SMMA 三种模型也不吃周期参数,周期从 21 加到 987 耗时纹丝不动。 唯独 LWMA 是例外:周期变大后计算从 0.00373 秒劣化到 0.145 秒,慢了近 40 倍。做高频均线重载的 EA 若用 LWMA,周期参数得谨慎给大值。外汇与贵金属杠杆高,回测耗时膨胀可能直接影响实盘信号延迟,属高风险环节。 下面四段是四种均线的测试封装,核心都是先 CopyClose 取价、ArrayCopy 转缓冲,再调对应的 OnBuffer 函数: //+------------------------------------------------------------------

//Test_SMA Model: MODE_SMA

//+------------------------------------------------------------------ void Test_SMA(int periodMA,ENUM_TIMEFRAMES periodTF) { CopyClose(_Symbol,periodTF,0,count,close); int total=ArrayCopy(arr,close); SimpleMAOnBuffer(total,0,0,periodMA,arr,buf); } //+------------------------------------------------------------------

//Test_EMA Model: MODE_EMA

//+------------------------------------------------------------------ void Test_EMA(int periodMA,ENUM_TIMEFRAMES periodTF) { CopyClose(_Symbol,periodTF,0,count,close); int total=ArrayCopy(arr,close); ExponentialMAOnBuffer(total,0,0,periodMA,arr,buf); } //+------------------------------------------------------------------

//Test_SMMA Model: MODE_SMMA

//+------------------------------------------------------------------ void Test_SMMA(int periodMA,ENUM_TIMEFRAMES periodTF) { CopyClose(_Symbol,periodTF,0,count,close); int total=ArrayCopy(arr,close); SmoothedMAOnBuffer(total,0,0,periodMA,arr,buf); } //+------------------------------------------------------------------

//Test_LWMA Model: MODE_LWMA

//+------------------------------------------------------------------ void Test_LWMA(int periodMA,ENUM_TIMEFRAMES periodTF) { CopyClose(_Symbol,periodTF,0,count,close); int total=ArrayCopy(arr,close); LinearWeightedMAOnBuffer(total,0,0,periodMA,arr,buf); } 开 MT5 把 Test_LWMA 的周期实参改成 987 跑一遍,能直接复现那 0.145 秒的耗时拐点。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| Test_SMA                          Model: MODE_SMA   |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test_SMA(class="type">int periodMA,ENUM_TIMEFRAMES periodTF)
  {
   CopyClose(_Symbol,periodTF,class="num">0,count,close);
   class="type">int total=ArrayCopy(arr,close);
   SimpleMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| Test_EMA                          Model: MODE_EMA   |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test_EMA(class="type">int periodMA,ENUM_TIMEFRAMES periodTF)
  {
   CopyClose(_Symbol,periodTF,class="num">0,count,close);
   class="type">int total=ArrayCopy(arr,close);
   ExponentialMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| Test_SMMA                         Model: MODE_SMMA  |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test_SMMA(class="type">int periodMA,ENUM_TIMEFRAMES periodTF)
  {
   CopyClose(_Symbol,periodTF,class="num">0,count,close);
   class="type">int total=ArrayCopy(arr,close);
   SmoothedMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| Test_LWMA                         Model: MODE_LWMA  |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Test_LWMA(class="type">int periodMA,ENUM_TIMEFRAMES periodTF)
  {
   CopyClose(_Symbol,periodTF,class="num">0,count,close);
   class="type">int total=ArrayCopy(arr,close);
   LinearWeightedMAOnBuffer(total,class="num">0,class="num">0,periodMA,arr,buf);
  }

均线算法选错会拖慢回测

把 SMA、EMA、LWMA 这类平滑算法写进 EA 时,如果没按数据特性挑算法,MT5 的策略测试器会多跑很多无用迭代。以 1 分钟 EURUSD 历史数据跑 3 个月回测为例,用双重循环手算 SMA 比调用内置 iMA 慢约 40%,CPU 占用也更陡。 外汇和贵金属杠杆高、滑点跳空频繁,回测快不等于实盘能复现,算法效率只是基础项,别当成胜率保证。 写指标前先想清楚:序列是否加权、是否要边界不变。选错内核,后面调参都是在给垃圾回收擦屁股。

把回测算力对比交给小布
这些不同周期与价格类型的均线耗时诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到跨平台参考值,你只需专注策略逻辑。

常见问题

iMA 首次调用在全局缓存建指标副本,后续同参调用仅增引用计数直接读值,需用唯一参数循环避开缓存命中才能测到真实算力。
实验相对值显示线性加权在部分调用法下开销略高,但绝对秒数受 CPU 与数据规模影响,结论倾向参考相对排名而非绝对值。
组织输入参数唯一的循环放大总耗时后再取平均,可削弱毫秒误差,文中案例用周期、周期帧与价格类型三维遍历实现。
可以,小布内置了多时间帧与价格类型的 MQL5 调用参考,省去你本地跑 GetTickCount 循环的步骤。
无绝对保证,外汇贵金属属高风险品种,相对性能在文中稳定但绝对值随硬件与客户设置浮动,可能仅在特定条件下明显。