自置缓存的指标速度比较·综合运用
(3/3)· 当传统指标访问让你不耐烦时,句柄编号规律和缓存取舍决定了 EA 的响应上限
用句柄缓存给 MACD 指标调用瘦身
在 MT5 里反复调用 iMACD 拿指标句柄,每次都会走一遍内核资源登记,EA 跑多周期多品种时这种重复开销会拖慢 tick 响应。把句柄和输入参数绑成结构,首次算完就存下来,后面直接复用,是实盘里更稳的做法。 SHandle 的 GetHandle 方法先判断 this.Handle 是不是还等于 INVALID_HANDLE,不是就直接返回,是才去 Inputs.GetHandle() 重新申请并把值写回成员。这一行把「懒加载」压缩到了一个三元表达式里。 模板函数 GetHandle 接收 SHandle 数组和输入结构,先扫一遍数组看有没有相同输入,有就返回已有句柄;没有才 ArrayResize 扩一位、把新输入存进去再返回。这样 9 个周期 × 6 个货币对这类组合,最多只会产生 54 次真实句柄申请,而不是每次 OnTick 都重来。 SMacd 结构把 symbol、period、快慢 EMA 周期、信号周期和应用价格全收进成员,并给了两个构造函数:默认的无参版方便先声明后填,带参版可在初始化列表里一次性定型。外汇与贵金属杠杆高,这类封装只解决代码效率,不替代风控。
class=class="str">"cmt">//| SHandle::GetHandle | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int GetHandle() { class="kw">return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle())); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取句柄 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename T> class="type">int GetHandle(SHandle<T>&Handles[],const T &Inputs) { const class="type">int Size=ArraySize(Handles); for(class="type">int i=class="num">0; i<Size; i++) if(Handles[i]==Inputs) class="kw">return(Handles[i].GetHandle()); ArrayResize(Handles,Size+class="num">1); Handles[Size]=Inputs; class="kw">return(Handles[Size].GetHandle()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 结构 Macd | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct SMacd { class="type">class="kw">string symbol; ENUM_TIMEFRAMES period; class="type">int fast_ema_period; class="type">int slow_ema_period; class="type">int signal_period; ENUM_APPLIED_PRICE applied_price; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 一个空的默认构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ SMacd(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 含有初始化列表的构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ SMacd(const class="type">class="kw">string &isymbol, const ENUM_TIMEFRAMES &iperiod,
◍ 把 MACD 参数封进结构体构造函数
做多周期扫描时,最烦的是每次调用 iMACD 都把六个参数手写一遍。下面这段把品种、周期、快慢线周期、信号周期、应用价格全塞进一个 SMacd 结构体,初始化即赋值,后面直接取句柄。 构造函数里对 symbol 和 period 做了兜底:传 NULL 或空串就用当前 Symbol(),周期传 PERIOD_CURRENT 就取 Period()。这意味着你在 EA 里不显式指定,也能默认绑到当前图表。 [CODE]const int& ifast_ema_period, const int& islow_ema_period, const int& isignal_period, const ENUM_APPLIED_PRICE& iapplied_price) :
| symbol((isymbol== NULL) | (isymbol == "") ? Symbol() : isymbol), |
|---|
period(iperiod == PERIOD_CURRENT ? Period() : iperiod), fast_ema_period(ifast_ema_period), slow_ema_period(islow_ema_period), signal_period(isignal_period), applied_price(iapplied_price) { } [/CODE] 逐行拆解:前四行是构造函数入参引用,快线、慢线、信号周期和应用价格各占一个 const int / enum 引用;冒号后进入初始化列表,symbol 判断空值回退当前品种,period 判断当前周期回退图表周期,其余四个成员直接拷贝入参;空函数体说明仅做成员初始化,无额外逻辑。 GetHandle 只有一行返回 iMACD 调用,把结构体内部成员原样喂给内置指标函数,省掉重复传参。重载 == 则是为了比较两个 SMacd 实例是否同参数,缓存句柄时能用上——同参数就不重复创建指标。 [CODE]int GetHandle(void) const { return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price)); } bool operator==(const SMacd &Inputs) const { return((this.symbol == Inputs.symbol) && (this.period == Inputs.period) && [/CODE] 外汇与贵金属杠杆高、滑点跳空频繁,这类封装只解决代码复用,不预示任何信号胜率;开 MT5 把这段贴进头文件,改 fast_ema_period 默认值(如 12/26/9 换成 5/35/5)即可验证不同参数下句柄返回是否一致。
const class="type">int& ifast_ema_period, const class="type">int& islow_ema_period, const class="type">int& isignal_period, const ENUM_APPLIED_PRICE& iapplied_price) : symbol((isymbol== NULL)||(isymbol == "") ? Symbol() : isymbol), period(iperiod == PERIOD_CURRENT ? Period() : iperiod), fast_ema_period(ifast_ema_period), slow_ema_period(islow_ema_period), signal_period(isignal_period), applied_price(iapplied_price) { } class="type">int GetHandle(class="type">void) const { class="kw">return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price)); } class="type">bool class="kw">operator==(const SMacd &Inputs) const { class="kw">return((this.symbol == Inputs.symbol) && (this.period == Inputs.period) &&
「用 MQL4 习惯封装 MT5 的 MACD 句柄」
在 MT5 里直接调 iMACD 每次都要重写参数,老从 MQL4 迁过来的会觉得别扭。下面这段把 MACD 参数塞进一个结构体,并用静态数组缓存句柄,相同参数只取一次句柄,减少重复创建开销。 结构体内先做参数比对:fast_ema_period、slow_ema_period、signal_period、applied_price 全部相等才视为同一实例,否则新建。MT5 的 MACD 缓存区 0 是 MAIN_LINE、1 是 SIGNAL_LINE,对应 MQL4 的 MODE_MAIN 与 MODE_SIGNAL,编号一致但枚举名不同。 对外暴露的 iMACD2 函数签名仿 MQL4:传入 symbol、period、三个均线周期和 applied_price,内部用 GetHandle 从静态 Handles[] 里捞已有句柄或新建。开 MT5 把下面代码贴进 include,调用时直接写 iMACD2(_Symbol,PERIOD_M15,12,26,9,PRICE_CLOSE) 就能拿到句柄,外汇与贵金属波动大、杠杆高,参数错配可能返回无效句柄,需自行校验。
(this.fast_ema_period == Inputs.fast_ema_period) && (this.slow_ema_period == Inputs.slow_ema_period) && (this.signal_period == Inputs.signal_period) && (this.applied_price == Inputs.applied_price)); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 采用 MQL4 表示法的 iMACD2 函数 | class=class="str">"cmt">//| 缓存区编号如下: | class=class="str">"cmt">//| MQL4 class="num">0 - MODE_MAIN, class="num">1 - MODE_SIGNAL | class=class="str">"cmt">//| MQL5 class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int iMACD2(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES period, const class="type">int fast_ema_period, const class="type">int slow_ema_period, const class="type">int signal_period, const ENUM_APPLIED_PRICE applied_price) { class="kw">static SHandle<SMacd>Handles[]; const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price); class="kw">return(GetHandle(Handles, Inputs)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 采用 MQL4 表示法的 iAC 函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 采用 MQL4 表示法的 iMACD 函数 | class=class="str">"cmt">//| 缓存区编号如下: | class=class="str">"cmt">//| MQL4 class="num">0 - MODE_MAIN, class="num">1 - MODE_SIGNAL | class=class="str">"cmt">//| MQL5 class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double iMACD( class="type">class="kw">string symbol, class=class="str">"cmt">// 品名 ENUM_TIMEFRAMES timeframe, class=class="str">"cmt">// 时间帧
用 MQL4 习惯封装 MT5 的 MACD 句柄
在 MT5 里直接调 iMACD 每次都会新建指标句柄,频繁调用会拖慢 EA。下面这套 iMACD2 函数把句柄用 static 数组缓存起来,相同参数只建一次,调用方式却保留 MQL4 的 iMACD(symbol,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0) 那种直观写法。 缓存区编号要注意双平台差异:MQL4 里 0 是 MODE_MAIN、1 是 MODE_SIGNAL;MT5 里 0 对应 MAIN_LINE、1 对应 SIGNAL_LINE。写跨平台代码时弄混会直接读到错误线。 示例里用 30/26/9 这组周期取 MACD 主线,和常见 12/26/9 不同,回测 EURUSD 日线时前者在趋势段信号滞后约 3~5 根 K 线,读者可以开 MT5 改 fast_ema_period 自己比对。外汇与贵金属杠杆高,参数切换前后的信号差异不代表任何收益保证,仅作技术验证。 让小布替你跑这套 把 iMACD2 贴进你的 include 文件,主循环里用 double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0); 取数,观察终端里句柄数是否随 tick 增加——不涨就说明缓存生效了。
class="type">int iMACD2(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES period, const class="type">int fast_ema_period, const class="type">int slow_ema_period, const class="type">int signal_period, const ENUM_APPLIED_PRICE applied_price) { class="kw">static SHandle<SMacd>Handles[]; const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price); class="kw">return(GetHandle(Handles, Inputs)); }
◍ 句柄复用与MACD缓存的落地写法
在 MT5 里反复调用 iMACD 这类指标函数会拖慢 EA 的 OnTick 节奏,尤其是多周期扫描时。把已创建的句柄存进数组、下次同参数直接取用,是降低开销的直接办法。 下面这段模板函数先遍历已有句柄数组,若输入参数一致就直接返回缓存的 Handle;否则扩容数组并新建一个。注意循环里用 == 比较输入结构体,前提是 SHandle 重载了相等运算符。 SHandle 自身的 GetHandle 做了懒加载:Handle 不为 INVALID_HANDLE 就复用,否则才真正去调 Inputs.GetHandle() 创建。MACD 的 GetHandle 则直接拼 iMACD 参数返回,没有内部缓存。 测试时可以用 SimpleCallString.mqh 跑字符串哈希方案,把 symbol、周期、快慢 EMA、信号周期、应用价格拼成 hash 串做键。外汇与贵金属杠杆高、滑点随机,句柄管理只解决性能,不改善信号胜率,回测请控制样本量。
class="kw">template<class="kw">typename T> class="type">int GetHandle(SHandle<T>&Handles[],const T &Inputs) { const class="type">int Size=ArraySize(Handles); for(class="type">int i=class="num">0; i<Size; i++) if(Handles[i]==Inputs) class="kw">return(Handles[i].GetHandle()); ArrayResize(Handles,Size+class="num">1); Handles[Size]=Inputs; class="kw">return(Handles[Size].GetHandle()); } class="type">int GetHandle() { class="kw">return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle())); } class="type">int GetHandle(class="type">void) const { class="kw">return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price)); } class="macro">#include <SimpleCall\SimpleCallString.mqh> class=class="str">"cmt">// 测试字符串 class="type">class="kw">string Hashes[]; class="kw">static class="type">int Handles[]; class="type">class="kw">string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ (class="type">class="kw">string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ (class="type">class="kw">string)(fast_ema_period)+ (class="type">class="kw">string)(slow_ema_period)+ (class="type">class="kw">string)(signal_period)+ (class="type">class="kw">string)(applied_price);
「用哈希串锁死指标实例参数」
在 MT5 里给 MACD 这类自定义指标做多实例管理时,最怕不同品种、周期或参数组合撞进同一个句柄。这段逻辑用一段字符串把调用上下文拼成唯一指纹,后面查 Hashes[] 就能知道该复用还是新建 Handles[]。 实际落地的写法有两种风格。一种是直接字符串相加:把 symbol(空则取 Symbol())、timeframe(当前则取 Period())、fast_ema_period、slow_ema_period、signal_period、applied_price 全部转成 string 接起来,代码短但可读性一般。 另一种走 StringConcatenate,先声明 hash="" 再塞进各字段。回测中若同时挂 EURUSD 的 M15(timeframe=15)与 XAUUSD 的 H1(timeframe=60),两串哈希前几位就不同,句柄不会误用。外汇与贵金属杠杆高,多周期并发测试先确认这类去重逻辑生效,否则可能重复开句柄拖慢终端。
class="kw">static class="type">class="kw">string Hashes[]; class="kw">static class="type">int Handles[]; class="type">class="kw">string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ (class="type">class="kw">string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ (class="type">class="kw">string)(fast_ema_period)+ (class="type">class="kw">string)(slow_ema_period)+ (class="type">class="kw">string)(signal_period)+ (class="type">class="kw">string)(applied_price); class="kw">static class="type">class="kw">string Hashes[]; class="kw">static class="type">int Handles[]; class="type">class="kw">string hash=""; StringConcatenate(hash, ((symbol==NULL) || (symbol=="") ? Symbol() : symbol), (timeframe==PERIOD_CURRENT ? Period() : timeframe), fast_ema_period, slow_ema_period,
用类把指标句柄和参数收拢起来
这段实现把 MACD 这类指标的调用从散落的函数收进 CiIndicators 类,再用内嵌的 CHandle 管理句柄数组与指标实例数组,避免每次 tick 都重复建句柄。 类里私有成员包含 m_symbol(品名)、m_period(时间帧)、m_indicator_type(来自 ENUM_INDICATOR)、m_parameters_cnt(参数数量)和 m_parameters_array[](参数数组),相当于把「调哪个品种、哪周期、什么指标、传什么参」一次性固化。 iMACD 的注释给出了缓存区编号对照:MQL4 的 0 是 MODE_MAIN、1 是 MODE_SIGNAL,对应 MQL5 的 0 是 MAIN_LINE、1 是 SIGNAL_LINE。你在 MT5 里接这套封装时,若从 MQL4 ea 迁移,注意缓冲区索引写法不变但底层句柄由 m_handle[] 托管。 当前只 include 了 SimpleCallMQL4CachingCiIndicators.mqh(带缓存的 Ci 版),另外三个无缓存 / 字符串测试头文件被注释掉。实盘接之前,建议先取消注释无缓存版跑一晚 EURUSD 的 M15,对比两种 include 下 OnCalculate 的 cpu 时间,可能缓存版会少 20%~40% 的重复调用开销。外汇与贵金属杠杆高,任何封装层变更都先在策略测试器回测验证。
signal_period,
applied_price);
class=class="str">"cmt">//class="macro">#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄
class=class="str">"cmt">//class="macro">#include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄
class=class="str">"cmt">//class="macro">#include <SimpleCall\SimpleCallString.mqh> // 用于测试字符串
class="macro">#include <SimpleCall\SimpleCallMQL4CachingCiIndicators.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类指标 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CiIndicators
{
class="kw">private:
class="type">class="kw">string m_symbol; class=class="str">"cmt">// 品名
ENUM_TIMEFRAMES m_period; class=class="str">"cmt">// 时间帧
ENUM_INDICATOR m_indicator_type; class=class="str">"cmt">// 来自枚举 ENUM_INDICATOR 的指标类型
class="type">int m_parameters_cnt; class=class="str">"cmt">// 参数数量
class="type">MqlParam m_parameters_array[]; class=class="str">"cmt">// 参数数组
class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类 CHandle |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHandle
{
class="kw">private:
class="type">int m_handle[];
CiIndicators m_indicators[];
class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 采用 MQL4 表示法的 iMACD 函数 |
class=class="str">"cmt">//| 缓存区编号如下: |
class=class="str">"cmt">//| MQL4 class="num">0 - MODE_MAIN, class="num">1 - MODE_SIGNAL |
class=class="str">"cmt">//| MQL5 class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double iMACD(
class="type">class="kw">string symbol, class=class="str">"cmt">// 品名
ENUM_TIMEFRAMES timeframe, class=class="str">"cmt">// 时间帧
class="type">int fast_ema_period, class=class="str">"cmt">// 计算快速均化的周期 ◍ 正文
<span class="keyword">int</span> slow_ema_period, <span class="comment">// 计算快速均化的周期</span> <span class="keyword">int</span> signal_period, <span class="comment">// 它们的差值均化周期</span> <span class="macro">ENUM_APPLIED_PRICE</span> applied_price, <span class="comment">// 价格类型或句柄</span> <span class="keyword">int</span> buffer, <span class="comment">// 缓存区 </span> <span class="keyword">int</span> &n
「记住这一条就够了」
实验跑下来,MQL5 直接拿指标句柄的访问方式比任何模拟 MQL4 的缓存写法都快,内核里的句柄引用计数和缓存管理才是真正拉开差距的地方。 论坛里有人拿实际 EA 抬杠,说动态输入参数的指标绕不开老式缓存,但官方态度很明确:MQL5 的设计就是为甩掉 MQL4 的数据访问包袱,硬套旧风格只会拖慢终端。 外汇和贵金属市场高杠杆、高波动,句柄用错轻则回测失真重则实盘掉帧;开 MT5 随便挂个自缓存指标跟原生 iCustom 比一次执行时间,结论你自己跑出来最踏实。