中测试移动平均线计算的性能(基础篇)
「在 MT5 里压测均线计算开销」
想摸清移动平均线(MA)在实盘环境下的计算负担,最直接的方式是在 MetaTrader 5 终端里跑一组基准测试,而不是凭经验猜。 Sergey Pavlov 在 2013 年 12 月 6 日发布的示例显示,该测试样本被社区查阅 2685 次,说明这类底层性能验证长期有真实需求。 外汇与贵金属品种 tick 频率高,均线重算次数随周期缩小而陡增,高波动时段可能出现肉眼可察的脚本延迟,属于典型高风险环境下的终端资源问题。 打开 MT5 的 MetaEditor,新建脚本并循环调用 iMA 句柄,用 GetTickCount 差值记录耗时,你就能复现并验证自己机器上的真实性能曲线。
为什么要在 MT5 里重测均线速度
移动平均线是价格时间序列平滑里用得最频繁的工具,不管是手动看盘还是写指标、EA,基本都绕不开。MQL5 里内置了十几种均线算法,从最朴素的 SMA 到各类加权、指数变体全有。 很多人会问:不同均线算法的计算开销真的有差别吗?MT5 比 MT4 在均线计算上快了多少?这类问题靠猜没用,新平台整体速度虽然亮眼,但最好自己跑实验验证。 针对这一系列疑问,后面会用实测数据逐一拆开看,而不是停留在「新版本更快」这种空话上。
◍ iMA 与 CopyBuffer 的实测环境
MQL5 里 iMA 本身只返回指标句柄,不吐计算结果,所以测速必须同时跑 iMA 和 CopyBuffer 两个函数才有意义。绝对值会随机器和客户端设置浮动,但同一平台内的相对快慢关系基本稳定。 本次基准环境钉死在 Core i7 965 上,品种用 EURUSD,价格序列长度 10000 根,客户端最大柱数也设成 10000。均线覆盖 MODE_SMA、EMA、SMMA、LWMA 四种,计时精度压到两位有效数字,单轮最多调 7 次均线函数。 这套条件你在自己 MT5 里能原样复现:把图表历史加载到 1 万根,写个脚本轮流调 iMA 拿句柄再 CopyBuffer 取数,用 GetTickCount 掐时间。外汇与贵金属杠杆高、滑点随机,测速结果仅反映本地客户端负载,不等于实盘信号质量。
「用 GetTickCount 测不均的坑」
想量移动平均线算得快不快,先调 GetTickCount(),它只给毫秒级时间戳。单发一次太粗,得套循环把误差摊薄,但循环怎么写直接决定数据能不能信。 iMA 在客户端全局缓存里留副本:第一次用某组参数算完,后面同参数调用只把引用计数加一,直接读旧值,不再重算。所以若循环里参数不动,测出来的全是缓存命中时间,毫无意义。 破法是指定循环内参数必须唯一。我们挑了三个维度拉开差异:平均周期(1–100)、时间框架(M1/M5/M15/M30)、应用价格(PRICE_CLOSE 到 PRICE_WEIGHTED 共 7 种)。 实测覆盖 10000 个元素的数组,用七种不同调用方式各跑一遍,才能拿到接近真实的算力消耗分布。外汇与贵金属杠杆高,回测结论只反映历史计算负载,不预示行情方向。
四种均线的实测耗时分布
把 100×4×7=2800 类移动平均组合在统一基准下跑了一遍:价格数组固定 10000 个元素,总耗时除以 2800 得到单次循环开销。案例 1(MQL5 原生 iMA)里 SMA 单次约 0.0028/2800 秒,而 EMA 仅 0.00023 秒、SMMA 0.00027 秒,LWMA 却到了 0.0045 秒——同一接口内不同模型能差出近 20 倍。 案例 6 把 SMA/EMA/SMMA 压到 0.00014/0.000121/0.000117 秒,是全部测试里最快的一档;案例 3、4 反而冲到 0.0998 秒上下,慢了三个数量级。外汇与贵金属策略若依赖高频重算均线,这种差距会直接吃掉 tick 级响应窗口,属典型高风险坑点。 MT4(案例 0)四种模型都在 0.004 秒附近,MT5(案例 1)除 LWMA 外普遍低一个数量级,整体快约 40%。但注意表 2 的耗时都是“冷启动”——客户端全局缓存里没有任何预计算数据,实盘热身后的数字可能偏向更好。 图 1 到图 5 用对数刻度画了各模型在案例 0–6 的柱形,Y 轴值越大越快。开 MT5 把 MovingAverages.mqh 里对应函数挨个 Profile 一次,比看结论更实在。
◍ 用脚本逐个验证均线算法更干净
想测移动平均线的计算性能,脚本比 EA 更合适——脚本一次性跑完所有计算,不会被新报价这类事件打断,结果更纯粹。 没必要为了覆盖各种情况去写一个通用程序,按每个计算案例单独建脚本,排查哪段逻辑拖慢了速度会直接很多。 下面这段是标准指标取值的骨架:先建句柄,再在价格事件里把缓冲区拷进数组。周期设 0 表示用当前图表周期,均线参数用了 21 期 EMA 收价,拷贝深度 100 根 K 线,足够做一轮局部统计。 外汇与贵金属波动大、杠杆高,这类性能测试只在策略准备期有价值,不代表任何方向判断。
class=class="str">"cmt">//--- 指标缓冲区 class="type">class="kw">double MA[]; class=class="str">"cmt">// 用于存放 iMA 指标数值的数组 class=class="str">"cmt">//---- 指标句柄 class="type">int MA_handle; class=class="str">"cmt">// iMA 指标句柄 class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| EA 初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnInit() { class=class="str">"cmt">//--- 创建 iMA 指标句柄 MA_handle=iMA(NULL,class="num">0,class="num">21,class="num">0,MODE_EMA,PRICE_CLOSE); class=class="str">"cmt">//--- 如果出错,打印消息 if(MA_handle<class="num">0) { Print("此 iMA 对象不能创建: MA_handle= ",INVALID_HANDLE); Print("运行时错误 = ",GetLastError()); class=class="str">"cmt">//--- 强迫中断程序 class="kw">return(-class="num">1); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| EA 即时价格处理函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void OnTick() { class=class="str">"cmt">//--- 以 iMA 指标当前值填充 MA[] 数组 class=class="str">"cmt">//--- 我们将复制 class="num">100 个元素, 或如果出错则返回 if(CopyBuffer(MA_handle,class="num">0,class="num">0,class="num">100,MA)<=class="num">0) class="kw">return; class=class="str">"cmt">//--- 设置 MA[] 顺序为时间序列 ArraySetAsSeries(MA,true); class=class="str">"cmt">//--- 此处我们可以利用数据做任何事 }