English Русский Español Deutsch 日本語 Português
自置缓存的指标速度比较

自置缓存的指标速度比较

MetaTrader 5积分 | 7 五月 2018, 07:58
5 473 0
Vladimir Karputov
Vladimir Karputov

概述

假设我们突然厌倦了传统的 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 Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/4388

附加的文件 |
MQL5.zip (12.02 KB)
深度神经网络 (第五部分)。 DNN 超参数的贝叶斯优化 深度神经网络 (第五部分)。 DNN 超参数的贝叶斯优化
本文研究利用贝叶斯优化深度神经网络 (DNN) 超参数,获取各种训练变体的可能性。 比较不同训练变体中最优超参数 DNN 的分类品质。 DNN 最优超参数的有效性的深度已在前瞻性测试中得以验证。 改善分类品质的可能方向也已确定。
如何创建任意复杂度的图形面板 如何创建任意复杂度的图形面板
本文详细介绍了如何在 CAppDialog 类的基础上创建面板,以及如何在面板上增加控件。它描述了面板的结构和框架,显示了对象的继承关系。从这篇文章中,您还可以学习到事件是怎样处理的以及它们是怎样在独立的控件之间传递的。另外还有实例演示了如何编辑面板参数,例如大小和背景颜色。
MetaTrader 5 中的多元品种余额图 MetaTrader 5 中的多元品种余额图
本文提供了一个 MQL 应用程序示例,其图形界面具有多元品种余额图,以及基于最后测试结果的资金回撤图。
在 MetaTrader 5 中交易策略优化的可视化 在 MetaTrader 5 中交易策略优化的可视化
本文采用图形界面实现 MQL 应用程序来扩展可视化的优化过程。 图形界面采用 EasyAndFast 函数库的最新版本。 许多用户可能会问为什么他们在 MQL 应用程序中需要图形界面。 本文为交易者展示了众多实用情况之一。