
自置缓存的指标速度比较
概述
假设我们突然厌倦了传统的 MQL5 指标访问方法。 我们来比较其它替代选项的访问速度。 例如,我们可以将它与那些带有和未带有缓存的 MQL4 风格的指标进行比较。 关于 MQL4 风格访问方法的想法源自文章 "交易者生存窍门: 由指标制作的快餐",并在此方式上加以改善。
分析 MQL5 指标句柄的编号
假设终端从零开始为指标句柄提供连续的编号。 为了检查这一假设,我们创建一个简单的智能交易程序 iMACD and IndicatorRelease.mq5 — 它创建若干个指标句柄,立即打印它们,并在 OnTick() 中定期访问它们:
//+------------------------------------------------------------------+ //| iMACD and IndicatorRelease.mq5 | //| 版权所有 © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "版权所有 © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.003" //--- 输入参数 input int count=6; // MACD 指标计数 int handles_array[]; // 保存 iMACD 指标句柄的数组 //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { int array_resize=ArrayResize(handles_array,count); if(array_resize==-1) { Print("ArrayResize 错误# ",GetLastError()); return(INIT_FAILED); } if(array_resize!=count) { Print("ArrayResize != \"MACD 指标计数\""); return(INIT_FAILED); } ArrayInitialize(handles_array,0); for(int i=0;i<count;i++) { handles_array[i]=CreateHandleMACD(12+i); //--- 如果句柄未能创建 if(handles_array[i]==INVALID_HANDLE) { //--- 告之失败并输出错误代码 PrintFormat("无法为品种 %s/%s 创建 iMACD 指标的句柄, 错误代码 %d", Symbol(), EnumToString(Period()), GetLastError()); //--- 指标提前停止 return(INIT_FAILED); } Print("图表标识符: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7), ", 创建句柄 iMACD (",handles_array[i],")"); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能系统逆初始化函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Comment(""); for(int i=0;i<count;i++) { Print("图表标识符: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7), ", 删除句柄 iMACD (",handles_array[i],"): ",IndicatorRelease(handles_array[i])); } } //+------------------------------------------------------------------+ //| 智能系统逐笔报价函数 | //+------------------------------------------------------------------+ void OnTick() { //--- string text=""; for(int i=0;i<count;i++) { double macd_main_1=iMACDGet(handles_array[i],MAIN_LINE,1); if(i<15) { text+="\n"+"ChartID: "+IntegerToString(ChartID())+": "+Symbol()+ ", MACD#"+IntegerToString(i)+" "+DoubleToString(macd_main_1,Digits()+1); Comment(text); } else if(i==15) { text+="\n"+"只显示前 15 个指标 ..."; Comment(text); } } } //+------------------------------------------------------------------+ //| 获取 iMACD 缓存区数值 | //| 缓存区编号如下: | //| 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACDGet(const int handle_iMACD,const int buffer,const int index) { double MACD[1]; //--- 重置错误代码 ResetLastError(); //--- 用索引为 0 的指标缓冲区中的数值填充 iMACDBuffer 数组的一部分 if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0) { //--- 如果复制失败,告之错误代码 PrintFormat("无法从 iMACD 指标复制数据,错误代码 %d",GetLastError()); //--- 以零结果退出 - 这意味着该指标被视为未计算 return(0.0); } return(MACD[0]); } //+------------------------------------------------------------------+ //| 创建 MACD 句柄 | //+------------------------------------------------------------------+ int CreateHandleMACD(const int fast_ema_period) { //--- 创建 iMACD 指标的句柄 return(iMACD(Symbol(),Period(),fast_ema_period,52,9,PRICE_CLOSE)); } //+------------------------------------------------------------------+
实验 1
源数据: 终端已打开 AUDJPY M15,USDJPY M15 和 EURUSD M15 等图表,没有加载指标和 EA。 iMACD and IndicatorRelease.mq5 中的参数 Count MACD indicators 为 6。
终端重启后立刻将 iMACD and IndicatorRelease.mq5 加载至 AUDJPY M15 (ChartID 131571247244850509) :
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (11) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (12) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (13) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (14) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (15)
我们可以看到句柄编号从 10 而非 0 开始。
实验 2
源数据: iMACD and IndicatorRelease.mq5 加载到 AUDJPY M15, Count MACD indicators 为 6。
将 iMACD and IndicatorRelease.mq5 加载至 USDJPY, M15 (ChartID 131571247244850510):
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (10) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (11) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (12) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (13) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (14) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (15)
我们可以看到在图表上句柄编号 (USDJPY M15) 也是从 10 开始, 而非 0。
结论:终端中的 指标句柄编号 (提供给用户的编号) 不是连续的,并且不以零开始。
实验 3
两个相同的图表 AUDJPY, M15 (ChartID 131571247244850509) 和 AUDJPY, M15 (ChartID 131571247244850510)。 每个都有 iMACD and IndicatorRelease.mq5 ,Count MACD indicators 等于 6。
所创建的指标句柄编号非连续,确定 MQL5 在账户内部维护它们( 计数器针对每个唯一句柄)。 为了确保这一点,我们 注释掉周期扩展:
int OnInit() { *** ArrayInitialize(handles_array,0); for(int i=0;i<count;i++) { handles_array[i]=CreateHandleMACD(12/*+i*/); //--- 如果句柄未能创建
因此,我们尝试使用完全相同的设置创建多个 MACD 指标句柄。
删除实验 1 和 2 留下的图表,并在 AUDJPY, M15 (ChartID 131571247244850509) 上启动 iMACD and IndicatorRelease.mq5:
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
正如我们所看到的,创建绝对相同指标时的响应 返回的句柄相同。
将 iMACD and IndicatorRelease.mq5 EA (也要 注释掉周期扩展) 加载到 AUDJPY, M15 (ChartID 131571247244850510):
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
再次返回相同的句柄。 第一和第二个图表上的句柄 "10" 是同一个还是两个不同的句柄? 为了验证这一点,从图表中删除 EA (如您所记得的,EA 在 OnDeinit() 中传递句柄数组,并使用 IndicatorRelease 逐个删除)。
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): true 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.116 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): true 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
如果我们参考 程序运行 的文档部分,结果 不出 所料:
EA 是在自己的线程中执行的,有多少 EA 就有多少线程执行
这意味着如果在同一个图表 (相同的品种和时间帧) 上的两个 EA 创建具有相同输入的指标,则 MQL5 在其内部账户中将它们标识为 两个不同的句柄。
有关开发 EA 中指标的一般结论
终端 (提供给用户) 的指标句柄编号不是连续的,并且不以零开始,且在其内部句柄账户中,MQL5 会考虑:
- 技术指标函数 (iMA,iAC,iMACD,iIchimoku,等等);
- 指标输入;
- 创建指标所在的品种;
- 创建指标的时间帧;
- EA 工作所在图表 ChartID。
缓存句柄是否有关键点?
初始数据 (时间帧, 品种, 测试的时段和逐笔报价生成类型) 如下:
图例 1. 设置
借助 Cache test.mq5 EA,以 MQL4 风格执行访问指标 (带有和不带有句柄缓存) 的测试,而采用 MQL5 风格访问的测试则使用 MQL5 test.mq5:
//+------------------------------------------------------------------+ //| MQL5 test.mq5 | //| 版权所有 © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "版权所有 © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.000" //--- 输入参数 input bool UseOneIndicator=false; // 使用指标: "false" -> 9 指标, "true" - 1 指标 //--- int arr_handle_iMACD[]; // 保存 iMACD 指标句柄的数值 //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { if(UseOneIndicator) ArrayResize(arr_handle_iMACD,1); else ArrayResize(arr_handle_iMACD,9); if(!CreateHandle(arr_handle_iMACD)) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能系统逆初始化函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| 智能系统逐笔报价函数 | //+------------------------------------------------------------------+ void OnTick() { //--- int arr_size=ArraySize(arr_handle_iMACD); for(int i=0;i<arr_size;i++) { double macd_main_30=iMACDGet(arr_handle_iMACD[i],MAIN_LINE,0); } } //+------------------------------------------------------------------+ //| CreateHandle | //+------------------------------------------------------------------+ bool CreateHandle(int &arr_handles[]) { int arr_size=ArraySize(arr_handles); for(int i=0;i<arr_size;i++) { int fast_ema_repiod=30+10*i; //--- 创建 iMACD 指标的句柄 arr_handles[i]=iMACD(NULL,0,fast_ema_repiod,26,9,PRICE_CLOSE); //--- 如果句柄未能创建 if(arr_handles[i]==INVALID_HANDLE) { //--- 告之失败并输出错误代码 PrintFormat("无法为品种 %s/%s 创建 iMACD 指标的句柄, 错误代码 %d", Symbol(), EnumToString(Period()), GetLastError()); //--- 指标提前停止 return(false); } } return(true); } //+------------------------------------------------------------------+ //| 获取 iMACD 缓存区数值 | //| 缓存区编号如下: | //| 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACDGet(const int handle_iMACD,const int buffer,const int index) { double MACD[1]; //--- 重置错误代码 ResetLastError(); //--- 用索引为 0 的指标缓冲区中的数值填充 iMACDBuffer 数组的一部分 if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0) { //--- 如果复制失败,告之错误代码 PrintFormat("无法从 iMACD 指标复制数据,错误代码 %d",GetLastError()); //--- 以零结果退出 - 这意味着该指标被视为未计算 return(0.0); } return(MACD[0]); } //+------------------------------------------------------------------+
MQL5 test.mq5 EA 参数:
图例 2. MQL5 test.mq5. 九指标
Cache test.mq5 EA 参数:
- Use Timer ("0" -> off timer) — 使用计时器 (0 — 不适用)。
- Use indicator ("false" -> 9 indicators, "true" - 1 indicator) — 受访指标的数量 (1 或 9)。
图例 3. Cache test.mq5. 无计时器, 九指标
IndicatorsMQL4.mq 文件则用来衡量 "MQL4 风格无句柄"。 文件使用 SimpleCallMQL4.mqh 来连接 (参阅文章 "交易者生存技巧: 用定义 (#define) 融合 ForEach")。
#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄 //#include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄 //#include <SimpleCall\SimpleCallString.mqh> // 用于测试字符串
若要衡量 "带有句柄缓存的 MQL4 风格",将来自 #113 的句柄缓存代码添加到 IndicatorsMQL4.mqh (仅用于 MACD,其它函数被删除)。 文件保存为 IndicatorsMQL4Caching.mqh — 它由 SimpleCallCaching.mqh 连接:
//#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄 #include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄 //#include <SimpleCall\SimpleCallString.mqh> // 用于测试字符串
9 个指标进行访问方式比较的结果 (设置见图例 1):
图例 4. 花费在访问九个指标上的时间
在比较结果时,请注意测试 EA 的任务相当复杂:
- 从九个指标同时获得数据;
- 在每次逐笔报价来临时访问指标;
- M1 时间帧 — 生成 26 169 180 逐笔报价,以及 370 355 根柱线。
现在我们来进行测试: 仅调用一个指标 (对于两个 EA, MQL5 test.mq5 和 Cache test.mq5, Use indicator... 参数为 "true", 而对于 Cache test.mq5, Use Timer 为 "0")
图例 5. 用于访问一个指标的时间
结论
与无句柄缓存的 MQL4风格相比,使用句柄缓存的 MQL4 风格拥有较大优势。 然而,MQL4 风格彻底败给了 MQL5 风格。
无句柄有效性控制
现在我们应该提及带有句柄缓存的巨大缺点: 它不检查用户缓存中句柄是否存在。 换言之,删除指标句柄的情况不会以任何方式进行处理。
我们来考虑以下情况: 我们使用 MQL4 风格的指标缓存句柄。 在 EA 第一次访问之后:
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
该句柄存储在用户缓存中 (可以是结构数组或字符串数组)。 之后,EA 的所有后续访问
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
没有传递给 MQL5 核心。 而是返回从缓存中取出的句柄所指向的指标值。 现在, 在 OnTimer() 中删除句柄— 假设我们知道它等于 "10"。 作为测试,我们使用 Cache test.mq5 文件,其中应包含 SimpleCallMQL4Caching.mqh 文件:
//#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄 #include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄 //#include <SimpleCall\SimpleCallString.mqh> // 用于测试字符串
确保设置计时器 (此处,计时器设置为六秒钟,我们可以访问一个指标)
图例 6. 测试设置与删除句柄
在最初的 OnTimer() 进入之后
OnTimer, IndicatorRelease(10)=true iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807
我们得到错误 4807:
ERR_INDICATOR_WRONG_HANDLE | 4807 | 无效指标句柄 |
这意味着不存在指标句柄的有效性控制。
缓存指标句柄。 它是如何运行的
缓存指标句柄的一般原则如下:
- 创建一个自定义句柄缓存;
- 当从指标请求数据时,检查是否已按要求的设置创建了句柄 (品种,时间帧,平均周期等):
- 如果它已经存在于自定义缓存中,则从指标返回数据;
- 如果尚未存在此类句柄,则创建它,并将其保存在缓存中,并从指标返回其数据。
选项 1: 结构数组
执行在 IndicatorsMQL4Caching.mqh 中实现 (使用 SimpleCallMQL4Caching.mqh 连接到 Cache test.mq5)。
在 Cache test.mq5, 包含 SimpleCallMQL4Caching.mqh:
//#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄 #include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄 //#include <SimpleCall\SimpleCallString.mqh> // 用于测试字符串
首先,我们来看看插入到文件和 iMACD 函数中的大代码块:
... //+------------------------------------------------------------------+ //| 结构 CHandle | //+------------------------------------------------------------------+ template<typename T> struct SHandle { private: int Handle; T Inputs; public: //+------------------------------------------------------------------+ //| 含有初始化列表的构造函数 | //+------------------------------------------------------------------+ SHandle() : Handle(INVALID_HANDLE) { } //+------------------------------------------------------------------+ //| 操作符 "==" 重载 | //+------------------------------------------------------------------+ bool operator==(const T &Inputs2) const { return(this.Inputs == Inputs2); } //+------------------------------------------------------------------+ //| 操作符 "=" 重载 | //+------------------------------------------------------------------+ void operator=(const T &Inputs2) { this.Inputs=Inputs2; } //+------------------------------------------------------------------+ //| SHandle::GetHandle | //+------------------------------------------------------------------+ int GetHandle() { return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle())); } }; //+------------------------------------------------------------------+ //| 获取句柄 | //+------------------------------------------------------------------+ template<typename T> int GetHandle(SHandle<T>&Handles[],const T &Inputs) { const int Size=ArraySize(Handles); for(int i=0; i<Size; i++) if(Handles[i]==Inputs) return(Handles[i].GetHandle()); ArrayResize(Handles,Size+1); Handles[Size]=Inputs; return(Handles[Size].GetHandle()); } //+------------------------------------------------------------------+ //| 结构 Macd | //+------------------------------------------------------------------+ struct SMacd { string symbol; ENUM_TIMEFRAMES period; int fast_ema_period; int slow_ema_period; int signal_period; ENUM_APPLIED_PRICE applied_price; //+------------------------------------------------------------------+ //| 一个空的默认构造函数 | //+------------------------------------------------------------------+ SMacd(void) { } //+------------------------------------------------------------------+ //| 含有初始化列表的构造函数 | //+------------------------------------------------------------------+ SMacd(const string &isymbol, const ENUM_TIMEFRAMES &iperiod, 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) { } //+------------------------------------------------------------------+ //| SMacd::GetHandle | //+------------------------------------------------------------------+ 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) && (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)); } }; //+------------------------------------------------------------------+ //| 采用 MQL4 表示法的 iMACD2 函数 | //| 缓存区编号如下: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ int iMACD2(const string symbol, const ENUM_TIMEFRAMES period, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price) { static SHandle<SMacd>Handles[]; const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price); return(GetHandle(Handles, Inputs)); } //+------------------------------------------------------------------+ //| 采用 MQL4 表示法的 iAC 函数 | ... //+------------------------------------------------------------------+ //| 采用 MQL4 表示法的 iMACD 函数 | //| 缓存区编号如下: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACD( string symbol, // 品名 ENUM_TIMEFRAMES timeframe, // 时间帧 int fast_ema_period, // 计算快速均化的周期 int slow_ema_period, // 计算快速均化的周期 int signal_period, // 它们的差值均化周期 ENUM_APPLIED_PRICE applied_price, // 价格类型或句柄 int buffer, // 缓存区 int shift // 偏移 ) { double result=NaN; //--- int handle=iMACD2(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price); if(handle==INVALID_HANDLE) ...
我们来描述它的工作。 首先, 有一个来自 MACD 的数据请求:
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
然后我们进入 iMACD 函数并转到 iMACD2:
//+------------------------------------------------------------------+ //| 采用 MQL4 表示法的 iMACD2 函数 | //| 缓存区编号如下: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ int iMACD2(const string symbol, const ENUM_TIMEFRAMES period, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price) { static SHandle<SMacd>Handles[]; const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price); return(GetHandle(Handles, Inputs)); }
在此声明含有 SMacd 类型的 Handles[] 静态数组 (它在首次进入时创建,不会在后续进入时重新创建)。 另外, 含有 SMacd 类型的 Inputs 对象 也会一次性创建并依照参数初始化。
之后,使用链接来传递 Handles[] 数组和 Inputs 对象至 GetHandle 函数 (不是 SHandle::GetHandle 和 SMacd::GetHandle):
//+------------------------------------------------------------------+ //| 获取句柄 | //+------------------------------------------------------------------+ template<typename T> int GetHandle(SHandle<T>&Handles[],const T &Inputs) { const int Size=ArraySize(Handles); for(int i=0; i<Size; i++) if(Handles[i]==Inputs) return(Handles[i].GetHandle()); ArrayResize(Handles,Size+1); Handles[Size]=Inputs; return(Handles[Size].GetHandle()); }
在这个函数中,返回数组中发现的指标句柄,或是 如果未找到句柄,则在 SHandle::GetHandle 中接收它。
但由于这是首次访问,且 尚无这样的句柄,
//+------------------------------------------------------------------+ //| SHandle::GetHandle | //+------------------------------------------------------------------+ int GetHandle() { return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle())); }
在 SMacd::GetHandle 中创建它:
//+------------------------------------------------------------------+ //| SMacd::GetHandle | //+------------------------------------------------------------------+ int GetHandle(void) const { return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price)); }
选项 2: 字符串数组
执行在 IndicatorsMQL4String.mqh 文件中实现 (使用 SimpleCallString.mqh 连接至 Cache test.mq5 )。
在 Cache test.mq5 EA 中, 包含 SimpleCallString.mqh:
//#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄 //#include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄 #include <SimpleCall\SimpleCallString.mqh> // 测试字符串
在速度方面,使用字符串非常昂贵。 稍后我们会看到。 所以, 将参数保存为字符串的想法如下所示:
string Hashes[]; static int Handles[]; string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ (string)(fast_ema_period)+ (string)(slow_ema_period)+ (string)(signal_period)+ (string)(applied_price);
我们将使用上面提供的参数从 EA 访问 iMACD,如图例 1 所示。
NN | 代码 | 时间 |
---|---|---|
1 | //--- NN2 //static string Hashes[]; //static int Handles[]; //string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ // (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ // (string)(fast_ema_period)+ // (string)(slow_ema_period)+ // (string)(signal_period)+ // (string)(applied_price); //--- NN3 //static string Hashes[]; //static int Handles[]; //string hash=""; //StringConcatenate(hash, // ((symbol==NULL) || (symbol=="") ? Symbol() : symbol), // (timeframe==PERIOD_CURRENT ? Period() : timeframe), // fast_ema_period, // slow_ema_period, // signal_period, // applied_price); |
0:01:40.953 |
2 | //--- NN2 static string Hashes[]; static int Handles[]; string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ (string)(fast_ema_period)+ (string)(slow_ema_period)+ (string)(signal_period)+ (string)(applied_price); //--- NN3 //static string Hashes[]; //static int Handles[]; //string hash=""; //StringConcatenate(hash, // ((symbol==NULL) || (symbol=="") ? Symbol() : symbol), // (timeframe==PERIOD_CURRENT ? Period() : timeframe), // fast_ema_period, // slow_ema_period, // signal_period, // applied_price); |
0:05:20.953 |
3 | //--- NN2 //static string Hashes[]; //static int Handles[]; //string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ // (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ // (string)(fast_ema_period)+ // (string)(slow_ema_period)+ // (string)(signal_period)+ // (string)(applied_price); //--- NN3 static string Hashes[]; static int Handles[]; string hash=""; StringConcatenate(hash, ((symbol==NULL) || (symbol=="") ? Symbol() : symbol), (timeframe==PERIOD_CURRENT ? Period() : timeframe), fast_ema_period, slow_ema_period, signal_period, applied_price); |
0:04:12.672 |
测试 1 是使用 MQL4 风格访问指标且不使用字符串的基准测试。 在测试 2 中,我们使用了字符串,字符串是用 "+" 形成的。 在测试 3 中,字符串使用 StringConcatenate 形成。
根据时间测量结果,很明显,虽然 StringConcatenate 与测试 2 相比时间增加了 21%,但总体性能仍然比测试 1 低 2.5 倍。
因此,可以放弃将指标句柄保存为字符串的想法。
选项 3 — 类缓存句柄 (iIndicators.mqh 类通过 SimpleCallMQL4CachingCiIndicators.mqh 连接至 Cache test.mq5 EA)。
在 Cache test.mq5 EA 中, 我们包含 SimpleCallMQL4CachingCiIndicators.mqh:
//#include <SimpleCall\SimpleCallMQL4.mqh> // 用于测试无缓存句柄 //#include <SimpleCall\SimpleCallMQL4Caching.mqh> // 用于测试缓存句柄 //#include <SimpleCall\SimpleCallString.mqh> // 用于测试字符串 #include <SimpleCall\SimpleCallMQL4CachingCiIndicators.mqh>
为每个指标创建 CHandle 类的静态对象 (在相应的 MQL4 风格函数内)。 它用作 CiIndicators 类对象存储 — 包含指标参数和设置的类。
图例 7. 结构
CiIndicators 类基于五个 "私有" 变量:
//+------------------------------------------------------------------+ //| 类指标 | //+------------------------------------------------------------------+ class CiIndicators { private: string m_symbol; // 品名 ENUM_TIMEFRAMES m_period; // 时间帧 ENUM_INDICATOR m_indicator_type; // 来自枚举 ENUM_INDICATOR 的指标类型 int m_parameters_cnt; // 参数数量 MqlParam m_parameters_array[]; // 参数数组 public:
它完全对应于 IndicatorCreate 函数变量。 这没啥要做的,因为我们通过 IndicatorCreate 接收指标句柄。
CHandle 类由两个数组构建:
//+------------------------------------------------------------------+ //| 类 CHandle | //+------------------------------------------------------------------+ class CHandle { private: int m_handle[]; CiIndicators m_indicators[]; public:
m_handle 数组包含所创建的指标句柄, 而 m_indicators 数组是 CiIndicators 类的数组。
CiIndicators 和 CHandle 类的使用代码,以如下 MACD 作为示例:
//+------------------------------------------------------------------+ //| 采用 MQL4 表示法的 iMACD 函数 | //| 缓存区编号如下: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACD( string symbol, // 品名 ENUM_TIMEFRAMES timeframe, // 时间帧 int fast_ema_period, // 计算快速均化的周期 int slow_ema_period, // 计算快速均化的周期 int signal_period, // 它们的差值均化周期 ENUM_APPLIED_PRICE applied_price, // 价格类型或句柄 int buffer, // 缓存区 int shift // 偏移 ) { //--- static CHandle Handles_MACD; //--- 用指标的参数填充结构 MqlParam pars[4]; //--- 快速均线周期 pars[0].type=TYPE_INT; pars[0].integer_value=fast_ema_period; //--- 慢速均线周期 pars[1].type=TYPE_INT; pars[1].integer_value=slow_ema_period; //--- 快速和慢速均线差值的均化周期 pars[2].type=TYPE_INT; pars[2].integer_value=signal_period; //--- 价格类型 pars[3].type=TYPE_INT; pars[3].integer_value=applied_price; CiIndicators MACD_Indicator; MACD_Indicator.Init(Symbol(),Period(),IND_MACD,4); int handle=Handles_MACD.GetHandle(MACD_Indicator,Symbol(),Period(),IND_MACD,4,pars); //--- double result=NaN; //--- if(handle==INVALID_HANDLE) { Print(__FUNCTION__,": INVALID_HANDLE 错误=",GetLastError()); return(result); } double val[1]; int copied=CopyBuffer(handle,buffer,shift,1,val); if(copied>0) result=val[0]; else Print(__FUNCTION__,": CopyBuffer 错误=",GetLastError()); return(result); }
- 声明 CHandle 类的 Handles_MACD 静态数组 — 它保存所生成的 MACD 句柄和参数。
- 创建并初始化 CiIndicators 类的 MACD_Indicator 对象。
- 指针句柄在 Handles_MACD::GetHandle 函数中创建 (或者如果它已经依据这些参数创建的话,则传递它)。
使用 MQL4 风格的CiIndicators.mqh 类在访问和处理缓存操作时耗时 2 分 30 秒。
最终访问九个指标的速度图表
带有和没有缓存的 MQL4 风格通过 Cache test.mq5 进行检查, 而标准 MQL5 风格得测试则通过 MQL5 test.mq5 进行。
结论
我们进行了一些有趣的实验,这些实验与 MQL5 正确访问指标的范例相悖。 结果就是,我们更多地了解了在 MQL5 内核中处理句柄的内部机制:
- 关于句柄计数器;
- 关于缓存和句柄管理。
各种访问指标方法的测试结果表明,MQL5 访问方式比任何 MQL4 风格 (带有和没有句柄缓存) 都快得多。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/4388
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



这就是我所说的解释
"内部会计 "解释为计数器。坦率地说,我不明白为什么文章的第一部分是关于手柄的?似乎一切都已经被咀嚼过不止一次,并以更方便的方式呈现出来。在没有开放图表的终端中执行指标的原因。
这是一个完全错误的结论。手柄是一样的,而且 ID 也是一致的。
第一个真实结果只表明句柄引用数减少了。
是的,这是文章中的一个错误。
总的来说,你应该放弃 "MQL4 风格 " 的发明和写作。 MQL5 更快、更正确。正是因为了解到 MQL4 的拐杖和局限性,我们才创建了一种新语言,并拒绝兼容,以免出现糟糕的数据访问方案。
一般来说,有必要发明和编写 "MQL4 风格"。MQL5 更快、更正确。正是因为了解到 MQL4 的弊端和局限性,我们才创建了一种新语言,并拒绝兼容,以免出现糟糕的数据访问方案。
转到
关于交易、自动交易系统和测试交易策略的论坛
讨论文章 "比较自缓存指标的速度"
fxsaber, 2018.03.07 08:17 pm.
一般来说,TS 调用的是经过计算的(而不是硬编码的)输入参数指标。而这里离不开 MQL4 风格+缓存。
我认为在 KB 中找到这种级别的 MT4-advisor 并不难。将其转换为文章中所谓的 MQL5 风格是不可能的。
我们走吧
太夸张了
不争论太夸张了。
没有争论。继续倡导原始 TC。