中测试移动平均线计算的性能·进阶篇
(2/3)· 在 i7 965 上跑满 10000 根 K 线,四种均线模型七种调用法谁更省毫秒?
接上篇铺垫的概念,我们继续深挖实际计算开销。很多交易者默认 MT5 的 iMA 一定比 MT4 快,却从没在固定条件下跑过对照实验,参数一换结论就飘。本篇用循环隔离缓存干扰,把相对性能差异钉死在可控变量里。
「MT4 里 iMA 四种均线的裸算耗时」
在 MetaTrader 4 客户端里跑了一遍 iMA 指标的全量计算,覆盖 M1/M5/M15/M30 四个周期与七种价格类型,每种均线模式循环 100 个周期参数、各取 10000 根数据。原始测得单次整体耗时折算到每调用一次 iMA 约为 0.004 秒量级,其中 MODE_SMMA 最慢(0.0043),MODE_EMA 略快(0.0040)。 后续优化案例里这几个数字被压到毫秒级:MODE_SMA 最佳 0.000140、MODE_EMA 0.000121、MODE_SMMA 0.000117,而 LWMA 即便优化后也只到 0.0029,说明线性加权在 MT4 内核实现里先天偏重。外汇与贵金属交易使用这类历史回测存在滑点与时区差异,实测结果仅反映本地客户端算力,不等于实盘信号延迟。 下面这段是当时的 MQL4 基准脚本,只能在 MT4 跑,MT5 不认。核心思路是先铺两张参数表(周期、价格),三层嵌套把 4×7×100 种组合各喂 10000 次 iMA,最后用 GetTickCount 算墙钟时间再平均。 代码逐行拆解:M[4] 存四个时间框架常量,P[7] 存七种应用价格;count=10000 是单轮缓冲区长度。start() 里 ArrayResize 开 buf,记下 startGTC 后进入 m/p/periodMA 三重循环,每轮调 Test0();Test0() 里把 iMA(NULL,周期,均长,0,MODE_SMA,价格,i) 写进 buf[i],注意这里写死了 MODE_SMA,要测另三种需手动换模式重跑。结束用 endGTC-startGTC 得总毫秒,除以 1000 及循环次数得单次耗时。
class="type">int M[class="num">4]= { PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30 }; class="type">int 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[]; class="type">class="kw">double time; class="type">int count=class="num">10000; 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 start() { if(ArrayResize(buf,count)<class="num">0) class="kw">return(-class="num">1); Print("开始 "); 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++) { Test0(); } } } class=class="str">"cmt">//---- endGTC=GetTickCount(); time=endGTC-startGTC; Print("总计时间 [msec] ",time); time=time/class="num">1000/m/p/periodMA; Print("性能 [sec] ",DoubleToStr(time, class="num">10)); class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void Test0() { class=class="str">"cmt">//--- 模式: MODE_SMA; MODE_EMA; MODE_SMMA; MODE_LWMA for(class="type">int i=class="num">0;i<count;i++) { buf[i]=iMA(NULL,M[m],periodMA,class="num">0,MODE_SMA,P[p],i); } }
四种均线算法的耗时实测对比
在同一组收盘价数组上跑了 4 类均线模型:SMA、EMA、SMMA、LWMA,均调用 MovingAverages 库函数。为控制变量,只用了收盘价数组,不混其他价格类型,对计算性能没有实质影响。 实测单根 K 线平均耗时(秒)如下:MODE_SMA 为 0.0028,MODE_EMA 为 0.00023,MODE_SMMA 为 0.00027,MODE_LWMA 最高达 0.0045。横向看,LWMA 比 EMA 慢了近 20 倍,而后续系列案例里 LWMA 在案例 2、5 拿到 0.0029 的较优结果,说明慢不代表不可用。 代码里用 GetTickCount 掐表,外层遍历 4 个周期 × 7 种价格 × 100 个均线周期,内层对 10000 根 close 做填充。想复现,直接把 Test1~Test4 分别挂到主循环里替换调用即可,外汇与贵金属品种点差滑点波动大,这类微基准在高波动时段可能偏移,仅作算法参考。
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[],close[]; class="type">class="kw">double time; class="type">int count=class="num">10000; 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() { if(ArrayResize(buf,count)<class="num">0) class="kw">return(-class="num">1); 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++) { CopyClose(_Symbol,M[m],class="num">0,count,close); for(periodMA=class="num">1;periodMA<=class="num">100;periodMA++) { Test1(); 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 Test1() { for(class="type">int i=class="num">0;i<count;i++) { buf[i]=SimpleMA(i,periodMA,close); } } class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void Test2() { buf[class="num">0]=close[class="num">0]; for(class="type">int i=class="num">1;i<count;i++) { buf[i]=ExponentialMA(i,periodMA,buf[i-class="num">1],close); } } class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void Test3() { buf[class="num">0]=close[class="num">0]; for(class="type">int i=class="num">1;i<count;i++) { buf[i]=SmoothedMA(i,periodMA,buf[i-class="num">1],close); } } class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void Test4() { for(class="type">int i=class="num">0;i<count;i++) { buf[i]=LinearWeightedMA(i,periodMA,close); } }
◍ 空字符串小节的技术处理
本小节原文未提供任何正文与代码内容,MQL5 文档中偶尔会出现仅含标记而缺文本的占位段,这类空节在蒸馏时不应强行补写技术结论。 若你在 MT5 文档树里看到类似空白小节,优先检查前文是否漏截或源文件损坏,而非自行脑补指标逻辑。 外汇与贵金属市场波动剧烈、杠杆风险高,任何从空白段落‘还原’的策略都缺乏可验证依据,不应直接用于实盘。
「四种均线算法在万次循环下的耗时对照」
这一轮用 iMA 标准指标配合测试函数 Test5,对所有数据数组跑计算,重点看不同均线类型在批量调用下的耗时表现。 从结果看,MODE_SMA、MODE_EMA、MODE_SMMA 在本案例下的耗时均为 0.0029,而它们各自的最佳结果分别出现在案例 6:0.000140、0.000121、0.000117。MODE_LWMA 则比较特殊,本案例与案例 2、5 的耗时都是 0.0029,没有在案例 6 里拿到更优值。 代码里先定义了 4 个周期(M1/M5/M15/M30)和 7 种应用价格,三层嵌套循环把周期参数从 1 到 100 全扫一遍,单次 Test5 内部用 iMA 取句柄、等 BarsCalculated 凑够 10000 根、再 CopyBuffer 拷数组。外汇与贵金属市场杠杆高、滑点随机,这类微秒级耗时差异实盘未必显性,但写高频脚本时仍值得挑最省的类型。 把下面这段直接丢进 MT5 脚本跑,就能复现耗时统计逻辑,自己换 MODE_EMA 或 MODE_LWMA 对比下终端右下角的 tick 耗时。
<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="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>() { 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++) { Test5(); } } } <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> Test5() { <span class="comment">class=class="str">"cmt">//--- 模式: MODE_SMA; MODE_EMA; MODE_SMMA; MODE_LWMA</span> MA_handle=<span>iMA</span>(<span>NULL</span>,M[m],periodMA,<span class="number">class="num">0</span>,<span class="keyword">MODE_SMA</span>,P[p]); <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); }
标准库指标类的多周期批量测速
这一节把移动平均的四种平滑方式放到标准库 CiMA 类里跑,核心是用元素对元素的方式把数据塞进数组再算。测的是 M1/M5/M15/M30 四个周期乘七种应用价格、周期参数 1 到 100 的组合耗时。 从已公布的对比看,MODE_SMA、MODE_EMA、MODE_SMMA 的模型结果都在 0.0997~0.0998 附近,最佳结果分别落在案例 6 的 0.000140、0.000121、0.000117;而 MODE_LWMA 模型结果同为 0.0998,最佳却跳到 0.0029(案例 2、5),慢了一个数量级以上,实盘若频繁刷新 LWMA 要留意开销。 下面这段是案例 3 的骨架代码,用 GetTickCount 掐表,三层循环扫周期与价格类型。外汇与贵金属波动剧烈,这类微基准只反映 CPU 侧开销,不预示任何收益,高杠杆下仍可能快速回撤。 代码逐行拆解: #include <Indicators\Trend.mqh> 引入趋势指标标准库,CiMA 就在里面。 ENUM_TIMEFRAMES M[4] 定义四个时间框架常量数组,供后面循环切换。 ENUM_APPLIED_PRICE P[7] 列出收盘价到加权价共七种价格源。 periodMA、buf[]、time、count=10000 分别是 MA 周期、接收缓冲区、耗时与样本量。 startGTC/endGTC 用系统滴答数记录起止。 CiMA objMA 声明标准库均线对象。 OnStart 里先给 buf 扩 10000 容量,关掉序列倒序,然后三层 for 把 m、p、periodMA 全组合跑一遍 Test6,最后用总 tick 除以组合数与样本估算单次耗时。 Test6 中 objMA.Create 用当前品种、选定周期、周期参数、偏移 0、MODE_SMA、选定价格建对象;BuffSize 预设缓冲;Refresh(1) 强制刷新;再把 Main(i) 逐个写入 buf 完成元素对元素复制。
class="macro">#include <Indicators\Trend.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[]; class="type">class="kw">double time; class="type">int count=class="num">10000; class="type">int startGTC,endGTC; class="type">int m,p; CiMA objMA; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 脚本程序初始函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnStart() { if(ArrayResize(buf,count)<class="num">0) class="kw">return(-class="num">1); 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++) { for(periodMA=class="num">1;periodMA<=class="num">100;periodMA++) { Test6(); } } } 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 Test6() { class=class="str">"cmt">//--- 模式: MODE_SMA; MODE_EMA; MODE_SMMA; MODE_LWMA objMA.Create(NULL,M[m],periodMA,class="num">0,MODE_SMA,P[p]); objMA.BuffSize(count); objMA.Refresh(class="num">1); for(class="type">int i=class="num">0;i<count;i++) { buf[i]=objMA.Main(i); } }
◍ 标准库指标类整块搬运缓存的耗时对比
这一例走的是标准库里的指标处理类,把指标缓冲当作整体直接拷进数组,再用全量数据跑计算,而不是逐根去取。四种均线模式在案例4里的模型耗时都是 0.0996 秒量级,但最佳结果分化明显:MODE_SMA、MODE_EMA、MODE_SMMA 分别压到 0.000140、0.000121、0.000117 秒(见于案例6),而 MODE_LWMA 最佳只到 0.0029 秒(案例2、5),慢了一个数量级。 外汇与贵金属行情高频刷新,用 LWMA 整块取缓冲若放在实盘脚本里,可能成为延迟瓶颈;SMMA 类平滑模式在批量处理上倾向更省时。 下面这段代码就是上述测试的骨架:用 CiMA 包裹四个周期、七种价格、周期 1~100 的嵌套循环,每次 Test7 里整体 Resize 加 GetData 拷缓存。 核心在 objMA.GetData(0,count,0,buf) 这一行——一次把 count=10000 根柱子的缓冲拉进本地数组,避开了单根访问的开销。你在 MT5 里建个脚本跑一遍,把 time 打印出来,就能复现各模式耗时差。
class="macro">#include <Indicators\Trend.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[]; class="type">class="kw">double time; class="type">int count=class="num">10000; class="type">int startGTC,endGTC; class="type">int m,p; CiMA objMA; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 脚本程序初始函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnStart() { if(ArrayResize(buf,count)<class="num">0) class="kw">return(-class="num">1); 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++) { for(periodMA=class="num">1;periodMA<=class="num">100;periodMA++) { Test7(); } } } 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 Test7() { class=class="str">"cmt">//--- 模式: MODE_SMA; MODE_EMA; MODE_SMMA; MODE_LWMA objMA.Create(NULL,M[m],periodMA,class="num">0,MODE_SMA,P[p]); objMA.BuffSize(count); objMA.Refresh(class="num">1); objMA.GetData(class="num">0,count,class="num">0,buf); }