概述
假设我们突然厌倦了传统的 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 风格 (带有和没有句柄缓存) 都快得多。