外汇EA编写教程:自置缓存的指标速度比较

概述

假设我们突然厌倦了传统的 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 参数:

MQL5 测试 1

图例 2. MQL5 test.mq5. 九指标

Cache test.mq5 EA 参数:

  • Use Timer (“0” -> off timer) — 使用计时器 (0 — 不适用)。
  • Use indicator (“false” -> 9 indicators, “true” – 1 indicator) — 受访指标的数量 (1 或 9)。

缓存测试 1

图例 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):

MQL5 对比 MQL4 9 指标

图例 4. 花费在访问九个指标上的时间

在比较结果时,请注意测试 EA 的任务相当复杂:

  • 从九个指标同时获得数据;
  • 在每次逐笔报价来临时访问指标;
  • M1 时间帧 — 生成 26 169 180 逐笔报价,以及 370 355 根柱线。

现在我们来进行测试: 仅调用一个指标 (对于两个 EA, MQL5 test.mq5Cache test.mq5, Use indicator… 参数为 “true”, 而对于 Cache test.mq5, Use Timer 为 “0”)

MQL5 对比 MQL4 1 指标

图例 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> // 用于测试字符串

确保设置计时器 (此处,计时器设置为六秒钟,我们可以访问一个指标)

缓存测试 2

图例 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 类的数组。

CiIndicatorsCHandle 类的使用代码,以如下 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 对比 MQL4 9 指标汇总图表

结论

我们进行了一些有趣的实验,这些实验与 MQL5 正确访问指标的范例相悖。 结果就是,我们更多地了解了在 MQL5 内核中处理句柄的内部机制:

  • 关于句柄计数器;
  • 关于缓存和句柄管理。

各种访问指标方法的测试结果表明,MQL5 访问方式比任何 MQL4 风格 (带有和没有句柄缓存) 都快得多。

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/4388

附加的文件 |

MQL5.zip
(12.02 KB)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投