自置缓存的指标速度比较(基础篇)
◍ 自置缓存指标到底能快多少
在 MT5 里写指标,很多人习惯每次 OnCalculate 都重算全部缓冲。Vladimir Karputov 在 2018-05-07 发布的自置缓存对比案例显示,把已算过的值留在自定义缓存数组里、只在新增 Bar 上补算,和每次全量重算相比,空载刷新耗时从约 5.8 ms 降到 1.2 ms 量级(基于当时 5779 次调用采样)。 外汇与贵金属行情 tick 密集,指标如果每根 K 都全量循环,CPU 占用会在新闻波动段明显抬升,终端卡顿概率增加。 做法上,核心是维护一个与 rates_total 等长的缓存数组,用 prev_calculated 判断增量区间,只对新索引算一次。这样开 MT5 按 F7 重编译后,能在「专家属性—测试」里直接看到计算时间下降。
换个姿势读指标,先比谁更快
如果你用惯了 MQL5 里标准的指标句柄访问方式,可能会忽略一个事实:在高频轮询或逐根 K 线重算的场景下,传统调用存在隐性开销。 我们不妨把 MQL5 原生访问,和一种改良过的 MQL4 风格访问摆到一起测速。后者思路来自早前“由指标制作的快餐”一文,但做了缓存层优化,分带缓存与不带缓存两种变体。 这类对比的价值不在于争论语法优雅度,而在于实盘里 EA 每 tick 拉一次指标缓冲,累积出来的延迟差异可能直接影响信号触发时机。外汇与贵金属市场高杠杆、滑点随机,任何额外毫秒级卡顿都值得警惕。
「句柄编号不是从零排的」
很多人默认 MT5 会给指标句柄从 0 开始连续编号,实测并非如此。在 AUDJPY M15 上加载一个创建 6 个 MACD 句柄的 EA,终端重启后立刻运行,打印出的句柄是从 10 开始而不是 0。 把同样的 EA 再挂到 USDJPY M15(ChartID 131571247244850510),它自己的句柄也是从 10 起算,说明编号不跨图表归零,也不是全局连续计数器。 进一步做相同图表实验:两张 AUDJPY M15(不同 ChartID)各跑一个注释掉周期扩展、参数完全相同的 EA,创建 6 个同输入 MACD 句柄,返回的数值完全一样。但 EA 各自在独立线程执行,删除时通过 IndicatorRelease 释放,文档明确“一个 EA 一个线程”,所以同数值在不同 ChartID 下被 MQL5 内部判定为两个不同句柄。 落地结论:用户拿到的句柄号不连续、不从零起;MQL5 内部记账会区分技术指标函数、指标输入、品种、时间帧以及 EA 所在 ChartID。写多图表 EA 时别用句柄值当全局唯一 ID,高风险品种上误删或复用句柄可能导致指标引用错乱。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| iMACD and IndicatorRelease.mq5 | class=class="str">"cmt">//| 版权所有 © class="num">2018, Vladimir Karputov | class=class="str">"cmt">//| http://wmua.ru/slesar/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 © class="num">2018, Vladimir Karputov" class="macro">#class="kw">property link "http:class=class="str">"cmt">//wmua.ru/slesar/" class="macro">#class="kw">property version "class="num">1.003" class=class="str">"cmt">//--- 输入参数 input class="type">int count=class="num">6; class=class="str">"cmt">// MACD 指标计数 class="type">int handles_array[]; class=class="str">"cmt">// 保存 iMACD 指标句柄的数组 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="type">int array_resize=ArrayResize(handles_array,count); if(array_resize==-class="num">1) { Print("ArrayResize 错误# ",GetLastError()); class="kw">return(INIT_FAILED); } if(array_resize!=count) { Print("ArrayResize != \"MACD 指标计数\""); class="kw">return(INIT_FAILED); } ArrayInitialize(handles_array,class="num">0); for(class="type">int i=class="num">0;i<count;i++) { handles_array[i]=CreateHandleMACD(class="num">12+i); class=class="str">"cmt">//--- 如果句柄未能创建 if(handles_array[i]==INVALID_HANDLE) { class=class="str">"cmt">//--- 告之失败并输出错误代码 PrintFormat("无法为品种 %s/%s 创建 iMACD 指标的句柄, 错误代码 %d", Symbol(),
◍ 多图表 MACD 句柄的初始化与释放
在 MT5 的 EA 里批量挂多个 iMACD 句柄时,初始化阶段若某个句柄创建失败,应当立刻用 EnumToString(Period()) 和 GetLastError() 打印上下文并 return(INIT_FAILED),否则后续 OnTick 读到空句柄会直接吐脏数。 OnDeinit 里不能只 Comment("") 清屏就完事,必须用 IndicatorRelease(handles_array[i]) 逐个释放,并打印删除结果;实测不释放的话,切周期重加载时句柄数会累积,可能拖慢终端响应。 OnTick 的轮询逻辑值得抄:只把前 15 个图表的 MACD 主线(shift=1)通过 Comment 显示,超出的用「只显示前 15 个指标 ...」截断。这样在多品种监控时,右下角注释不会爆屏,但也意味着第 16 个之后的品种你肉眼看不到,得自己改阈值或写文件日志。 外汇与贵金属品种波动大、滑点随机,这类多句柄监控仅作辅助参考,信号延迟或漏显都可能发生,请务必在策略测试器里先跑一遍再上实盘。
EnumToString(Period()), GetLastError()); class=class="str">"cmt">//--- 指标提前停止 class="kw">return(INIT_FAILED); } Print("图表标识符: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),class="num">7), ", 创建句柄 iMACD(",handles_array[i],")"); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统逆初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Comment(""); for(class="type">int i=class="num">0;i<count;i++) { Print("图表标识符: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),class="num">7), ", 删除句柄 iMACD(",handles_array[i],"): ",IndicatorRelease(handles_array[i])); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统逐笔报价函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- class="type">class="kw">string text=""; for(class="type">int i=class="num">0;i<count;i++) { class="type">class="kw">double macd_main_1=iMACDGet(handles_array[i],MAIN_LINE,class="num">1); if(i<class="num">15) { text+="\n"+"ChartID: "+IntegerToString(ChartID())+": "+Symbol()+ ", MACD#"+IntegerToString(i)+" "+DoubleToString(macd_main_1,Digits()+class="num">1); Comment(text); } else if(i==class="num">15) { text+="\n"+"只显示前 class="num">15 个指标 ..."; Comment(text); } } }
MACD 句柄的缓存区取法与日志实证
在 MT5 里调 iMACD,先得搞清楚它的缓存区编号:0 是主线条(MAIN_LINE),1 是信号线(SIGNAL_LINE)。后面写取值函数时,buffer 参数传 0 还是 1,直接决定你拿到的是柱差还是信号线穿越关系。 下面这段封装函数把 CopyBuffer 包了一层:传入指标句柄、缓存区编号、索引,先 ResetLastError 清掉旧错误码,再尝试拷贝 1 根数据。若返回小于 0,就打印错误码并回退 0.0,意味着该根视为未计算;正常则返回 MACD[0]。 double iMACDGet(const int handle_iMACD,const int buffer,const int index) { double MACD[1]; ResetLastError(); if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0) { PrintFormat("无法从 iMACD 指标复制数据,错误代码 %d",GetLastError()); return(0.0); } return(MACD[0]); } 创建句柄的函数更短,只暴露 fast_ema_period 一个入参,慢速 EMA 写死 52、信号周期写死 9、价格用收盘价:return(iMACD(Symbol(),Period(),fast_ema_period,52,9,PRICE_CLOSE));。这套参数在 AUDJPY 的 M15 上跑过,2018.02.16 09:36:30.240 的单条日志里,同一图表(ChartID 131571247244850509)连续建出句柄 10 到 14 共 5 个 iMACD 实例——说明循环创建时若不及时 IndicatorRelease,句柄会一直累加,实盘里可能拖慢终端。 外汇与贵金属波动剧烈、杠杆风险高,句柄泄漏虽不直接影响报价,但堆积多了会让 EA 初始化变慢,倾向在 OnDeinit 里显式释放。
class="type">class="kw">double iMACDGet(const class="type">int handle_iMACD,const class="type">int buffer,const class="type">int index) { class="type">class="kw">double MACD[class="num">1]; ResetLastError(); if(CopyBuffer(handle_iMACD,buffer,index,class="num">1,MACD)<class="num">0) { PrintFormat("无法从 iMACD 指标复制数据,错误代码 %d",GetLastError()); class="kw">return(class="num">0.0); } class="kw">return(MACD[class="num">0]); } class="type">int CreateHandleMACD(const class="type">int fast_ema_period) { class="kw">return(iMACD(Symbol(),Period(),fast_ema_period,class="num">52,class="num">9,PRICE_CLOSE)); }
「句柄重复创建拖慢初始化」
上面这段日志来自一次 M15 多品种 MACD 扫描实测:2018.02.16 09:37:32 这一秒内,USDJPY 连续建出 10~15 共 6 个 iMACD 句柄,AUDJPY 此前也已建了 15 号句柄。 问题出在 OnInit 里的循环——CreateHandleMACD(12/*+i*/) 被写死成固定参数 12,注释掉的 +i 让每次迭代都申请同一个周期的句柄,MT5 不会复用,只能重复创建。 到了 2018.02.18 07:53:13,AUDJPY 的 10 号句柄在日志里连刷三次,说明重启后旧句柄未释放干净又重新建了一遍。外汇与贵金属杠杆高、品种多,这种浪费会让 EA 初始化明显变慢,也可能在切换图表时卡顿。 把 +i 放开,让句柄按数组下标错位生成,并在反初始化里调 IndicatorRelease 逐个释放,才是正路。开 MT5 把下面这段循环改完,跑一次就能从专家日志看到句柄数从重复变成递增。
class="type">int OnInit() { ArrayInitialize(handles_array,class="num">0); for(class="type">int i=class="num">0;i<count;i++) { handles_array[i]=CreateHandleMACD(class="num">12class=class="str">"cmt">/*+i*/); class=class="str">"cmt">//--- 如果句柄未能创建 } }