English Русский Español Deutsch 日本語 Português
preview
多交易品种多周期指标中的 DRAW_ARROW 绘图类型

多交易品种多周期指标中的 DRAW_ARROW 绘图类型

MetaTrader 5示例 | 29 八月 2024, 10:45
476 0
Artyom Trishkin
Artyom Trishkin

目录


概述

我们继续讨论多交易品种多周期指标。本系列的上一篇文章介绍了多交易品种多周期指标。在本文中,我们将修改多指标类,使其可以与箭头指标一起使用。

箭头指标意味着其绘图缓冲区中的数据并非始终可用。该值存在于显示箭头的缓冲区中,而在其他时候,缓冲区中包含的是为其设置的空值。通常是 EMPTY_VALUE,但也可以将缓冲区设置为"空"且不会显示在图表上的任何值。可以使用这个函数

PlotIndexSetDouble(buffer_index,PLOT_EMPTY_VALUE,empty_value);

其中,buffer_index 是要设置为空的缓冲区索引,而 empty_value 则是将为该缓冲区设置的 "空值"。

在多周期指标中,缓冲区充满了有间隙的数据,因此需要考虑到没有箭头的空值的存在,不要将这些空值输入到已经输入了非空值的图表柱中。否则,先前放置的箭头将被一个新的空值擦除。如果将在较低时间框架内计算的指标数据复制到较高时间框架内,就会出现这种情况。

让我举一个例子。М15柱形叠加在M5图表上:


这里我们看到的是 M5 图表的分形,需要将其安装到 M15 图表的柱形图上。

  • 对于第一个柱 M15 (I),我们需要设置位于第 3 个柱的上分形 - 该分形在任何情况下都会在 M15 上出现,因为它是三个柱中的最后一个柱。
  • 对于第二个柱 M15 (II),您需要设置位于第 2 个柱的分形 - 只有在不复制第 3 个柱的空值的情况下,该分形才会在 M15 上显示,因为它将取代下分形的值。M15 柱形将有两个分形 - 一个上分形和一个下分形,因为 M5 柱形的第 2 个柱有一个下分形,第 3 个柱有一个上分形。
  • 对于 M15 的第三个柱形(III),我们需要设置第 1 个柱形图的顶部分形 - 只有在不复制第 2 个柱形和第 3 个柱形中的空值的情况下,这个分形才会在 M15 上显示,因为它们会取代分形值。

因此,我们需要跟踪当前图表柱形上的值,如果它是空的,我们就可以从较低时间框架的缓冲区中复制任何值。但如果值不再为空,那么我们只需复制非空值即可。在这种情况下,较高时间框架的图表条形图包含较低时间框架柱形中最后一个分形的值,这些分形是较高图表周期柱形的一部分。为什么是最后一个柱,而不是第一个柱?我认为最后一个值更有意义,因为它更接近当前时间。


在将较高时间框架计算的指标数据复制到较低时间框架的图表时,我们需要考虑到箭头可以不放在图表的零柱或第一个柱上,而是放在第二个柱、第三个柱或更高的一个柱上。如果只复制一号和零号柱的数据,就像现在在多指标类中所做的那样,那么图表上就不会显示箭头 - 它们只是位于索引较高的柱形上,因此指标计算算法根本无法看到它们:


在这里,我们可以看到分形是在索引为 2 的柱形上形成的。如果我们只复制零号和一号柱,那么程序将无法看到这个分形。

因此,由于我们无法准确知道哪个指标将箭头放在哪个柱形上,因此有必要为多指标类引入一个变量,在该变量中我们将明确指出搜索和复制值是从哪个柱形开始的。目前,该类只复制当前分时的第一个和第零个柱。因此,它无法看到分形 - 数值从索引为 2 的柱形开始。

如果使用自定义指标作为初始指标,那么它可以在任何其他柱形上放置箭头,而不是像分形指标那样,只能在第二个柱形上放置箭头。例如,如果这是一个自定义分形指标,需要搜索不同维度的分形,如 3-X-3,那么箭头可以放在索引为 3(如果分形被重绘)或 4(如果分形未被重绘)的柱形上。

对于标准指标,我们将明确指出柱形索引,因为它是已知的。对于自定义指标,我们将在类构造函数参数中指定搜索起始柱。


完善多指标类

在类方法中复制数据时,我们使用了本地临时数组。让我们将它们设为全局数组,这样就不必每次都为这些数组重新分配内存了。

在多交易品种多周期指标的 CIndMSTF 基类的私有部分中声明新数组,而在受保护部分中声明一个变量,用于存储计算当前分时指标的柱数

//+------------------------------------------------------------------+
//| Base class of the multi-symbol multi-period indicator            |
//+------------------------------------------------------------------+
class CIndMSTF : public CObject
  {
private:
   ENUM_PROGRAM_TYPE m_program;           // Program type
   ENUM_INDICATOR    m_type;              // Indicator type
   ENUM_TIMEFRAMES   m_timeframe;         // Chart timeframe
   string            m_symbol;            // Chart symbol
   int               m_handle;            // Indicator handle
   int               m_id;                // Identifier
   bool              m_success;           // Successful calculation flag
   ENUM_ERR_TYPE     m_type_err;          // Calculation error type
   string            m_description;       // Custom description of the indicator
   string            m_name;              // Indicator name
   string            m_parameters;        // Description of indicator parameters
   double            m_array_data[];      // Temporary array for copying data
   datetime          m_array_time[];      // Temporary array for copying time
   
protected:
   ENUM_IND_CATEGORY m_category;          // Indicator category
   MqlParam          m_param[];           // Array of indicator parameters
   string            m_title;             // Title (indicator name + description of parameters)
   SBuffer           m_buffers[];         // Indicator buffers
   int               m_digits;            // Digits in indicator values
   int               m_limit;             // Number of bars required to calculate the indicator on the current tick
   int               m_rates_total;       // Number of available bars for indicator calculation
   int               m_prev_calculated;   // Number of calculated bars on the previous indicator call
   uint              m_bars_to_calculate; // Number of bars required to calculate the indicator on the current tick


在类的公有部分,编写一个方法来设置计算当前分时指标所需的柱数:

//--- Returns amount of calculated data
   int               Calculated(void)                          const { return ::BarsCalculated(this.m_handle); }
//--- Set the number of bars required to calculate the indicator on the current tick
   void              SetBarsToCalculate(const int bars)
                       {
                        this.m_bars_to_calculate=bars;
                        if(this.m_array_data.Size()!=this.m_bars_to_calculate)
                          {
                           ::ArrayResize(this.m_array_data,this.m_bars_to_calculate);
                           ::ArrayResize(this.m_array_time,this.m_bars_to_calculate);
                          }
                       }
   
//--- Virtual method returning the type of object (indicator)

该方法会得到所需的柱数。新变量的值会被设置好,而数组的大小会被设置为等于该变量的值。

因此,如果成功更改了数组的大小,在数据复制方法中就不再需要每次都重新分配数组。如果无法在此更改数组的大小,在这种情况下,复制方法中的数据复制函数会尝试将数组的大小改为所需的大小。如果这也失败,则方法会显示复制错误信息。

因此,我们只需在类构造函数中或复制方法中(如果不成功)设置一次数组所需的大小。然后,我们使用设置了所需大小的数组,这样就不用不断为数组重新分配内存了。

在类的构造函数中,使用默认值调用方法复制数据(第一和零号柱)。对于绝大多数指标而言,它等于两个柱形:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Start the timer
   ::ResetLastError();
   if(!::EventSetTimer(1))
      ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError());
//--- Set properties to the values passed to the constructor or to default values
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_type=type;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   this.m_handle=INVALID_HANDLE;
   this.m_digits=::Digits();
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
//--- Set the size of the buffer structure array to the number of indicator buffers
   ::ResetLastError();
   if(::ArrayResize(this.m_buffers,buffers)!=buffers)
      ::PrintFormat("%s: Buffers ArrayResize failed. Error  %lu"__FUNCTION__,::GetLastError());
//--- Set the "empty" value for each buffer by default (can be changed it later)
   for(int i=0;i<(int)this.m_buffers.Size();i++)
      this.SetBufferInitValue(i,EMPTY_VALUE);
//--- Set initial values of variables involved in resource-efficient calculation of the indicator
   this.m_prev_calculated=0;
   this.m_limit=0;
   this.m_rates_total=::Bars(this.m_symbol,this.m_timeframe);
   this.SetBarsToCalculate(2);
//--- If the indicator is calculated on non-current chart, request data from the required chart
//--- (the first access to data starts data pumping)
   datetime array[];
   if(symbol!=::Symbol() || timeframe!=::Period())
      ::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_rates_total,array);
  }


在类的析构函数中,释放为临时数组分配的内存

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CIndMSTF::~CIndMSTF()
  {
//--- Delete timer
   ::EventKillTimer();
//--- Release handle of the indicator
   ::ResetLastError();
   if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle))
      ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError());
//--- Free up the memory of buffer arrays
   for(int i=0;i<(int)this.BuffersTotal();i++)
     {
      ::ArrayFree(this.m_buffers[i].array0);
      ::ArrayFree(this.m_buffers[i].array1);
      ::ArrayFree(this.m_buffers[i].array2);
      ::ArrayFree(this.m_buffers[i].array3);
      ::ArrayFree(this.m_buffers[i].color_indexes);
     }
//--- Free up the memory of temporary arrays
   ::ArrayFree(this.m_array_data);
   ::ArrayFree(this.m_array_time);
  }


现在,在复制指定缓冲区数组数据的方法中,使用新变量中设置的值检查正在复制的数据量。以前的硬编码值是两个柱:

//+------------------------------------------------------------------+
//| Copy data of the specified array of the specified buffer         |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArray(const uint buff_num,const uint array_num,const int to_copy,double &array[])
  {
   ::ResetLastError();
//--- Copy either the last two bars to 'array' or all available historical data from indicator's calculation part array to buffer array of indicator object
   int copied=0;
   if(to_copy==this.m_bars_to_calculate)
     {
      switch(array_num)
        {
         case 0   :  case 1 : case 2 :
         case 3   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,array);   break;
         case 4   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,array);   break;
         default  :  break;
        }
     }
   else
     {
      switch(array_num)
        {
         case 0   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array0);          break;
         case 1   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array1);          break;
         case 2   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array2);          break;
         case 3   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array3);          break;
         case 4   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].color_indexes);   break;
         default  :  break;
        }
     }
//--- If copied successfully
   if(copied>0)
      return true;
//--- If not all data is copied
//--- If CopyBuffer returned -1, this means the start of historical data downloading
//--- print a message about this to the log
   if(copied==WRONG_VALUE)
      ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
//--- In any other case, not all data has been copied yet
//--- print a message about this to the log
   else
      ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied);
   return false;
  }


在复制指定缓冲区内所有数组数据的方法中,复制两个柱形的工作以前是使用硬编码的。此外,还明确设置了缓冲数组的源索引和目标索引

//+------------------------------------------------------------------+
//| Copy data of all arrays of the specified buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy)
  {
   bool res=true;
   double array[2];
   if(to_copy==2)
     {

      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            res=this.CopyArray(buff_num,0,to_copy,array);
            if(res)
              {
               this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-1]=array[1];
               this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-2]=array[0];
              }
            return res;
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :

而现在,复制数据的数量是无法预先知道的。它被设置在一个新变量中。因此,我们将检查变量值,同时按复制 的数据量 循环复制 数据
我们将根据循环索引计算源数组和目标数组的索引。 同时,我们现在已经声明了全局临时数组,因此不再需要本地数组:

//+------------------------------------------------------------------+
//| Copy data of all arrays of the specified buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy)
  {
   bool res=true;
   if(to_copy==this.m_bars_to_calculate)

     {
      int total=(int)this.m_array_data.Size();
      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Four buffers
         case DRAW_BARS             :
         case DRAW_CANDLES          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- One buffer + color buffer
         case DRAW_COLOR_LINE       :
         case DRAW_COLOR_HISTOGRAM  :
         case DRAW_COLOR_ARROW      :
         case DRAW_COLOR_SECTION    :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Two buffers + color buffer
         case DRAW_COLOR_HISTOGRAM2 :
         case DRAW_COLOR_ZIGZAG     :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Four buffers + color buffer
         case DRAW_COLOR_BARS       :
         case DRAW_COLOR_CANDLES    :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //---DRAW_NONE
         default:
           break;
        }
     }
   else
     {
      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            return this.CopyArray(buff_num,0,to_copy,this.m_array_data);
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            return res;
         //--- Four buffers
         case DRAW_BARS             :
         case DRAW_CANDLES          :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            return res;
         //--- One buffer + color buffer
         case DRAW_COLOR_LINE       :
         case DRAW_COLOR_HISTOGRAM  :
         case DRAW_COLOR_ARROW      :
         case DRAW_COLOR_SECTION    :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //--- Two buffers + color buffer
         case DRAW_COLOR_HISTOGRAM2 :
         case DRAW_COLOR_ZIGZAG     :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //--- Four buffers + color buffer
         case DRAW_COLOR_BARS       :
         case DRAW_COLOR_CANDLES    :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //---DRAW_NONE
         default:
           break;
        }
     }
   return false;
  }


在负责当前分时操作的代码块中,使用计算部分缓冲区的数据填充对象缓冲区的方法中,复制 m_bars_to_calculate 变量中设置的柱数,而不是两个柱

//--- If calculated m_limit is less than or equal to 1, this means either opening of a new bar (m_limit==1) or current tick (m_limit==0)
//--- In this case, it is necessary to calculate the number of bars specified in the m_bars_to_calculate variable - the current one and the rest
   if(this.m_limit<=1)
     {
      //--- In a loop over the number of indicator buffers
      for(int i=0;i<total;i++)
        {
         //--- If this is the opening of a new bar and resizing the indicator buffer failed,
         if(this.m_limit==1 && !this.BufferResize(i,this.m_rates_total))
           {
            //--- add 'false' to the m_success variable and return 'false'
            //--- Here, an error message will be printed to log from the BufferResize method
            this.m_success=false;
            return false;
           }
         //--- If failed to copy bars in the amount of m_bars_to_calculate from the indicator's calculation part buffer,
         ::ResetLastError();
         if(!this.CopyArrays(i,this.m_bars_to_calculate))
           {
            //--- report this via the log, add 'false' to the m_success variable and return 'false'
            ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError());
            this.m_success &=false;
           }
        }
      //--- If the loop is followed by errors, return 'false'
      if(!this.m_success)
        {
         this.m_type_err=ERR_TYPE_NO_DATA;
         return false;
        }
      
      //--- Success
      this.m_type_err=ERR_TYPE_NO_ERROR;
      this.m_success=true;
      return true;
     }
//--- Undefined 'limit' option - return 'false'
   return false;
  }


将类缓冲区中的数据填入传递数组的方法现已完全重新设计。它考虑到了将数据从较低的时间框架复制到较高的时间框架、从较高的时间框架复制到较低的时间框架的细微差别,以及在本文开头讨论过的当前分时上复制的数据量。该方法的逻辑已被完全重写并注释:

//+------------------------------------------------------------------+
//| Fills the passed plot array with data from the class buffer      |
//+------------------------------------------------------------------+
bool CIndMSTF::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const uint array_num,const int limit,double &buffer[])
  {
//--- Set the success flag
   this.m_success=true;
//--- Get the indexing direction of the buffer array passed to the method and,
//--- if non-timeseries indexing, set timeseries indexing
   bool as_series=::ArrayGetAsSeries(buffer);
   if(!as_series)
      ::ArraySetAsSeries(buffer,true);
//--- Set the symbol name and timeframe value passed to the method
   string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to);
   ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to);

//--- If this is the first launch or history changes, initialize the buffer array passed to the method
   if(limit>1 && this.m_limit>1)
     {
      ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
      ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num));
     }
      
//--- Set the value of the loop counter (no more than the maximum number of bars in the terminal on the chart)
   int count=(limit<=1 ? (int)this.m_bars_to_calculate : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit));
//--- In a loop from the zero bar to the value of the loop counter
   for(int i=0;i<count;i++)
     {
      //--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array
      if(timeframe==::Period() && this.m_timeframe==::Period())
         buffer[i]=this.GetData(buffer_num,array_num,i);
      //--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object
      else
        {
         //--- If the chart timeframe is lower than the timeframe of the class object,
         if(timeframe<this.m_timeframe)
           {
            //--- If this is historical data (a bar with an index greater than m_bars_to_calculate-1)
            if(i>(int)this.m_bars_to_calculate-1)
              {
               //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
               ::ResetLastError();
               if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1)
                 {
                  //--- If there is no data in the terminal, move on
                  if(::GetLastError()==4401)
                     continue;
                  //--- Error in obtaining existing data - return false
                  this.m_success &=false;
                  return false;
                 }
               //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array
               int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]);
               if(bar==WRONG_VALUE)
                 {
                  this.m_success &=false;
                  continue;
                 }
               //--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer
               buffer[i]=this.GetData(buffer_num,array_num,bar);
              }
            //--- If this is the current (zero) bar or a bar with an index less than or equal to m_bars_to_calculate-1
            else
              {
               //--- Get the time of bars by symbol/timeframe of the class object in the amount of m_bars_to_calculate
               if(::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_bars_to_calculate,this.m_array_time)!=this.m_bars_to_calculate)
                 {
                  this.m_success=false;
                  return false;
                 }
               
               //--- Get the number of bars of the current chart period contained in the chart period of the class object
               int num_bars=::PeriodSeconds(this.m_timeframe)/::PeriodSeconds(timeframe);
               if(num_bars<1)
                  num_bars=1;
               
               //--- In a loop through the array of received bar times
               for(int j=0;j<(int)this.m_array_time.Size();j++)
                 {
                  //--- Get the corresponding bar on the chart by the time of the bar in the buffer array of the class object
                  int barN=::iBarShift(symbol,timeframe,this.m_array_time[j]);
                  if(barN==WRONG_VALUE)
                    {
                     this.m_success &=false;
                     return false;
                    }
                  //--- Calculate the final bar on the chart to copy data from the buffer of the calculation part of the indicator 
                  int end=barN-num_bars;
                  if(end<0)
                     end=-1;
                  //--- In a loop from barN to end, copy data from the buffer array of the calculation part to the indicator buffer being drawn on the chart 
                  for(int n=barN;n>end;n--)
                     buffer[n]=this.GetData(buffer_num,array_num,this.m_bars_to_calculate-1-j);
                 }
              }
           }
         
         //--- If the chart timeframe is higher than the timeframe of the class object,
         else if(timeframe>this.m_timeframe)
           {
            //--- Get the number of bars of the class object chart period contained in the bar of the current chart period
            int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(this.m_timeframe);
            //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
            ::ResetLastError();
            if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1)
              {
               //--- If there is no data in the terminal, move on
               if(::GetLastError()==4401)
                  continue;
               //--- Error in obtaining existing data - return false
               this.m_success &=false;
               return false;
              }
            //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array
            //--- This bar will be the first of several bars of the class object in the amount of num_bars included in the chart bar
            int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]);
            if(bar==WRONG_VALUE)
              {
               this.m_success &=false;
               continue;
              }
            //--- Calculate the index of the last bar to copy in the loop
            int end=bar-num_bars;
            if(end<0)
               end=-1;
            //--- In a loop from the first to the last bar included in the bar of the older chart period 
            for(int j=bar;j>end;j--)
              {
               //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
               ::ResetLastError();
               if(::CopyTime(this.m_symbol,this.m_timeframe,j,1,this.m_array_time)!=1)
                 {
                  //--- If there is no data in the terminal, move on
                  if(::GetLastError()==4401)
                     continue;
                  //--- Error in obtaining existing data - return false
                  this.m_success &=false;
                  return false;
                 }
               //--- Get the bar number on the chart corresponding to the time of the buffer array of the calculation part by j loop index
               int barN=::iBarShift(symbol,timeframe,this.m_array_time[0]);
               //--- Get the data of the j bar in the buffer array of the calculation part and the data of barN contained in the chart indicator buffer
               double value_data=this.GetData(buffer_num,array_num,j);
               double value_buff=buffer[barN];
               //--- If the bar on the chart has an empty value, then write data from the buffer array of the calculation part into it. Or
               //--- if there is already data on the chart, then we write new data to this bar only if the data contains a non-empty value.
               if(value_buff==this.BufferInitValue(buffer_num) || (value_buff!=this.BufferInitValue(buffer_num) && value_data!=this.BufferInitValue(buffer_num)))
                  buffer[barN]=value_data;
              }
           }
        }
     }
//--- Set initial indexing of the buffer array passed to the method
   ::ArraySetAsSeries(buffer,as_series);
//--- Successful
   return true;
  }

多交易品种多周期指标的基本对象类已准备就绪。

现在让我们最终完成后续类。

在所有标准指标中,只有比尔. 威廉姆斯的指标以箭头形式绘制,并带有断点。(抛物线 SAR也以箭头形式绘制,但在图表上没有断点)。

分形指标的绘制从索引为 2 的柱形开始。换句话说,要计算该指标,我们需要在每个分时更新三个柱形:2、1 和 0。

在指标类构造函数中,将计算柱数设为 3

//+------------------------------------------------------------------+
//| Fractals indicator class                                         |
//+------------------------------------------------------------------+
class CIndFractals : public CIndMSTF
  {
public:
//--- Constructor
   CIndFractals(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("Fractals");
      this.SetDescription("Fractals");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Description of UPPER_LINE and LOWER_LINE line buffers
      this.SetBufferDescription(UPPER_LINE,this.m_title+" Up");
      this.SetBufferDescription(LOWER_LINE,this.m_title+" Down");
      
      //--- Set drawing style for buffers 0 and 1 and the numbers of calculation part data buffers
      this.SetBufferDrawType(0,DRAW_ARROW,UPPER_LINE);
      this.SetBufferDrawType(1,DRAW_ARROW,LOWER_LINE);
      
      //--- Set default colors for buffers 0 and 1
      this.SetBufferColorToIndex(UPPER_LINE,0,clrGray);
      this.SetBufferColorToIndex(LOWER_LINE,0,clrGray);
      
      //--- Set the number of bars required to calculate the indicator on the current tick
      this.SetBarsToCalculate(3);
     }
  };

在基类中,该值默认设置为两个柱。在这里,我们将数量重新定义为三个柱,并相应地将临时数组的大小也增加到三。


在自定义指标类中,事先并不知道需要多少个柱来计算当前分时。这意味着我们不能像在分形指标类的构造函数中那样,事先指定所需的柱数。在这里,我们必须在类构造函数参数中设置这个数字,并通过输入参数进行传递和设置

//+------------------------------------------------------------------+
//| Custom indicator class                                           |
//+------------------------------------------------------------------+
class CIndCustom : public CIndMSTF
  {
public:
//--- Constructor
   CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,
              const string path,                      // path to the indicator (for example, "Examples\\MACD.ex5")
              const string name,                      // name of the custom indicator
              const uint   buffers,                   // number of indicator buffers
              const uint   bars_to_calculate,         // number of bars required to calculate the indicator on the current tick
              const MqlParam &param[]                 // array of custom indicator parameters
             ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe)
     {
      //--- If an empty array of parameters is passed, print this to log
      int total=(int)param.Size();
      if(total==0)
         ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__);
      //--- If the array is not empty and its size is increased by 1 (the first string parameter must contain the indicator name)
      ResetLastError();
      if(total>0 && ::ArrayResize(this.m_param,total+1)==total+1)
        {
         //--- Reset data in the array and enter name (path to file and name of .ex5 file)
         ::ZeroMemory(this.m_param);
         //--- name of the custom indicator
         this.m_param[0].type=TYPE_STRING;
         this.m_param[0].string_value=path;
         //--- fill the array of indicator parameters
         for(int i=0;i<total;i++)
           {
            this.m_param[i+1].type=param[i].type;
            this.m_param[i+1].double_value=param[i].double_value;
            this.m_param[i+1].integer_value=param[i].integer_value;
            this.m_param[i+1].string_value=param[i].string_value;
           }
         //--- Create description of parameters
         //--- If non-current chart symbol or period, their descriptions are added to parameters
         bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
         string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
         string param=(current ? "" : StringFormat("(%s)",symbol_period));
         //--- Write description of parameters, indicator name, its description, title and category
         this.SetParameters(param);
         this.SetName(name);
         this.SetDescription(name);
         this.m_title=this.Name()+this.Parameters();
         this.m_category=IND_CATEGORY_CUSTOM;
         //--- Write a description of the first line buffer
         this.SetBufferDescription(0,this.m_title);
         //--- Set the number of bars required to calculate the indicator on the current tick
         this.SetBarsToCalculate(bars_to_calculate);
        }
     }
  };


在创建新自定义指标对象的方法中,我们还需要在指标集合类中指定计算指标的柱数

   int               AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,             // path to the indicator (for example, "Examples\\MACD.ex5")
                                                                                      const string name,             // name of custom indicator (for example, "Custom MACD")
                                                                                      const uint   buffers,          // number of buffers
                                                                                      const uint   bars_to_calculate,// number of bars required to calculate the indicator on the current tick
                                                                                      const MqlParam &param[]);      // Array of parameters
//--- Timer

而在方法执行代码中,我们需要将该变量的值传递给自定义指标类的构造函数

//+------------------------------------------------------------------+
//| Add a custom indicator to the collection                         |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,const string name,const uint buffers,const uint bars_to_calculate,const MqlParam &param[])
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,bars_to_calculate,param);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }

现在,多交易品种多周期指标类应与箭头指标和包含缓冲区间隙数据的指标(DRAW_ARROW, DRAW_COLOR_ARROW, DRAW_SECTION, DRAW_ZIGZAG, DRAW_COLOR_SECTIONDRAW_COLOR_ZIGZAG 绘图样式的指标)一起使用。


测试

要进行测试,我可以使用之前创建的任何指标,例如抛物线 SAR,因为它也使用了箭头。它有一个绘制缓冲区,而分形指标有两个。一个缓冲区用于绘制上方部分的分形,第二个缓冲区用于绘制下方部分的分形。让我们修改指标代码,以便在两个缓冲区中绘制箭头,并将其保存为 TestMSTFFractals.mq5。

该指标将同时在图表上显示两个分形指标的数据 - 一个将绘制当前图表周期的分形,另一个将绘制根据设置中指定的交易品种和图表周期计算的分形指标的分形。此外,为了显示分形指标的数据,我们将在本系列文章的所有指标中使用数据面板

//+------------------------------------------------------------------+
//|                                             TestMSTFFractals.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   4 
//--- enums

//--- plot FractalsUp1
#property indicator_label1  "FractalsUp1"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrGray
#property indicator_width1  1

//--- plot FractalsDown1
#property indicator_label2  "FractalsDown1"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrGray
#property indicator_width2  1

//--- plot FractalsUp2
#property indicator_label3  "FractalsUp2"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrDodgerBlue
#property indicator_width3  1

//--- plot FractalsDown2
#property indicator_label4  "FractalsDown2"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrDodgerBlue
#property indicator_width4  1

//--- includes
#include <IndMSTF\IndMSTF.mqh>
#include <Dashboard\Dashboard.mqh>
//--- input parameters
input string               InpSymbol      =  NULL;             /* Symbol                     */ // Moving average symbol
input ENUM_TIMEFRAMES      InpTimeframe   =  PERIOD_CURRENT;   /* Timeframe                  */ // Moving average timeframe
input uchar                InpArrowShift1 =  10;               /* Senior period Arrow VShift */ // Shift the arrows vertically for the higher period
input uchar                InpArrowShift2 =  10;               /* Junior period Arrow VShift */ // Shift the arrows vertically for the lower period
input bool                 InpAsSeries    =  true;             /* As Series flag             */ // Timeseries flag of indicator buffer arrays

//--- indicator buffers
double         BufferFractalsUp1[];
double         BufferFractalsDn1[];
double         BufferFractalsUp2[];
double         BufferFractalsDn2[];
//--- global variables
int handle_fractals1;
int handle_fractals2;
CMSTFIndicators indicators;      // An instance of the indicator collection object
//--- variables for the panel
CDashboard *panel=NULL;          // Pointer to the panel object
int         mouse_bar_index;     // Index of the bar the data is taken from
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set a timer with an interval of 1 second
   EventSetTimer(1);
//--- Assign the BufferFractalsUp1 and BufferFractalsDn1 arrays to the plot buffers 0 and 1, respectively
   SetIndexBuffer(0,BufferFractalsUp1,INDICATOR_DATA);
   SetIndexBuffer(1,BufferFractalsDn1,INDICATOR_DATA);
//--- Assign the BufferFractalsUp2 and BufferFractalsDn2 arrays to the plot buffers 2 and 3, respectively
   SetIndexBuffer(2,BufferFractalsUp2,INDICATOR_DATA);
   SetIndexBuffer(3,BufferFractalsDn2,INDICATOR_DATA);
//--- Define the symbol code from the Wingdings font to draw in PLOT_ARROW
   PlotIndexSetInteger(0,PLOT_ARROW,217);
   PlotIndexSetInteger(1,PLOT_ARROW,218);
   PlotIndexSetInteger(2,PLOT_ARROW,217);
   PlotIndexSetInteger(3,PLOT_ARROW,218);
//--- Set the vertical shift of the arrows 
   PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-InpArrowShift1);
   PlotIndexSetInteger(1,PLOT_ARROW_SHIFT, InpArrowShift1);
   PlotIndexSetInteger(2,PLOT_ARROW_SHIFT,-InpArrowShift2);
   PlotIndexSetInteger(3,PLOT_ARROW_SHIFT, InpArrowShift2);
//--- Set the timeseries flags for the indicator buffer arrays (for testing, to see that there is no difference)
   ArraySetAsSeries(BufferFractalsUp1,InpAsSeries);
   ArraySetAsSeries(BufferFractalsDn1,InpAsSeries);
   ArraySetAsSeries(BufferFractalsUp2,InpAsSeries);
   ArraySetAsSeries(BufferFractalsDn2,InpAsSeries);
   
//--- Create two indicators of the same type
//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_fractals1=indicators.AddNewFractals(NULL,PERIOD_CURRENT);
   handle_fractals2=indicators.AddNewFractals(InpSymbol,InpTimeframe);

//--- If failed to create indicator handles, return initialization error
   if(handle_fractals1==INVALID_HANDLE || handle_fractals2==INVALID_HANDLE)
      return INIT_FAILED;
//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(0,handle_fractals1,0);
   indicators.SetPlotLabelFromBuffer(1,handle_fractals1,1);
   indicators.SetPlotLabelFromBuffer(2,handle_fractals2,0);
   indicators.SetPlotLabelFromBuffer(3,handle_fractals2,1);
      
//--- Dashboard
//--- Create the panel
   int width=311;
   panel=new CDashboard(1,20,20,width,264);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Set font parameters
   panel.SetFontParams("Calibri",9);
//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));

//--- Create a table with ID 0 to display bar data in it
   panel.CreateNewTable(0);
//--- Draw a table with ID 0 on the panel background
   panel.DrawGrid(0,2,20,6,2,18,width/2-2);

//--- Create a table with ID 1 to display the data of indicator 1
   panel.CreateNewTable(1);
//--- Get the Y2 table coordinate with ID 0 and
//--- set the Y1 coordinate for the table with ID 1
   int y1=panel.TableY2(0)+22;
//--- Draw a table with ID 1 on the panel background
   panel.DrawGrid(1,2,y1,2,2,18,width/2-2);

//--- Create a table with ID 2 to display the data of indicator 2
   panel.CreateNewTable(2);
//--- Get the Y2 coordinate of the table with ID 1 and
//--- set the Y1 coordinate for the table with ID 2
   int y2=panel.TableY2(1)+3;
//--- Draw a table with ID 2 on the background of the dashboard
   panel.DrawGrid(2,2,y2,3,2,18,width/2-2);
   
//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=0;
//--- Display the data of the current bar on the panel
   DrawData(mouse_bar_index,TimeCurrent());

//--- Successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the timer
   EventKillTimer();
//--- If the panel object exists, delete it
   if(panel!=NULL)
      delete panel;
//--- Delete all comments
   Comment("");
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//--- Number of bars for calculation
   int limit=rates_total-prev_calculated;
//--- If limit > 1, then this is the first calculation or change in the history
   if(limit>1)
     {
      //--- specify all the available history for calculation
      limit=rates_total-1;
      /*
      // If the indicator has any buffers that display other calculations (not multi-indicators),
      // initialize them here with the "empty" value set for these buffers
      */
     }
//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      return 0;

//--- Display the bar data under cursor (or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);

//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,0,0,limit,BufferFractalsUp1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,1,0,limit,BufferFractalsDn1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,0,0,limit,BufferFractalsUp2))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,1,0,limit,BufferFractalsDn2))
      return 0;

//--- return value of prev_calculated for the next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   | 
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Call the indicator collection timer
   indicators.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Handling the panel
//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Declare the variables to record time and price coordinates in them
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }

//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
//+------------------------------------------------------------------+
//| Display data from the specified timeseries index to the panel    |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Declare the variables to receive data in them
   MqlRates rates[1];

//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Set font parameters for bar and indicator data headers
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,9);

//--- Display the data of the specified bar in table 0 on the panel
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Output the data of buffer 0 of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_fractals1)+" Up", panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value10=indicators.GetData(handle_fractals1,0,0,index);
   string value_str10=(value10!=EMPTY_VALUE ? DoubleToString(value10,indicators.Digits(handle_fractals1)) : " ");
   panel.DrawText(value_str10,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,110);
   
//--- Output the data of buffer 1 of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_fractals1)+" Down", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   double value11=indicators.GetData(handle_fractals1,1,0,index);
   string value_str11=(value11!=EMPTY_VALUE ? DoubleToString(value11,indicators.Digits(handle_fractals1)) : " ");
   panel.DrawText(value_str11,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,110);
   
//--- Output the data of buffer 0 of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_fractals2)+" Up", panel.CellX(2,0,0)+2, panel.CellY(2,0,0)+2);
   double value20=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,0,0,index);
   string value_str20=(value20!=EMPTY_VALUE ? DoubleToString(value20,indicators.Digits(handle_fractals2)) : " ");
   panel.DrawText(value_str20,panel.CellX(2,0,1)+2,panel.CellY(2,0,1)+2,clrNONE,110);
   
//--- Output the data of buffer 1 of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_fractals2)+" Down", panel.CellX(2,1,0)+2, panel.CellY(2,1,0)+2);
   double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,1,0,index);
   string value_str21=(value21!=EMPTY_VALUE ? DoubleToString(value21,indicators.Digits(handle_fractals2)) : " ");
   panel.DrawText(value_str21,panel.CellX(2,1,1)+2,panel.CellY(2,1,1)+2,clrNONE,110);
   
//--- Redraw the chart to immediately display all changes on the panel
   ChartRedraw(ChartID());
  }
//+------------------------------------------------------------------+


编译指标并在 M1 图表上运行,然后将图表周期切换为 M5 和 M15:


我们可以看到,M1 图表上的指标缓冲区已被为 M5 设计的指标数据填满。

同时,当切换到 M15 时,我们可以看到为 M5 设计的指标的所有分形是如何绘制在 M15 柱形上的。同时,它们似乎不应出现在 M15 图表的每个柱形上。但事实并非如此。在 M15 图表的每个柱形中,有三个来自 M5 图表的柱形,其中包含分形。正是这些分形显示在 M15 图表的柱形上,因为这正是创建该指标的目的 - 成为一个多交易品种多周期指标。


结论

在本文中,我们使用 DRAW_ARROW 绘图样式创建并测试了多交易品种多周期指标类的操作。在随后的文章中,我们将重点介绍其余涉及图表数据分离的绘制样式。此外,我们还将完成尚未考虑的其余标准指标,以结束多交易品种多周期指标这一主题。

文章中使用的所有文件(多指标类、面板类和指标文件)都附在文章中,可供独立测试和使用。



本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14105

附加的文件 |
IndMSTF.mqh (645.11 KB)
Dashboard.mqh (217.99 KB)
神经网络变得简单(第 68 部分):离线优先引导政策优化 神经网络变得简单(第 68 部分):离线优先引导政策优化
自从第一篇专门讨论强化学习的文章以来,我们以某种方式触及了 2 个问题:探索环境和检定奖励函数。最近的文章曾专门讨论了离线学习中的探索问题。在本文中,我想向您介绍一种算法,其作者完全剔除了奖励函数。
开发多币种 EA 交易(第 1 部分):多种交易策略的协作 开发多币种 EA 交易(第 1 部分):多种交易策略的协作
交易策略是多种多样的,因此,或许可以采用几种策略并行运作,以分散风险,提高交易结果的稳定性。但是,如果每个策略都作为单独的 EA 交易来实现,那么在一个交易账户上管理它们的工作就会变得更加困难。为了解决这个问题,在一个 EA 中实现不同交易策略的操作是合理的。
MQL5 简介(第 3 部分):掌握 MQL5 的核心元素 MQL5 简介(第 3 部分):掌握 MQL5 的核心元素
在这篇便于初学者阅读的文章中,我们将为您揭开数组、自定义函数、预处理器和事件处理的神秘面纱,并对所有内容进行清晰讲解,让您可以轻松理解每一行代码,从而探索 MQL5 编程的基础知识。加入我们,用一种独特的方法释放 MQL5 的力量,确保每一步都能理解。本文为掌握 MQL5 奠定了基础,强调了对每行代码的解释,并提供了独特而丰富的学习体验。
开发回放系统(第 41 部分):启动第二阶段(二) 开发回放系统(第 41 部分):启动第二阶段(二)
如果到目前为止,你觉得一切都很好,那就说明你在开始开发应用程序时,并没有真正考虑到长远的问题。随着时间的推移,你将不再需要为新的应用程序编程,只需让它们协同工作即可。让我们看看如何完成鼠标指标的组装。