中测试移动平均线计算的性能·综合运用
(3/3)·从七种调用方法到结果输出,用实验数据看清 MT4 与 MT5 的均线算力差距
很多交易者默认 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 取数组。
<span class="keyword">ENUM_TIMEFRAMES</span> M[<span class="number">class="num">4</span>]= { <span class="keyword">PERIOD_M1</span>, <span class="keyword">PERIOD_M5</span>, <span class="keyword">PERIOD_M15</span>, <span class="keyword">PERIOD_M30</span> }; <span class="keyword">ENUM_APPLIED_PRICE</span> P[<span class="number">class="num">7</span>]= { <span class="keyword">PRICE_CLOSE</span>, <span class="keyword">PRICE_OPEN</span>, <span class="keyword">PRICE_HIGH</span>, <span class="keyword">PRICE_LOW</span>, <span class="keyword">PRICE_MEDIAN</span>, <span class="keyword">PRICE_TYPICAL</span>, <span class="keyword">PRICE_WEIGHTED</span> }; <span class="keyword">class="type">int</span> periodMA; <span class="keyword">class="type">class="kw">double</span> time; <span class="keyword">class="type">int</span> count=<span class="number">class="num">10000</span>; <span class="keyword">class="type">int</span> startGTC,endGTC; <span class="keyword">class="type">int</span> m,p; <span class="keyword">class="type">class="kw">double</span> MA[]; <span class="comment">class=class="str">"cmt">// 用于存放 iMA 指标数组</span> <span class="keyword">class="type">int</span> MA_handle; <span class="comment">class=class="str">"cmt">// iMA 指标句柄</span> <span>class="type">MqlParam</span> params[]; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">//| 脚本程序初始函数 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span> <span class="keyword">class="type">int</span> <span class="functions">OnStart</span>() { <span class="functions">ArrayResize</span>(params,<span class="number">class="num">4</span>); startGTC=<span class="functions">GetTickCount</span>(); <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">for</span>(m=<span class="number">class="num">0</span>;m<=<span class="number">class="num">3</span>;m++) { <span class="keyword">for</span>(p=<span class="number">class="num">0</span>;p<=<span class="number">class="num">6</span>;p++) { <span class="keyword">for</span>(periodMA=<span class="number">class="num">1</span>;periodMA<=<span class="number">class="num">100</span>;periodMA++) { Test8(); } } } <span class="comment">class=class="str">"cmt">//---</span> endGTC=<span class="functions">GetTickCount</span>(); time=endGTC-startGTC; time=time/<span class="number">class="num">1000</span>/m/p/periodMA; <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="kw">return</span>(<span class="number">class="num">0</span>); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span> <span class="keyword">class="type">void</span> Test8() { <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> params[<span class="number">class="num">0</span>].type =<span class="keyword">TYPE_INT</span>; params[<span class="number">class="num">0</span>].integer_value=periodMA; <span class="comment">class=class="str">"cmt">//--- 设置 ma_shift</span> params[<span class="number">class="num">1</span>].type =<span class="keyword">TYPE_INT</span>; 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> params[<span class="number">class="num">2</span>].type =<span class="keyword">TYPE_INT</span>; 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> params[<span class="number">class="num">3</span>].type =<span class="keyword">TYPE_INT</span>; params[<span class="number">class="num">3</span>].integer_value=P[p]; <span class="comment">class=class="str">"cmt">//--- 创建 MA</span> 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); <span class="keyword">class="kw">while</span>(<span class="functions">BarsCalculated</span>(MA_handle)<count){} <span class="functions">CopyBuffer</span>(MA_handle,<span class="number">class="num">0</span>,<span class="number">class="num">0</span>,count,MA); }
四种均线算法在同一缓冲上的耗时对照
这一节把 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 上限就能复测你自己的品种。
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 和循环次数,得到单次平均耗时(秒)。外汇与贵金属品种波动剧烈,这类基准测试请在模拟盘跑,真实点差可能让耗时分布偏移。
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 秒的耗时拐点。
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 占用也更陡。 外汇和贵金属杠杆高、滑点跳空频繁,回测快不等于实盘能复现,算法效率只是基础项,别当成胜率保证。 写指标前先想清楚:序列是否加权、是否要边界不变。选错内核,后面调参都是在给垃圾回收擦屁股。