DoEasy 函数库中的时间序列(第四十部分):基于函数库的指标 - 实时刷新数据

10 八月 2020, 09:42
Artyom Trishkin
0
852

内容


概念

上一篇文章中,我们着眼于配合指标的 DoEasy 库。 出于指标里资源节省计算的某些特性,以及在同一图表上启动的指标中在获取当前品种和图表周期数据时的限制,这类程序需要稍微不同的方法来构建和更新时间序列。

我们设法达成正确的请求,并加载历史数据。 现在,我们要开发实时刷新指标所需的所有时间序列中的所有数据的功能(我们假设指标是多周期的,并从其启动的指定图表时间帧接收操作数据)。

在构造指标缓冲区数据时,我们在循环中自指定历史数据深度的柱线移动到当前(零)柱线。 于此,最简单的解决方案是按循环索引获取数据 — 数据已在函数库时间序列对象中创建,因此没有什么可以阻止我们按索引接收数据。 然而,这仅在构造静态数据时有效。 实时刷新时间序列列表数据的过程中,我们面临按柱线索引的问题。 将新柱线添加到时间序列列表时,新柱线的索引为 0,因为新柱线将变为零(当前)。 时间序列中所有先前柱线的索引将加 1。 因此,每次在图表上新开一根新柱线时,我们都需要将此根新出现的柱线添加到时间序列列表当中,并把更新后的时间序列列表中所有其他柱线的编号加一。

这是极端不切实际的。 代之,我们应在时间序列列表中取用柱线时间 — 时间序列列表中每根柱线的开立时间始终保持不变。 若想引用函数库时间序列集合中的任何柱线,柱线时间即为起点。 我打算还是保留柱线索引。 但柱线编号并不是从柱线属性里提取的。 代之,当按时间序列索引请求一根柱线时,将按请求的索引来计算柱线时间,并按计算出的柱线时间从时间序列列表中提取所需的柱线,以供进一步使用。

改善时间序列类

在 \MQL5\Include\DoEasy\Defines.mqh 里,从枚举柱线对象的整数型属性之中删除柱线索引属性:

//+------------------------------------------------------------------+
//| Bar integer properties                                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_PROP_INTEGER
  {
   BAR_PROP_INDEX = 0,                                      // Bar index in timeseries
   BAR_PROP_TYPE,                                           // Bar type (from the ENUM_BAR_BODY_TYPE enumeration)

将其替换为“柱线时间”属性,并将柱线对象整数型属性的数量减 1(从 14 减至 13):

//+------------------------------------------------------------------+
//| Bar integer properties                                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_PROP_INTEGER
  {
   BAR_PROP_TIME = 0,                                       // Bar period start time
   BAR_PROP_TYPE,                                           // Bar type (from the ENUM_BAR_BODY_TYPE enumeration)
   BAR_PROP_PERIOD,                                         // Bar period (timeframe)
   BAR_PROP_SPREAD,                                         // Bar spread
   BAR_PROP_VOLUME_TICK,                                    // Bar tick volume
   BAR_PROP_VOLUME_REAL,                                    // Bar exchange volume

   BAR_PROP_TIME_DAY_OF_YEAR,                               // Bar day serial number in a year
   BAR_PROP_TIME_YEAR,                                      // A year the bar belongs to
   BAR_PROP_TIME_MONTH,                                     // A month the bar belongs to
   BAR_PROP_TIME_DAY_OF_WEEK,                               // Bar week day
   BAR_PROP_TIME_DAY,                                       // Bar day of month (number)
   BAR_PROP_TIME_HOUR,                                      // Bar hour
   BAR_PROP_TIME_MINUTE,                                    // Bar minute
  }; 
#define BAR_PROP_INTEGER_TOTAL (13)                         // Total number of integer bar properties
#define BAR_PROP_INTEGER_SKIP  (0)                          // Number of bar properties not used in sorting
//+------------------------------------------------------------------+

相应地,在可能的柱线排序标准枚举里,我们需要删除按索引排序,并替换为按柱线时间排序

//+------------------------------------------------------------------+
//| Possible bar sorting criteria                                    |
//+------------------------------------------------------------------+
#define FIRST_BAR_DBL_PROP          (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP)
#define FIRST_BAR_STR_PROP          (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP+BAR_PROP_DOUBLE_TOTAL-BAR_PROP_DOUBLE_SKIP)
enum ENUM_SORT_BAR_MODE
  {
//--- Sort by integer properties
   SORT_BY_BAR_TIME = 0,                                    // Sort by bar period start time
   SORT_BY_BAR_TYPE,                                        // Sort by bar type (from the ENUM_BAR_BODY_TYPE enumeration)
   SORT_BY_BAR_PERIOD,                                      // Sort by bar period (timeframe)
   SORT_BY_BAR_SPREAD,                                      // Sort by bar spread
   SORT_BY_BAR_VOLUME_TICK,                                 // Sort by bar tick volume
   SORT_BY_BAR_VOLUME_REAL,                                 // Sort by bar exchange volume
   SORT_BY_BAR_TIME_DAY_OF_YEAR,                            // Sort by bar day number in a year
   SORT_BY_BAR_TIME_YEAR,                                   // Sort by a year the bar belongs to
   SORT_BY_BAR_TIME_MONTH,                                  // Sort by a month the bar belongs to
   SORT_BY_BAR_TIME_DAY_OF_WEEK,                            // Sort by a bar week day
   SORT_BY_BAR_TIME_DAY,                                    // Sort by a bar day
   SORT_BY_BAR_TIME_HOUR,                                   // Sort by a bar hour
   SORT_BY_BAR_TIME_MINUTE,                                 // Sort by a bar minute
//--- Sort by real properties
   SORT_BY_BAR_OPEN = FIRST_BAR_DBL_PROP,                   // Sort by bar open price
   SORT_BY_BAR_HIGH,                                        // Sort by the highest price for the bar period
   SORT_BY_BAR_LOW,                                         // Sort by the lowest price for the bar period
   SORT_BY_BAR_CLOSE,                                       // Sort by a bar close price
   SORT_BY_BAR_CANDLE_SIZE,                                 // Sort by a candle price
   SORT_BY_BAR_CANDLE_SIZE_BODY,                            // Sort by a candle body size
   SORT_BY_BAR_CANDLE_BODY_TOP,                             // Sort by a candle body top
   SORT_BY_BAR_CANDLE_BODY_BOTTOM,                          // Sort by a candle body bottom
   SORT_BY_BAR_CANDLE_SIZE_SHADOW_UP,                       // Sort by candle upper wick size
   SORT_BY_BAR_CANDLE_SIZE_SHADOW_DOWN,                     // Sort by candle lower wick size
//--- Sort by string properties
   SORT_BY_BAR_SYMBOL = FIRST_BAR_STR_PROP,                 // Sort by a bar symbol
  };
//+------------------------------------------------------------------+

在 \MQL5\Include\DoEasy\Objects\Series\Bar.mqh 中重新构建 CBar 类,以便操控柱线时间。

以前,SetSymbolPeriod() 方法为柱线对象设置指定的品种、图表周期和柱线索引。 现在,索引则已由柱线时间所替代:

//--- Set (1) bar symbol, timeframe and time, (2) bar object parameters
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetProperties(const MqlRates &rates);

我们再来修复方法实现:

//+------------------------------------------------------------------+
//| Set bar symbol, timeframe and index                              |
//+------------------------------------------------------------------+
void CBar::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   this.SetProperty(BAR_PROP_TIME,time);
   this.SetProperty(BAR_PROP_SYMBOL,symbol);
   this.SetProperty(BAR_PROP_PERIOD,timeframe);
   this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   this.m_period_description=TimeframeDescription(timeframe);
  }
//+------------------------------------------------------------------+

为了可获取更多数据,现在第一个参数型类构造函数接收来自 CBar 类构造函数的柱线时间,代替了柱线索引。 添加变量,该变量用来传递在创建柱线对象时调用的类方法描述:

//--- Constructors
                     CBar(){;}
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source);
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates);

我们还要修复构造函数的实现 — 采用柱线时间代替索引,然后添加变量,该变量保存类构造函数调用的获取历史数据方法的错误文本

//+------------------------------------------------------------------+
//| Constructor 1                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source)
  {
   this.m_type=COLLECTION_SERIES_ID;
   MqlRates rates_array[1];
   this.SetSymbolPeriod(symbol,timeframe,time);
   ::ResetLastError();
//--- If failed to get the requested data by time and write bar data to the MqlRates array,
//--- display an error message, create and fill the structure with zeros, and write it to the rates_array array
   if(::CopyRates(symbol,timeframe,time,1,rates_array)<1)
     {
      int err_code=::GetLastError();
      ::Print
        (
         DFUN,"(1)-> ",source,symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ",
         CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",
         CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ",
         CMessage::Retcode(err_code)
        );
      //--- Set the requested bar time to the structure with zero fields
      MqlRates err={0};
      err.time=time;
      rates_array[0]=err;
     }
   ::ResetLastError();
//--- If failed to set time to the time structure, display the error message
   if(!::TimeToStruct(rates_array[0].time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print
        (
         DFUN,"(1) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ",
         CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ",
         CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ",
         CMessage::Retcode(err_code)
        );
     }
//--- Set the bar properties
   this.SetProperties(rates_array[0]);
  }
//+------------------------------------------------------------------+

在获取历史数据时,将 source 变量值添加到显示的错误消息中,从而可以找到尝试创建新柱线对象的类及其方法。

现在,第二个参数型构造函数也采用柱线时间代替了索引:

//+------------------------------------------------------------------+
//| Constructor 2                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates)
  {
   this.m_type=COLLECTION_SERIES_ID;
   this.SetSymbolPeriod(symbol,timeframe,rates.time);
   ::ResetLastError();
//--- If failed to set time to the time structure, display the error message,
//--- create and fill the structure with zeros, set the bar properties from this structure and exit
   if(!::TimeToStruct(rates.time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print
        (
         DFUN,"(2) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(rates.time),": ",
         CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ",
         CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ",
         CMessage::Retcode(err_code)
        );
      //--- Set the requested bar time to the structure with zero fields
      MqlRates err={0};
      err.time=rates.time;
      this.SetProperties(err);
      return;
     }
//--- Set the bar properties
   this.SetProperties(rates);
  }
//+------------------------------------------------------------------+

在类的公开部分里,简化访问柱线对象属性的方法模块之中,将 Period() 方法重命名为 Timeframe(),并删除 Index() 方法,返回已删除的柱线属性:

//+------------------------------------------------------------------+
//| Methods of simplified access to bar object properties            |
//+------------------------------------------------------------------+
//--- Return the (1) type, (2) period, (3) spread, (4) tick, (5) exchange volume,
//--- (6) bar period start time, (7) year, (8) month the bar belongs to
//--- (9) week number since the year start, (10) week number since the month start
//--- (11) day, (12) hour, (13) minute
   ENUM_BAR_BODY_TYPE TypeBody(void)                                    const { return (ENUM_BAR_BODY_TYPE)this.GetProperty(BAR_PROP_TYPE);  }
   ENUM_TIMEFRAMES   Timeframe(void)                                    const { return (ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD);   }
   int               Spread(void)                                       const { return (int)this.GetProperty(BAR_PROP_SPREAD);               }
   long              VolumeTick(void)                                   const { return this.GetProperty(BAR_PROP_VOLUME_TICK);               }
   long              VolumeReal(void)                                   const { return this.GetProperty(BAR_PROP_VOLUME_REAL);               }
   datetime          Time(void)                                         const { return (datetime)this.GetProperty(BAR_PROP_TIME);            }
   long              Year(void)                                         const { return this.GetProperty(BAR_PROP_TIME_YEAR);                 }
   long              Month(void)                                        const { return this.GetProperty(BAR_PROP_TIME_MONTH);                }
   long              DayOfWeek(void)                                    const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_WEEK);          }
   long              DayOfYear(void)                                    const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_YEAR);          }
   long              Day(void)                                          const { return this.GetProperty(BAR_PROP_TIME_DAY);                  }
   long              Hour(void)                                         const { return this.GetProperty(BAR_PROP_TIME_HOUR);                 }
   long              Minute(void)                                       const { return this.GetProperty(BAR_PROP_TIME_MINUTE);               }
   long              Index(void)                                        const { return this.GetProperty(BAR_PROP_INDEX);                     }

现在, Index() 方法不再返回不存在的柱线对象属性,而是替换为按柱线时间返回计算值:

//--- Return bar symbol
   string            Symbol(void)                                       const { return this.GetProperty(BAR_PROP_SYMBOL);                    }
//--- Return bar index on the specified timeframe the bar time falls into
   int               Index(const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT)  const
                       { return ::iBarShift(this.Symbol(),(timeframe>PERIOD_CURRENT ? timeframe : this.Timeframe()),this.Time());            }  
//+------------------------------------------------------------------+

该方法在输入当中指定时间帧,并返回由 iBarShift() 函数计算出的当前时间序列的柱线索引。

在返回柱线对象简称的方法里,我们现在调用新讲述的方法,其默认值为 PERIOD_CURRENT ,该索引将返回柱线对象所属的时间序列:

//+------------------------------------------------------------------+
//| Return the bar object short name                                 |
//+------------------------------------------------------------------+
string CBar::Header(void)
  {
   return
     (
      CMessage::Text(MSG_LIB_TEXT_BAR)+" \""+this.GetProperty(BAR_PROP_SYMBOL)+"\" "+
      TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD))+"["+(string)this.Index()+"]"
     );
  }
//+------------------------------------------------------------------+

从返回柱线对象整数型属性方法中删除返回柱线索引的模块

//+------------------------------------------------------------------+
//| Return the description of the bar integer property               |
//+------------------------------------------------------------------+
string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property)
  {
   return
     (
      property==BAR_PROP_INDEX               ?  CMessage::Text(MSG_LIB_TEXT_BAR_INDEX)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_TYPE                ?  CMessage::Text(MSG_ORD_TYPE)+

代之,设置一个代码模块,返回柱线时间(完整的方法清单):

//+------------------------------------------------------------------+
//| Return the description of the bar integer property               |
//+------------------------------------------------------------------+
string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property)
  {
   return
     (
      property==BAR_PROP_TIME                ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==BAR_PROP_TYPE                ?  CMessage::Text(MSG_ORD_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.BodyTypeDescription()
         )  :
      property==BAR_PROP_PERIOD              ?  CMessage::Text(MSG_LIB_TEXT_BAR_PERIOD)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.m_period_description
         )  :
      property==BAR_PROP_SPREAD              ?  CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_VOLUME_TICK         ?  CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_TICK)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_VOLUME_REAL         ?  CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_REAL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_TIME_YEAR           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_YEAR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.Year()
         )  :
      property==BAR_PROP_TIME_MONTH          ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MONTH)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+MonthDescription((int)this.Month())
         )  :
      property==BAR_PROP_TIME_DAY_OF_YEAR    ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.DayOfYear(),3,'0')
         )  :
      property==BAR_PROP_TIME_DAY_OF_WEEK    ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+DayOfWeekDescription((ENUM_DAY_OF_WEEK)this.DayOfWeek())
         )  :
      property==BAR_PROP_TIME_DAY            ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Day(),2,'0')
         )  :
      property==BAR_PROP_TIME_HOUR           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_HOUR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Hour(),2,'0')
         )  :
      property==BAR_PROP_TIME_MINUTE         ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MINUTE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Minute(),2,'0')
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

柱线对象类的修改既已完成。

如果我们仔细查看标准库类的清单,我们将在其中看到两个文件 MQL5\Include\Indicators\Series.mqhTimeSeries.mqh
该函数库还有同名的类文件,而这显然是错误的。 我们为两个类重命名 — 在它们的名称里加入 DE (DoEasy),以及文件名中。 另外,在访问这些文件和类的代码里修改名称。 这些修改会影响三个文件:Series.mqh(现在已重命名为 SeriesDE.mqh 和 CSeriesDE 类),TimeSeries.mqh(现在已重命名为 TimeSeriesDE.mqh 和 CTimeSeriesDE 类),和 TimeSeriesCollection.mqh(都应用了这两个重命名的类)。 我们按顺序研究所有这些文件及其类。

现在将 Series.mqh 文件另存为 \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh,且类名也要相应修改

//+------------------------------------------------------------------+
//| Timeseries class                                                 |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:

相应地,返回类对象的方法现在具有新类的类型

public:
//--- Return (1) oneself and (2) the timeseries list
   CSeriesDE        *GetObject(void)                                    { return &this;         }

如同 GetBarBySeriesIndex 时间序列中那样,按索引返回柱线对象的公开方法已重命名为 GetBar()我们再添加一个同类型的方法以便按时间序列中的开立时间返回柱线对象

//--- Return the bar object by (1) a real index in the list, (2) an index as in the timeseries, (3) time and (4) the real list size
   CBar             *GetBarByListIndex(const uint index);
   CBar             *GetBar(const uint index);
   CBar             *GetBar(const datetime time);
   int               DataTotal(void)                                       const { return this.m_list_series.Total();               }

由此,我们现在有两个重载方法,可以按时间和按索引返回柱线对象。

实现按开立时间返回柱线对象的方法:

//+------------------------------------------------------------------+
//| Return the bar object by time in the timeseries                  |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const datetime time)
  {
   CBar *obj=new CBar(this.m_symbol,this.m_timeframe,time,DFUN_ERR_LINE); 
   if(obj==NULL)
      return NULL;
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   int index=this.m_list_series.Search(obj);
   delete obj;
   CBar *bar=this.m_list_series.At(index);
   return bar;
  }
//+------------------------------------------------------------------+

方法接收时间,该时间用于查找并返回对应的柱线对象。
为当前时间序列创建一个临时的柱线r对象,其时间属性等于传递给方法的数值。
设置按时间针对柱线对象列表进行排序的标志
,并在列表中搜索柱线对象,其时间属性等于传递给方法的数值
搜索结果为列表中的柱线索引,如果不成功,则返回 -1。
删除临时柱线对象,并按得到的索引从列表中获取必要的柱线。 如果索引小于零,则 CArrayObj 类的 At() 方法返回 NULL
如果按时间查找得到对象,则返回一个柱线对象,或者从方法中返回 NULL

实现按索引返回柱线对象的方法:

//+------------------------------------------------------------------+
//| Return the bar object by index as in the timeseries              |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const uint index)
  {
   datetime time=::iTime(this.m_symbol,this.m_timeframe,index);
   if(time==0)
      return NULL;
   return this.GetBar(time);
  }
//+------------------------------------------------------------------+

该方法接收必要的柱线索引
利用 iTime() 函数按索引获取柱线时间
,并返回上述 GetBar() 方法操作的结果,它按获取的时间返回柱线对象。

在类的公开部分当中,与按索引返回主要柱线属性的方法一起,声明按柱线时间返回相同属性的方法:

//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index
   double            Open(const uint index,const bool from_series=true);
   double            High(const uint index,const bool from_series=true);
   double            Low(const uint index,const bool from_series=true);
   double            Close(const uint index,const bool from_series=true);
   datetime          Time(const uint index,const bool from_series=true);
   long              TickVolume(const uint index,const bool from_series=true);
   long              RealVolume(const uint index,const bool from_series=true);
   int               Spread(const uint index,const bool from_series=true);

//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index
   double            Open(const datetime time);
   double            High(const datetime time);
   double            Low(const datetime time);
   double            Close(const datetime time);
   datetime          Time(const datetime time);
   long              TickVolume(const datetime time);
   long              RealVolume(const datetime time);
   int               Spread(const datetime time);

所声明方法的实现将在以后研究。

在类的同一公开部分里,声明该方法允许将指定的时间序列对象数据写至传递给该方法的数组当中

//--- (1) Create and (2) update the timeseries list
   int               Create(const uint required=0);
   void              Refresh(SDataCalculate &data_calculate);
//--- Copy the specified double property of the timeseries to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool              CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE);

//--- Create and send the "New bar" event to the control program chart
   void              SendEvent(void);


假设我们需要一次性将时间序列数据写入指标缓冲区。 柱线对象能够包含许多不同的属性 — 整数型和实数型。 可以利用此方法将任何柱线对象的实数型属性写入数组。 将所有数据写入数组的方式与写入时间序列数组的方式相同 — 将存储在列表末尾的时间序列对象中的当前柱线数据写入接收一方数组的零号索引,即,反向写入。

我们看一下它的实现:

//+------------------------------------------------------------------+
//| Copy the specified double property of the timeseries to the array|
//+------------------------------------------------------------------+
bool CSeriesDE::CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE)
  {
//--- Get the number of bars in the timeseries list
   int total=this.m_list_series.Total();
   if(total==0)
      return false;
//--- If a dynamic array is passed to the method and its size is not equal to that of the timeseries list,
//--- set the new size of the passed array equal to that of the timeseries list
   if(::ArrayIsDynamic(array) && ::ArraySize(array)!=total)
      if(::ArrayResize(array,total,this.m_required)==WRONG_VALUE)
         return false;
//--- In the loop from the very last timeseries list element (from the current bar)
   int n=0;
   for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--)
     {
      //--- get the next bar object by the loop index,
      CBar *bar=this.m_list_series.At(i);
      //--- calculate the index, based on which the bar property is saved to the passed array
      n=total-1-i;
      //--- write the value of the obtained bar property using the calculated index
      //--- if the bar is not received or the property is equal to zero, write the value passed to the method as "empty" to the array
      array[n]=(bar==NULL ? empty : (bar.GetProperty(property)>0 && bar.GetProperty(property)<EMPTY_VALUE ? bar.GetProperty(property) : empty));
     }
   return true;
  }
//+------------------------------------------------------------------+

如我们所见,接收一方数组的索引是如此计算的:源数组中的最后一个值落入接收一方数组的零号单元格中。 因此,我们的时间序列列表(请求的柱线属性)将按照品种图表上的柱线编号顺序写入数组(例如,指标缓冲区),而时间序列列表中的柱线对象则以相反的顺序排放 — 最近时间(当前柱线)位于列表的末尾。 如果所复制时间序列的时间帧与图表时间帧匹配,我们可以用此方法将时间序列复制到缓冲区之中,这令我们能够快速将时间序列列表中所有柱线的属性复制到指标缓冲区。

在这两个类的构造函数中,设置按柱线时间对时间序列列表进行排序的标志

//+------------------------------------------------------------------+
//| Constructor 1 (current symbol and period timeseries)             |
//+------------------------------------------------------------------+
CSeriesDE::CSeriesDE(void) : m_bars(0),m_amount(0),m_required(0),m_sync(false)
  {
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   this.SetSymbolPeriod(NULL,(ENUM_TIMEFRAMES)::Period());
   this.m_period_description=TimeframeDescription(this.m_timeframe);
  }
//+------------------------------------------------------------------+
//| Constructor 2 (specified symbol and period timeseries)           |
//+------------------------------------------------------------------+
CSeriesDE::CSeriesDE(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false)
  {
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   this.SetSymbolPeriod(symbol,timeframe);
   this.m_sync=this.SetRequiredUsedData(required,0);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
  }
//+------------------------------------------------------------------+

在创建时间序列列表的方法中,把按索引排序替换为按时间排序,并补充创建柱线对象时出错情况下显示的文本,和添加对象到时间序列列表时发生的错误

//+------------------------------------------------------------------+
//| Create the timeseries list                                       |
//+------------------------------------------------------------------+
int CSeriesDE::Create(const uint required=0)
  {
//--- If the required history depth is not set for the list yet,
//--- display the appropriate message and return zero,
   if(this.m_amount==0)
     {
      ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA));
      return 0;
     }
//--- otherwise, if the passed 'required' value exceeds zero and is not equal to the one already set, 
//--- while being lower than the available bar number,
//--- set the new value of the required history depth for the list
   else if(required>0 && this.m_amount!=required && required<this.m_bars)
     {
      //--- If failed to set a new value, return zero
      if(!this.SetRequiredUsedData(required,0))
         return 0;
     }
//--- For the rates[] array we are to receive historical data to,
//--- set the flag of direction like in the timeseries,
//--- clear the bar object list and set the flag of sorting by bar index
   MqlRates rates[];
   ::ArraySetAsSeries(rates,true);
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   ::ResetLastError();
//--- Get historical data of the MqlRates structure to the rates[] array starting from the current bar in the amount of m_amount,
//--- if failed to get data, display the appropriate message and return zero
   int copied=::CopyRates(this.m_symbol,this.m_timeframe,0,(uint)this.m_amount,rates),err=ERR_SUCCESS;
   if(copied<1)
     {
      err=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                   CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
      return 0;
     }
//--- Historical data is received in the rates[] array
//--- In the rates[] array loop,
   for(int i=0; i<copied; i++)
     {
      //--- create a new bar object out of the current MqlRates structure by the loop index
      ::ResetLastError();
      CBar* bar=new CBar(this.m_symbol,this.m_timeframe,rates[i]);
      if(bar==NULL)
        {
         ::Print
           (
            DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ)," ",this.Header()," ",::TimeToString(rates[i].time),". ",
            CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError())
           );
         continue;
        }
      //--- If failed to add bar object to the list,
      //--- display the appropriate message with the error description in the journal
      if(!this.m_list_series.Add(bar))
        {
         err=::GetLastError();
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST)," ",bar.Header()," ",::TimeToString(rates[i].time),". ",
                      CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
        }
     }
//--- Return the size of the created bar object list
   return this.m_list_series.Total();
  }
//+------------------------------------------------------------------+

列表和时间序列数据的更新方法也稍作了修改:

//+------------------------------------------------------------------+
//| Update timeseries list and data                                  |
//+------------------------------------------------------------------+
void CSeriesDE::Refresh(SDataCalculate &data_calculate)
  {
//--- If the timeseries is not used, exit
   if(!this.m_available)
      return;
   MqlRates rates[1];
//--- Set the flag of sorting the list of bars by time
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
//--- If a new bar is present on a symbol and period,
   if(this.IsNewBarManual(data_calculate.rates.time))
     {
      //--- create a new bar object and add it to the end of the list
      CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,this.m_new_bar_obj.TimeNewBar(),DFUN_ERR_LINE);
      if(new_bar==NULL)
         return;
      if(!this.m_list_series.InsertSort(new_bar))
        {
         delete new_bar;
         return;
        }
      //--- Write the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol 
      this.SetServerDate();
      //--- if the timeseries exceeds the requested number of bars, remove the earliest bar
      if(this.m_list_series.Total()>(int)this.m_required)
         this.m_list_series.Delete(0);
      //--- save the new bar time as the previous one for the subsequent new bar check
      this.SaveNewBarTime(data_calculate.rates.time);
     }
     
//--- Get the bar index with the maximum time (zero bar) and bar object from the list by the obtained index
   int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME);
   CBar *bar=this.m_list_series.At(index);
   if(bar==NULL)
      return;
//--- if the work is performed in an indicator and the timeseries belongs to the current symbol and timeframe,
//--- copy price parameters (passed to the method from the outside) to the bar price structure
   int copied=1;
   if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==(ENUM_TIMEFRAMES)::Period())
     {
      rates[0].time=data_calculate.rates.time;
      rates[0].open=data_calculate.rates.open;
      rates[0].high=data_calculate.rates.high;
      rates[0].low=data_calculate.rates.low;
      rates[0].close=data_calculate.rates.close;
      rates[0].tick_volume=data_calculate.rates.tick_volume;
      rates[0].real_volume=data_calculate.rates.real_volume;
      rates[0].spread=data_calculate.rates.spread;
     }
//--- otherwise, get data to the bar price structure from the environment
   else
      copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates);
//--- If the prices are obtained, set the new properties from the price structure for the bar object
   if(copied==1)
      bar.SetProperties(rates[0]);
  }
//+------------------------------------------------------------------+

此处,列表排序现在也可以设置为按时间在创建新的柱线对象时将来自“新柱线”对象的柱线时间传递给类构造函数,因为我们仅在新柱线开立成为事实时才将添加其到列表之中,而“新柱线”对象已经包含了柱线的开立时间。 将其传递给构造函数。 此外,将创建新柱线对象的方法说明传递给构造函数。 如果创建新柱线对象失败,来自构造函数的消息会发送到日志,其中包含 CSeriesDE::Refresh 方法,和调用 CBar 类构造函数的代码。
若要从时间序列列表中获取最新的(当前)柱线,取时间序列列表中所有柱形对象的最长时间来查找它。 为此,先用 CSelect 类的 FindBarMax() 方法以最长时间查找柱线对象索引。 利用获得的索引,从列表中取出最后一根柱线。 该柱线即为当前柱线。 若是,出于某种原因我们无法获得当前的柱线索引,则索引值为 -1。 如果在索引为负数的情况下使用 At() 方法接收列表元素,则会得到 NULL 如果事实上为空,只需退出更新方法

按时间返回主要柱线对象属性的方法:

//+------------------------------------------------------------------+
//| Return bar's Open by time                                        |
//+------------------------------------------------------------------+
double CSeriesDE::Open(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Open() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar's High by time                                        |
//+------------------------------------------------------------------+
double CSeriesDE::High(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.High() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar's Low by time                                         |
//+------------------------------------------------------------------+
double CSeriesDE::Low(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Low() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar's Close by time                                       |
//+------------------------------------------------------------------+
double CSeriesDE::Close(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Close() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar time by time                                          |
//+------------------------------------------------------------------+
datetime CSeriesDE::Time(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Time() : 0);
  }
//+------------------------------------------------------------------+
//| Return bar tick volume by time                                   |
//+------------------------------------------------------------------+
long CSeriesDE::TickVolume(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar real volume by time                                   |
//+------------------------------------------------------------------+
long CSeriesDE::RealVolume(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar spread by time                                        |
//+------------------------------------------------------------------+
int CSeriesDE::Spread(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Spread() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

它们都以同样的方式工作:
从时间序列列表中按时间获取柱线对象
并考虑到接收柱线对象的错误,返回相应属性的值。

考虑到需要按时间获取当前柱线对象,还改进了在控制程序图表上创建和发送“新柱线”事件的方法:

//+------------------------------------------------------------------+
//| Create and send the "New bar" event                              |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CSeriesDE::SendEvent(void)
  {
   int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME);
   CBar *bar=this.m_list_series.At(index);
   if(bar==NULL)
      return;
   ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,bar.Time(),this.Timeframe(),this.Symbol());
  }
//+------------------------------------------------------------------+

就像在 Refresh() 方法中一样,这里我们从时间序列列表中获取当前的柱线对象,并当向控制程序图表发送自定义事件时,把柱线时间传递至 lparam 参数。

时间序列类的改进既已完成。 现在我们改进单一品种的所有时间序列的类

如前所述,CTimeSerirs 类可能会导致与标准库的同名类发生冲突。 因此,我已将其重命名为 CTimeSerirsDE。 在类列表中,我将 CTimeSerirs 的所有实例替换为 CTimeSerirsDE,以及 CSerirs 替换为 CSerirsDE。 研究以下简短示例来代替研究详细描述:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "SeriesDE.mqh"
#include "..\Ticks\NewTickObj.mqh"
//+------------------------------------------------------------------+
//| Symbol timeseries class                                          |
//+------------------------------------------------------------------+
class CTimeSeriesDE : public CBaseObjExt
  {
private:

在该类的公开部分,声明将指定时间序列柱线的指定实数型属性复制到所传递数组的方法

//--- Copy the specified double property of the specified timeseries to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool              CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe,
                                          const ENUM_BAR_PROP_DOUBLE property,
                                          double &array[],
                                          const double empty=EMPTY_VALUE);

//--- Compare CTimeSeriesDE objects (by symbol)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Display (1) description and (2) short symbol timeseries description in the journal
   void              Print(const bool created=true);
   void              PrintShort(const bool created=true);
   
//--- Constructors
                     CTimeSeriesDE(void){;}
                     CTimeSeriesDE(const string symbol);
  };
//+------------------------------------------------------------------+

上面我们在改进 CSeriesDE 类的同时曾研究过此方法。 我们来实现该方法:

//+------------------------------------------------------------------+
//| Copy the specified double property of the specified timeseries   |
//+------------------------------------------------------------------+
bool CTimeSeriesDE::CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe,
                                         const ENUM_BAR_PROP_DOUBLE property,
                                         double &array[],
                                         const double empty=EMPTY_VALUE)
  {
   CSeriesDE *series=this.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.CopyToBufferAsSeries(property,array,empty);
  }
//+------------------------------------------------------------------+

此处的一切都很简单:首先,在指定的时间帧内获取所需的时间序列,然后基于得到的时间序列对象返回方法调用结果

方法返回时间序列在所有品种时间序列列表中的索引,为进行搜索验证指定时间帧:

//+------------------------------------------------------------------+
//| Return the timeframe index in the list                           |
//+------------------------------------------------------------------+
int CTimeSeriesDE::IndexTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   const CSeriesDE *obj=new CSeriesDE(this.m_symbol,(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe));
   if(obj==NULL)
      return WRONG_VALUE;
   this.m_list_series.Sort();
   int index=this.m_list_series.Search(obj);
   delete obj;
   return index;
  }
//+------------------------------------------------------------------+

当为搜索而创建临时对象时,需检查指定的时间帧,如果它是 CURRENT_PERIOD ,则搜索将取当前时间帧

将新事件添加到事件列表时,在更新指定时间序列列表的方法里,使用来自 data_calculate 结构中的新柱线开立时间作为 lparam 参数值:

//+------------------------------------------------------------------+
//| Update a specified timeseries list                               |
//+------------------------------------------------------------------+
void CTimeSeriesDE::Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
  {
//--- Reset the timeseries event flag and clear the list of all timeseries events
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the timeseries from the list by its timeframe
   CSeriesDE *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0 || !series_obj.IsAvailable())
      return;
//--- Update the timeseries list
   series_obj.Refresh(data_calculate);
//--- If the timeseries object features the New bar event
   if(series_obj.IsNewBar(data_calculate.rates.time))
     {
      //--- send the "New bar" event to the control program chart
      series_obj.SendEvent();
      //--- set the values of the first date in history on the server and in the terminal
      this.SetTerminalServerDate();
      //--- add the "New bar" event to the list of timeseries events
      //--- in case of successful addition, set the event flag for the timeseries
      if(this.EventAdd(SERIES_EVENTS_NEW_BAR,series_obj.Time(data_calculate.rates.time),series_obj.Timeframe(),series_obj.Symbol()))
         this.m_is_event=true;
     }
  }
//+------------------------------------------------------------------+

CTimeSeriesDE 类的改进既已完成。 移至所有品种的所有时间序列对象集合对象的 CRime SeriesCollection 类。

当前,我们有两个被重新命名的类:CSeriesDE 和 CTimeSerirsDE。 在 CTimeSeriesCollection 类列表中,将 CTimeSerirs 的所有实例替换为 CTimeSerirsDE,将 CSerirs 替换为 CSerirsDE
研究以下简短示例来代替研究详细描述:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Series\TimeSeriesDE.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
//+------------------------------------------------------------------+
//| Symbol timeseries collection                                     |
//+------------------------------------------------------------------+
class CTimeSeriesCollection : public CBaseObjExt
  {
private:
   CListObj                m_list;                    // List of applied symbol timeseries
//--- Return the timeseries index by symbol name
   int                     IndexTimeSeries(const string symbol);
public:
//--- Return (1) oneself and (2) the timeseries list
   CTimeSeriesCollection  *GetObject(void)            { return &this;         }
   CArrayObj              *GetList(void)              { return &this.m_list;  }
//--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period
   CTimeSeriesDE          *GetTimeseries(const string symbol);
   CSeriesDE              *GetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe);

//--- Create the symbol timeseries list collection

在该类的公开部分,声明三个新方法:
按柱线开立时间返回指定品种的指定时间序列的柱线对象的方法,另两种方法按柱线索引柱线时间返回在另一个时间序列中的柱线开立时间相对应的单一时间序列的柱线对象:

//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time
//--- bar object of the first timeseries corresponding to the bar open time on the second timeseries (3) by index, (4) by time
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true);
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);

另外,在公开部分中声明另外两种方法:更新指定品种的所有时间序列的方法,和复制指定品种的指定时间序列的指定 double 属性至数组的方法

//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols
   void                    Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate);
   void                    Refresh(const string symbol,SDataCalculate &data_calculate);
   void                    Refresh(SDataCalculate &data_calculate);

//--- Get events from the timeseries object and add them to the list
   bool                    SetEvents(CTimeSeriesDE *timeseries);

//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(const bool created=true);
   void                    PrintShort(const bool created=true);
   
//--- Copy the specified double property of the specified timeseries of the specified symbol to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool                    CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                const ENUM_BAR_PROP_DOUBLE property,
                                                double &array[],
                                                const double empty=EMPTY_VALUE);
//--- Constructor
                           CTimeSeriesCollection();
  };
//+------------------------------------------------------------------+

按时间返回指定位置的指定品种的指定时间序列的柱线对象的方法实现:

//+------------------------------------------------------------------+
//| Return the bar object of the specified timeseries                |
//| of the specified symbol of the specified position by time        |
//+------------------------------------------------------------------+
CBar *CTimeSeriesCollection::GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time)
  {
   CSeriesDE *series=this.GetSeries(symbol,timeframe);
   if(series==NULL)
      return NULL;
   return series.GetBar(bar_time);
  }
//+------------------------------------------------------------------+

该方法传递时间序列的品种时间帧,我们应从中获得含有指定开立时间的柱线。

获取含有指定品种时间帧的时间序列对象,然后返回按柱线时间从获得的时间序列中获取的柱线对象
如果获取柱线失败,则返回 NULL

按索引从第一个时间序列获取柱线对象,其开立时间应与第二个时间序列相对应,方法实现如下:

//+------------------------------------------------------------------+
//| Return the bar object of the first timeseries by index           |
//| corresponding to the bar open time on the second timeseries      |
//+------------------------------------------------------------------+
CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                               const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
  {
   CBar *bar_first=this.GetBar(symbol_first,timeframe_first,index);
   if(bar_first==NULL)
      return NULL;
   CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time());
   return bar_second;
  }
//+------------------------------------------------------------------+

该方法接收第一张图表的品种时间帧,第一张图表上的柱线索引,以及第二张图表的品种周期

按指定的索引从第一个品种周期的时间序列中获取第一个柱线对象按第一个获得的柱线时间获取并返回第二个品种周期 的第二个柱线对象

第一张图表与第二张图表品种、周期必须匹配,按开立时间从第二张图表获取柱线位置,该方法再按索引返回第一张图表里的柱线。
这对我们意味着什么? 举例,我们可以在 M15 图表上快速标记出所有 Н1 柱线。
只需向方法传递当前品种М15 图表周期柱线在图表上的索引位置(例如,指标计算时的循环索引),当前品种Н1 周期。 该方法从当前品种图表和 Н1 周期返回柱线对象,且其开立时间应包含第一个指定柱线的开立时间。

从第一时间序列返回与第二时间序列上的开立时间相对应的柱线对象,方法实现如下:

//+------------------------------------------------------------------+
//| Return the bar object of the first timeseries by time            |
//| corresponding to the bar open time on the second timeseries      |
//+------------------------------------------------------------------+
CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time,
                                                               const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
  {
   CBar *bar_first=this.GetBar(symbol_first,timeframe_first,first_bar_time);
   if(bar_first==NULL)
      return NULL;
   CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time());
   return bar_second;
  }
//+------------------------------------------------------------------+

该方法类似于上面刚刚讲述的按索引I接收柱线对象的方法。 在此,系统会在指定的第一个时间序列中设置其开立时间,替代时间序列中的柱线索引。

您可能已经注意到,两个方法都会接收两个图表的周期和品种。 这意味着该方法能够从任何周期品种图表取回柱线对象,且其对应于第一个周期品种时间序列里指定位置的柱线对象。 这令我们可以轻松地匹配任何周期品种中的两根柱线,从而可比较任何柱线对象的属性。

将“非原生品种”的校验添加到更新指定品种的指定时间序列的方法之中:

//+------------------------------------------------------------------+
//| Update the specified timeseries of the specified symbol          |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
  {
//--- Reset the flag of an event in the timeseries collection and clear the event list
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the object of all symbol timeseries by a symbol name
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries==NULL)
      return;
//--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit
   if(symbol!=::Symbol() && !timeseries.IsNewTick())
      return;
//--- Update the required object timeseries of all symbol timeseries
   timeseries.Refresh(timeframe,data_calculate);
//--- If the timeseries has the enabled event flag,
//--- get events from symbol timeseries, write them to the collection event list
//--- and set the event flag in the collection
   if(timeseries.IsEvent())
      this.m_is_event=this.SetEvents(timeseries);
  }
//+------------------------------------------------------------------+

我们为什么需要这个? 我们在函数库计时器中更新所有的时间序列,也许并不属于当前周期和品种。 属于程序启动品种的时间序列应从程序的 Start, NewTick 或 Calculate 事件处理程序进行更新。 为避免计时器中当前品种出现新的即时报价事件(无论如何,当前品种时间序列都会通过即时报价进行更新),我们检查时间序列的品种是否与当前品种匹配,并且仅在时间序列与当前品种不匹配时才检查“新即时报价”事件。

更新指定品种所有时间序列的方法实现:

//+------------------------------------------------------------------+
//| Update all timeseries of the specified symbol                    |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,SDataCalculate &data_calculate)
  {
//--- Reset the flag of an event in the timeseries collection and clear the event list
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the object of all symbol timeseries by a symbol name
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries==NULL)
      return;
//--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit
   if(symbol!=::Symbol() && !timeseries.IsNewTick())
      return;
//--- Update all object timeseries of all symbol timeseries
   timeseries.RefreshAll(data_calculate);
//--- If the timeseries has the enabled event flag,
//--- get events from symbol timeseries, write them to the collection event list
//--- and set the event flag in the collection
   if(timeseries.IsEvent())
      this.m_is_event=this.SetEvents(timeseries);
  }
//+------------------------------------------------------------------+

方法逻辑的每行代码都在注释里加以讲述,所以我希望此处的一切都很清晰明了。

将指定时间序列对象的指定实数型数据写入传递给该方法的数组的方法实现:

//+------------------------------------------------------------------+
//| Copy the specified double property to the array                  |
//| for a specified timeseries of a specified symbol                 |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                 const ENUM_BAR_PROP_DOUBLE property,
                                                 double &array[],
                                                 const double empty=EMPTY_VALUE)
  {
   CSeriesDE *series=this.GetSeries(symbol,timeframe);
   if(series==NULL)
      return false;
   return series.CopyToBufferAsSeries(property,array,empty);
  }
//+------------------------------------------------------------------+

我们在改进 CSeriesDE 类时已经研究过上面的方法操作。
在此,我们只需按指定的品种周期即可获取所需的时间序列对象,然后返回调用时间序列的同名方法的结果

如此时间序列集合类的工作既已完成。

现在,我们需要为基于函数库的程序中新创建的方法提供访问。 这样的访问由 CEngine 库的主要对象提供。

打开 \MQL5\Include\DoEasy\Engine.mqh,并将所有 CSerirs 字符串实例替换为 CSerirsDE,并将 CTimeSerirs 替换为 CTimeSerirsDE。

在类的私密部分中,声明存储程序名称的类成员变量

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CTimeSeriesCollection m_time_series;                  // Timeseries collection
   CResourceCollection  m_resource;                      // Resource list
   CTradingControl      m_trading;                       // Trading management object
   CPause               m_pause;                         // Pause object
   CArrayObj            m_list_counters;                 // List of timer counters
   int                  m_global_error;                  // Global error code
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_tester;                     // Flag of working in the tester
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   bool                 m_is_account_event;              // Account change event flag
   bool                 m_is_symbol_event;               // Symbol change event flag
   ENUM_TRADE_EVENT     m_last_trade_event;              // Last account trading event
   int                  m_last_account_event;            // Last event in the account properties
   int                  m_last_symbol_event;             // Last event in the symbol properties
   ENUM_PROGRAM_TYPE    m_program;                       // Program type
   string               m_name;                          // Program name

在类构造函数中,将程序名称分配给变量

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name=::MQLInfoString(MQL_PROGRAM_NAME);
   
...

在类的公开部分中,添加按柱线时间返回指定位置指定品种指定时间序列柱线对象的方法
两个方法,可按索引和时间从第一个时间序列返回与第二个时间序列上的柱线开立时间相对应的柱线对象
更新指定品种所有时间序列的方法
按时间返回柱线基础属性的方法
将指定品种指定时间序列的指定 double 型属性复制到数组的方法
,和 返回基于函数库的程序名称的方法

//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time
   CBar                *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true)
                          { return this.m_time_series.GetBar(symbol,timeframe,index,from_series);                 }
   CBar                *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
                          { return this.m_time_series.GetBar(symbol,timeframe,time);                              }
//--- Return the bar object of the first timeseries corresponding to the bar open time on the second timeseries (1) by index, (2) by time
   CBar                *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                                const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
                          { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,index,symbol_second,timeframe_second); }
   
   CBar                *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime time,
                                                                const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
                          { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,time,symbol_second,timeframe_second); }

//--- Return the flag of opening a new bar of the specified timeseries of the specified symbol
   bool                 SeriesIsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0)
                          { return this.m_time_series.IsNewBar(symbol,timeframe,time);                            }

//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols
   void                 SeriesRefresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(symbol,timeframe,data_calculate);                          }
   void                 SeriesRefresh(const string symbol,SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(symbol,data_calculate);                                    }
   void                 SeriesRefresh(SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(data_calculate);                                           }

//--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period
   CTimeSeriesDE       *SeriesGetTimeseries(const string symbol)
                          { return this.m_time_series.GetTimeseries(symbol);                                      }
   CSeriesDE           *SeriesGetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe)
                          { return this.m_time_series.GetSeries(symbol,timeframe);                                }
//--- Return (1) an empty, (2) partially filled timeseries
   CSeriesDE           *SeriesGetSeriesEmpty(void)       { return this.m_time_series.GetSeriesEmpty();            }
   CSeriesDE           *SeriesGetSeriesIncompleted(void) { return this.m_time_series.GetSeriesIncompleted();      }

//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume,
//--- (7) RealVolume, (8) Spread of the bar, specified by index, of the specified symbol of the specified timeframe
   double               SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   datetime             SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   long                 SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   long                 SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   int                  SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   
//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume,
//--- (7) RealVolume, (8) Spread of the bar, specified by time, of the specified symbol of the specified timeframe
   double               SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   double               SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   double               SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   double               SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   datetime             SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   long                 SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   long                 SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   int                  SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   
//--- Copy the specified double property of the specified timeseries of the specified symbol to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool                 SeriesCopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_BAR_PROP_DOUBLE property,
                                                   double &array[],const double empty=EMPTY_VALUE)
                          { return this.m_time_series.CopyToBufferAsSeries(symbol,timeframe,property,array,empty);}

...

//--- Return the program name
   string               Name(void)                                const { return this.m_name;                                 }

在类主体中实现的所有方法都返回调用上述 TimeSeriesCollection 时间序列集合的同名方法的结果。

实现按时间返回柱线基础属性的方法:

//+------------------------------------------------------------------+
//| Return Open of the specified bar by time                         |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Open() : 0);
  }
//+------------------------------------------------------------------+
//| Return High of the specified bar by time                         |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.High() : 0);
  }
//+------------------------------------------------------------------+
//| Return Low of the specified bar by time                          |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Low() : 0);
  }
//+------------------------------------------------------------------+
//| Return Close of the specified bar by time                        |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Close() : 0);
  }
//+------------------------------------------------------------------+
//| Return Time of the specified bar by time                         |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
datetime CEngine::SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Time() : 0);
  }
//+------------------------------------------------------------------+
//| Return TickVolume of the specified bar by time                   |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
long CEngine::SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return RealVolume of the specified bar by time                   |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
long CEngine::SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return Spread of the specified bar by time                       |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
int CEngine::SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Spread() : INT_MIN);
  }
//+------------------------------------------------------------------+

此处的一切都很简单:
利用 GetBar() 方法从时间序列集合类中获取柱线对象,指定时间序列品种周期,以及时间序列中所请求的柱线开立时间,并考虑到从时间序列获取柱线出错,则返回所获柱线的相应属性值。

将当前品种的所有时间序列的更新添加到当前品种的新即时报价事件处理程序当中:

//+------------------------------------------------------------------+
//| NewTick event handler                                            |
//+------------------------------------------------------------------+
void CEngine::OnTick(SDataCalculate &data_calculate,const uint required=0)
  {
//--- If this is not a EA, exit
   if(this.m_program!=PROGRAM_EXPERT)
      return;
//--- Re-create empty timeseries and update the current symbol timeseries
   this.SeriesSync(data_calculate,required);
   this.SeriesRefresh(NULL,data_calculate);
//--- end
  }
//+------------------------------------------------------------------+

这允许在尝试同步之后,立即更新 EA 中当前品种的所有用到的时间序列,因此我们不必等待函数库计时器中当前品种的时间序列更新,因为当前品种的新即时报价到达时会调用计时器中的数据更新,然而有时会导致数据不同步。

在当前品种的 “Calculate” 事件处理程序中,所有时间序列同步后,添加刷新当前品种的所有时间序列

//+------------------------------------------------------------------+
//| Calculate event handler                                          |
//+------------------------------------------------------------------+
int CEngine::OnCalculate(SDataCalculate &data_calculate,const uint required=0)
  {
//--- If this is not an indicator, exit
   if(this.m_program!=PROGRAM_INDICATOR)
      return data_calculate.rates_total;
//--- Re-create empty timeseries
//--- If at least one of the timeseries is not synchronized, return zero
   if(!this.SeriesSync(data_calculate,required))
     {
      return 0;
     }
//--- Update the timeseries of the current symbol and return rates_total
   this.SeriesRefresh(NULL,data_calculate);
   return data_calculate.rates_total;
  }
//+------------------------------------------------------------------+

这是与 OnTick() 处理程序的区别 — 该方法返回零,直到当前品种的所有用到的时间序列都已同步为止,继而通知指标的 OnCalculate() 处理程序进行全新的历史数据计算。

因此,所有时间序列的数据同步方法现在应返回布尔值:

//+------------------------------------------------------------------+
//| Synchronize timeseries data with the server                      |
//+------------------------------------------------------------------+
bool CEngine::SeriesSync(SDataCalculate &data_calculate,const uint required=0)
  {
//--- If the timeseries data is not calculated, try re-creating the timeseries
//--- Get the pointer to the empty timeseries
   CSeriesDE *series=this.SeriesGetSeriesEmpty();
//--- If there is an empty timeseries
   if(series!=NULL)
     {
      //--- Display the empty timeseries data as a chart comment and try synchronizing the timeseries with the server data
      ::Comment(series.Header(),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_WAIT_FOR_SYNC));
      ::ChartRedraw(::ChartID());
      //--- if the data has been synchronized
      if(series.SyncData(required,data_calculate.rates_total))
        {
         //--- if managed to re-create the timeseries
         if(this.m_time_series.ReCreateSeries(series.Symbol(),series.Timeframe(),data_calculate.rates_total))
           {
            //--- display the chart comment and the journal entry with the re-created timeseries data
            ::Comment(series.Header(),": OK");
            ::ChartRedraw(::ChartID());
            Print(series.Header()," ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED_OK),":");
            series.PrintShort();
            return true;
           }
        }
      //--- Data is not yet synchronized or failed to re-create the timeseries
      return false;
     }
//--- There are no empty timeseries - all is synchronized, delete all comments
   else
     {
      ::Comment("");
      ::ChartRedraw(::ChartID());
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

CEngine 类的改进现已完成。

现在,我们查验一下在指标中所有这些如何工作。 由于我们在单个指标中使用了多个不同的时间序列,并且能够获得与其他柱线数据相对应的单根柱线数据,且时间在其他时间序列的第一根柱线的边界内,由此想到的第一件事就是创建一个指标,在当前图表上以 OHLC 形式显示其他时间帧内的柱线。

创建和测试多周期指标

为了执行测试,我们借用在上一篇文章中开发的指标,并将其命名为 TestDoEasyPart40.mq5,并保存在 \MQL5\Indicators\TestDoEasy\Part40\ 当中。

我们也许会用到标准可用图表的全部周期数量,即 21 个时间序列。 这些设置含有一组标准的已用时间帧,而图表会显示在设置中所选已用时间帧相对应的按钮。 为避免声明指标缓冲区的代码过滥,只需为终端中存在的每个图表周期分配缓冲区,并存于结构数组当中。
通过启用/禁用相应的按钮,可在指标数据窗口中启用/禁用图表上缓冲区线形及其数据的可见性。 会为每个时间帧分配两个缓冲区(绘制和计算)。 计算用缓冲区允许存储相应时间序列的中间数据。 然而,在当前实现中,未用到计算用的缓冲区。 为了避免编写所有 42 个缓冲区(绘制 21 和计算 21 个缓冲区),我们将创建一个结构,该结构存储每个时间帧的参数:

  • 数组是按指标绘制缓冲区分配
  • 数组是按指标计算用缓冲区分配
  • 缓冲区 ID(时间序列的时间帧,其数据将由缓冲区显示)
  • 指标缓冲区的索引与绘制缓冲区数组相关
  • 指标缓冲区的索引与计算用缓冲区数组相关
  • 在指标中使用缓冲区的标志(按钮已按下/未按下)
  • 通过图表按钮启用/禁用,表示缓冲区在指标中的显示标志

指标设置令您可以决定是否应启用每个时间帧,以及相应地选择哪个时间序列。 根据所选时间序列绘制的图表按钮,允许启用/禁用图表上相应指标缓冲区的显示。 在指标上显示缓冲区直到按钮再次启用/禁用缓冲区显示的标记,我们仅在按下相应按钮时才决定在图表上删除或显示缓冲区数据。

为每个指标缓冲区设置所有参数(我们可以通过编程方式设置它,但是目前的方法更迅捷):

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart40.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_chart_window
#property indicator_buffers 43
#property indicator_plots   21
//--- plot M1
#property indicator_label1  " M1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot M2
#property indicator_label2  " M2"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot M3
#property indicator_label3  " M3"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//--- plot M4
#property indicator_label4  " M4"
#property indicator_type4  DRAW_LINE
#property indicator_color4  clrGray
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- plot M5
#property indicator_label5  " M5"
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrGray
#property indicator_style5  STYLE_SOLID
#property indicator_width5  1
//--- plot M6
#property indicator_label6  " M6"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrGray
#property indicator_style6  STYLE_SOLID
#property indicator_width6  1
//--- plot M10
#property indicator_label7  " M10"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrGray
#property indicator_style7  STYLE_SOLID
#property indicator_width7  1
//--- plot M12
#property indicator_label8  " M12"
#property indicator_type8   DRAW_LINE
#property indicator_color8  clrGray
#property indicator_style8  STYLE_SOLID
#property indicator_width8  1
//--- plot M15
#property indicator_label9  " M15"
#property indicator_type9   DRAW_LINE
#property indicator_color9  clrGray
#property indicator_style9  STYLE_SOLID
#property indicator_width9  1
//--- plot M20
#property indicator_label10 " M20"
#property indicator_type10  DRAW_LINE
#property indicator_color10 clrGray
#property indicator_style10 STYLE_SOLID
#property indicator_width10 1
//--- plot M30
#property indicator_label11 " M30"
#property indicator_type11  DRAW_LINE
#property indicator_color11 clrGray
#property indicator_style11 STYLE_SOLID
#property indicator_width11 1
//--- plot H1
#property indicator_label12 " H1"
#property indicator_type12  DRAW_LINE
#property indicator_color12 clrGray
#property indicator_style12 STYLE_SOLID
#property indicator_width12 1
//--- plot H2
#property indicator_label13 " H2"
#property indicator_type13  DRAW_LINE
#property indicator_color13 clrGray
#property indicator_style13 STYLE_SOLID
#property indicator_width13 1
//--- plot H3
#property indicator_label14 " H3"
#property indicator_type14  DRAW_LINE
#property indicator_color14 clrGray
#property indicator_style14 STYLE_SOLID
#property indicator_width14 1
//--- plot H4
#property indicator_label15 " H4"
#property indicator_type15  DRAW_LINE
#property indicator_color15 clrGray
#property indicator_style15 STYLE_SOLID
#property indicator_width15 1
//--- plot H6
#property indicator_label16 " H6"
#property indicator_type16  DRAW_LINE
#property indicator_color16 clrGray
#property indicator_style16 STYLE_SOLID
#property indicator_width16 1
//--- plot H8
#property indicator_label17 " H8"
#property indicator_type17  DRAW_LINE
#property indicator_color17 clrGray
#property indicator_style17 STYLE_SOLID
#property indicator_width17 1
//--- plot H12
#property indicator_label18 " H12"
#property indicator_type18  DRAW_LINE
#property indicator_color18 clrGray
#property indicator_style18 STYLE_SOLID
#property indicator_width18 1
//--- plot D1
#property indicator_label19 " D1"
#property indicator_type19  DRAW_LINE
#property indicator_color19 clrGray
#property indicator_style19 STYLE_SOLID
#property indicator_width19 1
//--- plot W1
#property indicator_label20 " W1"
#property indicator_type20  DRAW_LINE
#property indicator_color20 clrGray
#property indicator_style20 STYLE_SOLID
#property indicator_width20 1
//--- plot MN1
#property indicator_label21 " MN1"
#property indicator_type21  DRAW_LINE
#property indicator_color21 clrGray
#property indicator_style21 STYLE_SOLID
#property indicator_width21 1

//--- classes

如我们所见,缓冲区总数设置为 43,而绘制缓冲区的数量设置为 21。 鉴于我决定向每个绘制缓冲区里添加一个计算用缓冲区,因此结果为 21 + 21 = 42。 那个额外的缓冲区从哪里来? 我们需要 OnCalculate() 里 time[] 数组存储的时间数据。 由于某些函数需要按索引显示柱线时间,而 time[] 数组仅存在于 OnCalculate() 处理程序的可见性范围之内,因此,最简单的解决方案是将存有当前时间帧每根柱线时间数据的 time[] 数组保存在指标的一个计算用缓冲区之内。 这就是为什么我还要设置一个缓冲区。

该指标提供了显示柱线四个价格的能力:开盘价,最高价,最低价和收盘价。 柱线对象拥有更多实数型属性:

  • 柱线开盘价 (Open)
  • 柱线周期内最高价 (High)
  • 柱线周期内最低价 (Low)
  • 柱线收盘价 (Close)
  • 蜡烛尺寸
  • 蜡烛实体尺寸
  • 蜡烛实体顶
  • 蜡烛实体底
  • 蜡烛上影尺寸
  • 蜡烛下影尺寸

因此,我们不能在设置中使用枚举的值(ENUM_BAR_PROP_DOUBLE)。 我们创建另一个枚举,其枚举含有与 ENUM_BAR_PROP_DOUBLE 柱线对象的实数型属性匹配的必要属性,可以在显示设置中进行选择,并为可用图表的周期总数设置宏替换

//--- classes

//--- enums
enum ENUM_BAR_PRICE
  {
   BAR_PRICE_OPEN    =  BAR_PROP_OPEN,    // Bar Open
   BAR_PRICE_HIGH    =  BAR_PROP_HIGH,    // Bar High
   BAR_PRICE_LOW     =  BAR_PROP_LOW,     // Bar Low
   BAR_PRICE_CLOSE   =  BAR_PROP_CLOSE,   // Bar Close
  };
//--- defines
#define PERIODS_TOTAL   (21)              // Total amount of available chart periods
//--- structures

现在,我们创建一个数据结构,可为单一时间序列(图表周期)分配一个绘制缓冲区和一个计算用缓冲区:

//--- structures
struct SDataBuffer
  {
private:
   int               m_buff_id;           // Buffer ID (timeframe)
   int               m_buff_data_index;   // The index of the indicator buffer related to the Data[] array
   int               m_buff_tmp_index;    // The index of the indicator buffer related to the Temp[] array
   bool              m_used;              // The flag of using the buffer in the indicator
   bool              m_show_data;         // The flag of displaying the buffer on the chart before enabling/disabling its display
public:
   double            Data[];              // The array assigned as INDICATOR_DATA by the indicator buffer
   double            Temp[];              // The array assigned as INDICATOR_CALCULATIONS by the indicator buffer
//--- Set indices for the drawn and calculated buffers assigned to the timeframe
   void              SetIndex(const int index)
                       {
                        this.m_buff_data_index=index;
                        this.m_buff_tmp_index=index+PERIODS_TOTAL;
                       }
//--- Methods of setting and returning values of the private structure members
   void              SetID(const int id)              { this.m_buff_id=id;             }
   void              SetUsed(const bool flag)         { this.m_used=flag;              }
   void              SetShowData(const bool flag)     { this.m_show_data=flag;         }
   int               IndexDataBuffer(void)      const { return this.m_buff_data_index; }
   int               IndexTempBuffer(void)      const { return this.m_buff_tmp_index;  }
   int               ID(void)                   const { return this.m_buff_id;         }
   bool              IsUsed(void)               const { return this.m_used;            }
   bool              GetShowDataFlag(void)      const { return this.m_show_data;       }
   void              Print(void);
  };
//--- Display structure data to the journal
void SDataBuffer::Print(void)
  {
   ::Print
     (
      "Buffer[",this.IndexDataBuffer(),"], ID: ",(string)this.ID(),
      " (",TimeframeDescription((ENUM_TIMEFRAMES)this.ID()),
      "), temp buffer index: ",(string)this.IndexTempBuffer(),
      ", used: ",this.IsUsed()
     );
  }
//--- input variables

结构可存储所有数据,可在单一时间帧内操作。 为指标用到的每个时间帧分配一个单独的结构。 相应的结构数组是最理想的解决方案。 我们在定义指标缓冲区的模块里创建它。

编写指标输入:

//--- input variables
/*sinput*/ENUM_SYMBOLS_MODE   InpModeUsedSymbols=  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
/*sinput*/string              InpUsedSymbols    =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list
sinput   string               InpUsedTFs        =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   ENUM_BAR_PRICE       InpBarPrice       =  BAR_PRICE_OPEN;                  // Applied bar price
sinput   bool                 InpShowBarTimes   =  false;                           // Show bar time comments
sinput   uint                 InpControlBar     =  1;                               // Control bar
sinput   uint                 InpButtShiftX     =  0;    // Buttons X shift 
sinput   uint                 InpButtShiftY     =  10;   // Buttons Y shift 
sinput   bool                 InpUseSounds      =  true; // Use sounds
//--- indicator buffers

此处的一切都类似于我为每篇文章提供的测试 EA 和指标。 鉴于我打算测试用单一品种测试操控,因此在品种设置中注释掉 sinput 修饰符,表示该变量是一个指标输入( sinput 修饰符表示已禁用变量的参数优化)。 所以,这些参数无法在设置里选择,而 SYMBOLS_MODE_CURRENT 值已分配给 InpModeUsedSymbols 变量 — 仅适合操控当前品种。
InpShowBarTimes 变量允许在图表上显示/隐藏注释 — 在当前周期图表显示与测试时间序列在时间上匹配的柱线。 InpControlBar 变量用于指定可以通过图表注释跟踪其值的柱线索引。

最后,编写指标缓冲区和全局变量:

//--- indicator buffers
SDataBuffer    Buffers[PERIODS_TOTAL];          // Array of the indicator buffer data structures assigned to the timeseries
double         BufferTime[];                    // The calculated buffer for storing and passing data from the time[] array
//--- global variables
CEngine        engine;                          // CEngine library main object
string         prefix;                          // Prefix of graphical object names
bool           testing;                         // Flag of working in the tester
int            used_symbols_mode;               // Mode of working with symbols
string         array_used_symbols[];            // Array of used symbols
string         array_used_periods[];            // Array of used timeframes
//+------------------------------------------------------------------+

如您所见,我已经把上述结构数组设置为指标缓冲区定义。 在初始化指标时,我们将数据分配给结构数组,并将结构数组与指标缓冲区绑定。 此处定义了所计算的缓冲区,该缓冲区存储并传递时间给指标函数。
指标的全局变量也已注释,并且我相信这都是可以理解的。

在指标的 OnInit() 处理程序中,首先创建含有按钮的面板,这些按钮对应于设置中选择的时间帧。 然后分配所有指标缓冲区,并在位于指标缓冲区结构数组中为所有指标缓冲区设置参数

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set indicator global variables
   prefix=engine.Name()+"_";
   testing=engine.IsTester();
   ZeroMemory(rates_data);
   
//--- Initialize DoEasy library
   OnInitDoEasy();

//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;

//--- Check playing a standard sound using macro substitutions
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(SND_NEWS);

//--- indicator buffers mapping

   //--- In the loop by the total number of available timeframes,
   for(int i=0;i<PERIODS_TOTAL;i++)
     {
      //--- get the next timeframe
      ENUM_TIMEFRAMES timeframe=TimeframeByEnumIndex(uchar(i+1));
      //--- Bind the drawn indicator buffer by the buffer index equal to the loop index with the structure Data[] array
      SetIndexBuffer(i,Buffers[i].Data);
      //--- set "the empty value" for the Data[] buffer, 
      //--- set the name of the graphical series displayed in the data window for the Data[] buffer
      //--- set the direction of indexing the Data[] drawn buffer as in the timeseries
      PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,EMPTY_VALUE);
      PlotIndexSetString(i,PLOT_LABEL,"Buffer "+TimeframeDescription(timeframe));
      ArraySetAsSeries(Buffers[i].Data,true);
      //--- Setting the drawn buffer according to the button status
      bool state=false;
      //--- Set the name of the button correspondign to the buffer with the loop index and its timeframe
      string name=prefix+"BUTT_"+TimeframeDescription(timeframe);
      //--- If not in the tester, while the chart features the button with the specified name,
      if(!engine.IsTester() && ObjectFind(ChartID(),name)==0)
        {
         //--- set the name of the terminal global variable for storing the button status
         string name_gv=(string)ChartID()+"_"+name;
         //--- if no global variable with such a name is found, create it set to 'false',
         if(!GlobalVariableCheck(name_gv))
            GlobalVariableSet(name_gv,false);
         //--- get the button status from the terminal global variable
         state=GlobalVariableGet(name_gv);
        }
      //--- Set the values for all structure fields
      Buffers[i].SetID(timeframe);
      Buffers[i].SetIndex(i);
      Buffers[i].SetUsed(state);
      Buffers[i].SetShowData(state);
      //--- Set the button status
      ButtonState(name,state);
      //--- Depending on the button status, specify whether the buffer data should be displayed should be displayed in the data window
      PlotIndexSetInteger(i,PLOT_SHOW_DATA,state);
      //--- Bind the calculated indicator buffer by the buffer index from IndexTempBuffer() with the Temp[] array of the structure
      SetIndexBuffer(Buffers[i].IndexTempBuffer(),Buffers[i].Temp,INDICATOR_CALCULATIONS);
      //--- set the direction of indexing the Temp[] calculated buffer as in the timeseries
      ArraySetAsSeries(Buffers[i].Temp,true);
     }
   //--- Bind the calculated indicator buffer by the PERIODS_TOTAL*2 buffer index with the BufferTime[] array of the indicator
   SetIndexBuffer(PERIODS_TOTAL*2,BufferTime,INDICATOR_CALCULATIONS);
   //--- set the direction of indexing the BufferTime[] calculated buffer as in the timeseries
   ArraySetAsSeries(BufferTime,true);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

在此,我针对循环的所有代码进行了注释,其中指标缓冲区按循环索引与结构数组绑定,并且为每个结构数组单元中存储的每个结构设置其余参数。 如果您还有任何疑问,请随时在评论中提问:

按钮函数:

//+------------------------------------------------------------------+
//| Create the buttons panel                                         |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=20,const int shift_y=0)
  {
   int total=ArraySize(array_used_periods);
   uint w=30,h=20,x=InpButtShiftX+1, y=InpButtShiftY+h+1;
   //--- In the loop by the amount of used timeframes
   for(int i=0;i<total;i++)
     {
      //--- create the name of the next button
      string butt_name=prefix+"BUTT_"+array_used_periods[i];
      //--- create a new button with the offset by ((button width + 1) * loop index)
      if(!ButtonCreate(butt_name,x+(w+1)*i,y,w,h,array_used_periods[i],clrGray))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),array_used_periods[i]);
         return false;
        }
     }   
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| Create the button                                                |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Set the terminal's global variable value                         |
//+------------------------------------------------------------------+
bool SetGlobalVariable(const string gv_name,const double value)
  {
//--- If the variable name length exceeds 63 symbols, return 'false'
   if(StringLen(gv_name)>63)
      return false;
   return(GlobalVariableSet(gv_name,value)>0);
  }
//+------------------------------------------------------------------+
//| Return the button status                                         |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Return the button status by the timeframe name                   |
//+------------------------------------------------------------------+
bool ButtonState(const ENUM_TIMEFRAMES timeframe)
  {
   string name=prefix+"BUTT_"+TimeframeDescription(timeframe);
   return ButtonState(name);
  }
//+------------------------------------------------------------------+
//| Set the button status                                            |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
   if(state)
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240');
   else
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240');
  }
//+------------------------------------------------------------------+
//| Track the buttons' status                                        |
//+------------------------------------------------------------------+
void PressButtonsControl(void)
  {
   int total=ObjectsTotal(0,0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
//--- Convert button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
//--- Create the button name for the terminal's global variable
   string name_gv=(string)ChartID()+"_"+prefix+button;
//--- Get the button status (pressed/released). If not in the tester,
//--- write the status to the button global variable (1 or 0)
   bool state=ButtonState(button_name);
   if(!engine.IsTester())
      SetGlobalVariable(name_gv,state);
//--- Get the timeframe from the button string ID and
//--- the drawn buffer index by timeframe
   ENUM_TIMEFRAMES timeframe=TimeframeByDescription(StringSubstr(button,5));
   int buffer_index=IndexBuffer(timeframe);
//--- Set the button color depending on its status, 
//--- write its status to the buffer structure depending on the button status (used/not used)
//--- initialize the buffer corresponding to the button timeframe by the buffer index received earlier
   ButtonState(button_name,state);
   Buffers[buffer_index].SetUsed(state);
   if(Buffers[buffer_index].GetShowDataFlag()!=state)
     {
      InitBuffer(buffer_index);
      BufferFill(buffer_index);
      Buffers[buffer_index].SetShowData(state);
     }

//--- Here you can add additional handling of button pressing:
//--- If the button is pressed
   if(state)
     {
      //--- If M1 button is pressed
      if(button=="BUTT_M1")
        {
         
        }
      //--- If button M2 is pressed
      else if(button=="BUTT_M2")
        {
         
        }
      //---
      // Remaining buttons ...
      //---
     }
   //--- Not pressed
   else 
     {
      //--- M1 button
      if(button=="BUTT_M1")
        {
         
        }
      //--- M2 button
      if(button=="BUTT_M2")
        {
         
        }
      //---
      // Remaining buttons ...
      //---
     }
//--- re-draw the chart
   ChartRedraw();
  }
//+------------------------------------------------------------------+

所有这些函数都非常简单明了,此外,还对其中的一些代码进行了注释。

我们来查看一下指标的 OnCalculate() 应答函数:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the library:             |
//+------------------------------------------------------------------+
   
//--- Pass the current symbol data from OnCalculate() to the price structure
   CopyData(rates_data,rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Handle the Calculate event in the library
   engine.OnCalculate(rates_data);

//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      EventsHandling();             // Working with events
     }

//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Set OnCalculate arrays as timeseries
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);

//--- Setting buffer arrays as timeseries

//--- Check for the minimum number of bars for calculation
   if(rates_total<2 || Point()==0) return 0;
   
//--- Display reference data on bar open time
   if(InpShowBarTimes)
     {
      string txt="";
      int total=ArraySize(array_used_periods);
      //--- In the loop by the amount of used timeframes
      for(int i=0;i<total;i++)
        {
         //--- get the next timeframe, buffer index and timeseries object by timeframe
         ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[i]);
         int buffer_index=IndexBuffer(timeframe);
         CSeriesDE *series=engine.SeriesGetSeries(NULL,timeframe);
         //--- If failed to get the timeseries or the buffer is not used (the button is released), move on to the next one
         if(series==NULL || !Buffers[buffer_index].IsUsed())
            continue;
         //--- Get the reference bar from the timeseries list
         CBar *bar=series.GetBar(InpControlBar);
         if(bar==NULL)
            continue;
         //--- Collect data for the comment text
         string t1=TimeframeDescription((ENUM_TIMEFRAMES)Period());
         string t2=TimeframeDescription(bar.Timeframe());
         string t3=(string)InpControlBar;
         string t4=TimeToString(bar.Time());
         string t5=(string)bar.Index((ENUM_TIMEFRAMES)Period());
         //--- Set the comment text depending on the terminal language
         string tn=TextByLanguage
           (
            "Бар на "+t1+", соответствующий бару "+t2+"["+t3+"] со временеи открытия "+t4+", расположен на баре "+t5,
            "The bar on "+t1+", corresponding to the "+t2+"["+t3+"] bar since the opening time of "+t4+", is located on bar "+t5
           );
         txt+=tn+"\n";
        }
      //--- Display the comment on the chart
      Comment(txt);
     }

//--- Check and calculate the number of calculated bars
   int limit=rates_total-prev_calculated;

//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      InitBuffersAll();
     }
//--- Prepare data

//--- Calculate the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      BufferTime[i]=(double)time[i];
      CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

如果 "显示柱线时间注释" 蚕食 (InpShowBarTimes 变量) 设为 true,则代码模块在在当前图表 InpControlBar ("ControlBar") 变量指定柱线位置显示数据,其应与所有时间帧de1时间序列柱线相匹配。

如果计算出的 limit 值超过原有值(这意味着历史记录已刷新,且需要重新绘制整个历史记录),则 limit 设置为等于当前图表上的历史记录开始处,并调用初始化所有指标缓冲区的函数

指标是从 limit 值(正常情况下,等于1(新柱线)或零 — 从当前柱线计算)到零进行计算。
在指标主要计算循环当中从 time[] 数组中提取数据填充计算出的时间缓冲区(我们需要时间缓冲区,以便其他指标函数可按索引获取时间,因为 time[] 数组在有些地方已不可用),并调用为所有用到的指标缓冲区计算单根柱线的函数

初始化指标缓冲区的函数:

//+------------------------------------------------------------------+
//| Initialize the timeseries and the appropriate buffers by index   |
//+------------------------------------------------------------------+
bool InitBuffer(const int buffer_index)
  {
//--- Leave if the wrong index is passed
   if(buffer_index==WRONG_VALUE)
      return false;
Initialize the variables using the "Not rendered" drawing style and disable the display in the data window
   int draw_type=DRAW_NONE;
   bool show_data=false;
//--- If the buffer is used (button pressed)
//--- Set the "Line" drawing style for variables and enable display in the data window
   if(Buffers[buffer_index].IsUsed())
     {
      draw_type=DRAW_LINE;
      show_data=true;
     }
//--- Set the drawing style and display in the data window for the buffer by its index
   PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_DRAW_TYPE,draw_type);
   PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_SHOW_DATA,show_data);
//--- Initialize the calculated buffer using zero, while the drawn one is initialized using the "empty" value 
   ArrayInitialize(Buffers[buffer_index].Temp,0);
   ArrayInitialize(Buffers[buffer_index].Data,EMPTY_VALUE);
   return true;
  }
//+------------------------------------------------------------------+
//|Initialize the timeseries and the appropriate buffers by timeframe|
//+------------------------------------------------------------------+
bool InitBuffer(const ENUM_TIMEFRAMES timeframe)
  {
   return InitBuffer(IndexBuffer(timeframe));
  }
//+------------------------------------------------------------------+
//| Initialize all timeseries and the appropriate buffers            |
//+------------------------------------------------------------------+
void InitBuffersAll(void)
  {
//--- Initialize the next buffer in the loop by the total number of chart periods
   for(int i=0;i<PERIODS_TOTAL;i++)
      if(!InitBuffer(i))
         continue;
  }
//+------------------------------------------------------------------+

计算所有已用指标缓冲区的单根指定柱线的函数(按钮已按下):

//+------------------------------------------------------------------+
//| Calculating a single bar of all active buffers                   |
//+------------------------------------------------------------------+
void CalculateSeries(const ENUM_BAR_PROP_DOUBLE property,const int index,const datetime time)
  {
//--- Get the next buffer in the loop by the total number of chart periods
   for(int i=0;i<PERIODS_TOTAL;i++)
     {
      //--- if the buffer is not used (the button is released), move on to the next one
      if(!Buffers[i].IsUsed())
         continue;
      //--- get the timeseries object by the buffer timeframe
      CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[i].ID());
      //--- if the timeseries is not received
      //--- or the bar index passed to the function is beyond the total number of bars in the timeseries, move on to the next buffer
      if(series==NULL || index>series.GetList().Total()-1)
         continue;
      //--- get the bar object from the timeseries corresponding to the one passed to the bar time function on the current chart
      CBar *bar=engine.SeriesGetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,time,NULL,series.Timeframe());
      if(bar==NULL)
         continue;
      //--- get the specified property from the obtained bar and
      //--- call the function of writing the value to the buffer by i index
      double value=bar.GetProperty(property);
      SetBufferData(i,value,index,bar);
     }
  }
//+------------------------------------------------------------------+

按当前图表上的多根柱线索引将柱线对象属性写入指标缓冲区的函数:

//+------------------------------------------------------------------+
//| Write data on a single bar to the specified buffer               |
//+------------------------------------------------------------------+
void SetBufferData(const int buffer_index,const double value,const int index,const CBar *bar)
  {
//--- Get the bar index by its time falling within the time limits on the current chart
   int n=iBarShift(NULL,PERIOD_CURRENT,bar.Time());
//--- If the passed index on the current chart (index) is less than the calculated time of bar start on another timeframe
   if(index<n)
      //--- in the loop from the n bar on the current chart to zero
      while(n>WRONG_VALUE && !IsStopped())
        {
         //--- fill in the n index buffer with the 'value' passed to the function (0 - EMPTY_VALUE)
         //--- and decrease the n value
         Buffers[buffer_index].Data[n]=(value>0 ? value : EMPTY_VALUE);
         n--;
        }
//--- If the passed index on the current chart (index) is not less than the calculated time of bar start on another timeframe
//--- Set 'value' for the buffer by the 'index' passed to the function (0 - EMPTY_VALUE)
   else
      Buffers[buffer_index].Data[index]=(value>0 ? value : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

为了在当前图表上正确显示来自另一个时间帧的柱线数据,需在当前图表上找到指定蜡烛(柱线)的起始位置,然后用另一个周期的柱线值填充当前图表上的所有缓冲区索引。 这就是该函数的作用。

当按下时间帧激活按钮时,我们需要在显示的缓冲区中填充一个空值(如果按钮已被释放),或者完整重新计算该按钮代表的缓冲区所有数据(如果按钮已被按下)。 缓冲区初始化函数删除数据,而以下函数会用指定的时间序列数据填充缓冲区:

//+------------------------------------------------------------------+
//| Fill in the entire buffer with historical data                   |
//+------------------------------------------------------------------+
void BufferFill(const int buffer_index)
  {
//--- Leave if the wrong index is passed
   if(buffer_index==WRONG_VALUE)
      return;
//--- Leave if the buffer is not used (the button is released)
   if(!Buffers[buffer_index].IsUsed())
      return;
//--- Get the timeseries object by the buffer timeframe
   CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[buffer_index].ID());
   if(series==NULL)
      return;
//--- If the buffer belongs to the current chart, copy the bar data from the timeseries to the buffer
   if(Buffers[buffer_index].ID()==Period())
      series.CopyToBufferAsSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,Buffers[buffer_index].Data,EMPTY_VALUE);
//--- Otherwise, calculate each next timeseries bar and write it to the buffer in the loop by the number of the current chart bars
   else 
      for(int i=rates_data.rates_total-1;i>WRONG_VALUE && !IsStopped();i--)
         CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,(datetime)BufferTime[i]);
  }
//+------------------------------------------------------------------+

完整的指标代码在下面的文件中提供。

请记住,此测试指示标以 MQL5 开发的。 它也可以在 MQL4 上运行,但不能按正常方式运行 — 按下相应的按钮时,不会显示当前图表周期。 仅在激活另一个时间帧时才会显示。 在 MetaTrader 4 设定中设置非标准图表周期时,指标将无休止地等待同步。
另外,某些数据在终端数据窗口中显示不正确 — 显示了所有指标缓冲区(包括计算出用指标缓冲区),这很自然,因为并非所有 MQL5 函数都能在 MQL4 里工作,应将其替换为 MQL4 对应的函数。
甚至,由于指标是出于测试目的,即检查多周期模式下的操作,因此指标也可能会错误地处理 MetaTrader 5 中的历史数据变化。 所有检测到的错误将在后续文章里逐步修复。 如果在 MetaTrader 5 中消除了所有瑕疵,则函数库会针对 MetaTrader 4 指标进行调整。

编译指标并在图表上启动它:


如我们所见,在 М15上,来自 М5 的数据缓冲区仅在当前图表上的三分之一蜡烛中显示了 М5 的收盘价,这是可以理解的,因为单根 М15 的柱线包含三根 М5 的柱线,而 М5 的收盘价会显示在 М15 柱线上。

在测试器中启动指标,,启用在当前周期图表上显示时间序列数据:



下一步是什么?

在下一篇文章中,我们将继续处理指标中的函数库时间序列对象。

以下附件是函数库当前版本的所有文件,以及测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。

返回内容目录

该系列中的先前文章:

DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表
DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象
DoEasy 函数库中的时间序列(第三十七部分):时间序列集合 - 按品种和周期的时间序列数据库
DoEasy 函数库中的时间序列(第三十八部分):时间序列集合 - 实时更新以及从程序访问数据
DoEasy 函数库中的时间序列(第三十九部分):基于函数库的指标 - 准备数据和时间序列事件



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

附加的文件 |
MQL5.zip (3700.41 KB)
MQL4.zip (3700.4 KB)
连续前行优化 (第六部分): 自动优化器的逻辑部分和结构 连续前行优化 (第六部分): 自动优化器的逻辑部分和结构

我们之前曾研究过创建自动前行优化。 这次,我们将继续探究自动优化器工具的内部结构。 本文对于那些希望深入操控所创建项目并进行修改的人士,以及那些希望理解程序逻辑的人士来说都很有用处。 本文包含 UML 示意图,它能揭示项目的内部结构,以及对象之间的关系。 它还阐述了优化开始的过程,但未包含优化器实现过程的讲述。

MQL 作为 MQL 程序图形界面的标记工具。 第二部分 MQL 作为 MQL 程序图形界面的标记工具。 第二部分

本篇论文继续验证新概念,即利用 MQL 结构描述 MQL 程序的窗口界面。 基于 MQL 标记自动创建 GUI 提供了缓存和动态生成元素和控制风格,以及事件处理的新方案。 随附的是标准控件库的增强版本。

开发跨平台网格 EA:测试多币种 EA 开发跨平台网格 EA:测试多币种 EA

行情在一个月内下跌了 30% 以上。 这似乎是测试基于网格和马丁格尔的智能交易系统的最佳时间。 本文是“创建跨平台网格 EA”系列的计划外延续。 当前行情为安排网格 EA 提供了疏解压力的机会。 因此,我们要把握这次机会,并测试我们的智能交易系统。

连续前行优化 (第七部分): 将自动优化器的逻辑部分与图形绑定,并通过程序控制图形 连续前行优化 (第七部分): 将自动优化器的逻辑部分与图形绑定,并通过程序控制图形

本文介绍了自动优化程序的图形部分与其逻辑部分的连接。 它考虑了优化启动过程,从单击按钮到把任务重定向到优化管理器。