English Русский Español Deutsch Português
preview
多銘柄多期間指標のDRAW_ARROW描画タイプ

多銘柄多期間指標のDRAW_ARROW描画タイプ

MetaTrader 5 | 18 6月 2024, 10:56
86 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に位置する上部フラクタルを設定する必要があります。このフラクタルは、3つのうちの最後であるため、いずれにしてもM15で表示されます。
  • 2番目のバーM15 (II)では、バー2に位置するフラクタルを設定する必要があります。このフラクタルは、バー3の空の値をコピーしない場合にのみM15で表示されます。M15バーには、上部と下部の2つのフラクタルがあります。M5では、バー2に下部フラクタルがあり、バー3に上部フラクタルがあるためです。
  • M15の3番目のバー(III)では、バー1のトップフラクタルを設定する必要があります。このフラクタルは、バー2と3の空の値をコピーしない場合にのみM15で表示されます。

したがって、現在のチャートのバーの値を追跡し、それが空であれば、下位時間枠のバッファから任意の値をコピーする必要があります。しかし、値が空でなくなった場合は、空でない値もコピーすればよいのです。この場合、上位時間枠のチャートバーには、上位チャート期間のバーの一部である下位時間枠のバーの最後のフラクタルの値が含まれます。なぜ最初のバーではなく、最後のバーなのでしょうか。最後の値の方が現在時刻に近いので、より適切だと思います。


上位時間枠で計算された指標のデータを下位時間枠のチャートにコピーする場合、矢印はチャートのゼロまたは最初のバーではなく、例えば2本目、3本目、またはそれ以上のバーに配置できることを考慮する必要があります。現在多指標クラスでおこなわれているように、最初のバーとゼロのバーのデータだけをコピーすると、チャート上に矢印は表示されません。これらは単にインデックスが高いバーに配置されているため、指標の計算アルゴリズムでは認識できません。


ここでは、インデックス2のバー上にフラクタルが形成されていることがわかります。ゼロと最初のバーだけをコピーすれば、このフラクタルはプログラムからは見えません。

したがって、どの指標がどのバーに矢印を置くかを正確に知ることはできないので、多指標のクラスでは、検索と値のコピーがどのバーから始まるかを明示的に示す変数を導入する必要があります。現時点では、このクラスは現在のティックの最初のバーとゼロバーのみをコピーします。したがって、フラクタルは見ることができません。フラクタルはインデックス2のバーから始まります。

カスタム指標を最初の指標として使用する場合、フラクタル指標のように2本目のバーだけでなく、他のどのバーにも矢印を置くこと ができます。例えば、これがカスタムフラクタルの指標で、異なる次元のフラクタル、例えば3-X-3のフラクタルが検索される場合、矢印はインデックス3(フラクタルが再描画される場合)または4(フラクタルが再描画されない場合)のバーに配置することができます。

標準的な指標については、バーインデックスが既知であるため、明示的に表示します。カスタム指標については、クラスコンストラクタのパラメータで検索開始バーを指定できるようにします。


多指標クラスの改善

クラスメソッドでデータをコピーするときは、ローカルの一時配列を使用しました。これらの配列のために毎回メモリを再割り当てする必要がないように、グローバル配列にしましょう。

多銘柄多期間指標の基本クラス CIndMSTF のprivateセクションで、新しい配列を宣言し、protectセクションで、現在のティックで指標を計算するためのバーの数を格納するための変数を宣言します。

//+------------------------------------------------------------------+
//| 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


クラスのpublicセクションに、現在のティックで指標を計算するのに必要なバーの数を設定するメソッドを記述します。

//--- 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)

メソッドは必要な数のバーを受け取ります。新しい変数には値が設定され、配列のサイズはこの変数の値と等しく設定されます。

このため、配列のサイズが正常に変更されれば、データコピーメソッドで毎回配列を再インストールする必要はなくなります。ここで配列のサイズを変更できない場合、この場合、コピーメソッド内のデータコピー関数は、配列のサイズを必要な量に変更しようとします。これも失敗すると、メソッドはコピーのエラーメッセージを表示します。

したがって、クラスのコンストラクタか、(失敗した場合は)コピーメソッドで、配列に必要なサイズを一度設定します。そして、必要なサイズが設定された配列を使用することで、配列のために常にメモリを再割り当てする手間を省くことができます。

クラスコンストラクタで、デフォルト値を持つメソッドを呼び出し、データ(最初とゼロ)をコピーします。大半の指標では、2本のバーと等しいです。

//+------------------------------------------------------------------+
//| 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);
  }


ここで、指定されたバッファの配列のデータをコピーするメソッドで、新しい変数にセットされた値を使用してコピーされるデータ量を確認します。以前はハードコードされた2本のバーがありました。

//+------------------------------------------------------------------+
//| 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;
  }


指定されたバッファのすべての配列のデータをコピーするメソッドでは、以前は2本のバーのコピーがハードコードされていました。さらに、バッファ配列のソースと目標のインデックスも明示的に設定されました。

//+------------------------------------------------------------------+
//| 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;
  }


現在のティックに対する操作を担当するコードブロック内の計算部バッファからオブジェクトバッファにデータを充填するメソッドにおいて、2本のバーの代わりに、変数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のバーから始まります。言い換えれば、指標を計算するには、各ティックで3本のバーを更新する必要があります。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);
     }
  };

基本クラスでは、この量はデフォルトで2バーに設定されています。ここでは、量を3本のバーに再定義し、それに伴って一時的な配列の寸法も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_ARROWDRAW_COLOR_ARROWDRAW_SECTIONDRAW_ZIGZAGDRAW_COLOR_SECTIONDRAW_COLOR_ZIGZAGの描画スタイルの指標)で動作するようになりました。


検証

例えば、パラボリックSARは矢印も適用されるので、そのような指標を使用することができます。フラクタル指標が2つであるのに対して、描画されるバッファは1つです。1つのバッファは上のフラクタル図形を描くのに使用され、もう1つのバッファは下のフラクタル図形を描くのに使用されます。2つのバッファに矢印を描くように指標のコードを修正し、TestMSTFFractals.mq5という名前で保存します。

1つ目は現在のチャート期間からフラクタルを描画し、2つ目は設定で指定された銘柄とチャート期間で計算されたFractals指標からフラクタルを描画します。また、フラクタル指標のデータを表示するために、本連載ではすべての指標でデータパネルを使用します。

//+------------------------------------------------------------------+
//|                                             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チャートの3本のバーがあります。M15チャートのバー上に表示されるのは、これらのフラクタルです。この指標は、多銘柄多期間指標として作成されたためです。


結論

この記事では、DRAW_ARROW描画スタイルを持つ多銘柄多期間指標のクラスを作成し、その動作をテストしました。次回以降は、チャート上のデータの区切りを伴う残りの描画スタイルに焦点を当てます。さらに、まだ検討されていない残りの標準指標を完成させ、多銘柄多期間指標のトピックを締めくくります。

この記事で使用されているすべてのファイル(多指標クラス、パネルクラス、指標ファイル)は、独立したテストと使用のために記事に添付されています。



MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/14105

添付されたファイル |
IndMSTF.mqh (645.11 KB)
Dashboard.mqh (217.99 KB)
フリーランスサービスでトレーダーから受注して収入を得る方法 フリーランスサービスでトレーダーから受注して収入を得る方法
MQL5フリーランスは、開発者がトレーダー顧客のために取引アプリケーションを作成して報酬を得ることができるオンラインサービスです。このサービスは2010年以来成功裏に運営されており、これまでに10万件以上のプロジェクトが完了し、その総額は700万ドルに達しています。ご覧の通り、相当な額の資金が絡んでいます。
多通貨エキスパートアドバイザーの開発(第1回):複数取引戦略の連携 多通貨エキスパートアドバイザーの開発(第1回):複数取引戦略の連携
取引戦略にはさまざまなものがあります。リスクを分散し、取引結果の安定性を高めるためには、複数の戦略を並行して適用することが有効かもしれません。ただし、それぞれのストラテジーが個別のエキスパートアドバイザー(EA)として実装されている場合、1つの取引口座でそれらの作業を管理することは非常に難しくなります。この問題を解決するのに合理的なのは、1つのEAで異なる取引戦略の運用を実装することです。
時系列分類問題における因果推論 時系列分類問題における因果推論
この記事では、機械学習を用いた因果推論の理論と、Pythonによるカスタムアプローチの実装について見ていきます。因果推論と因果思考は哲学と心理学にルーツを持ち、現実を理解する上で重要な役割を果たしています。
RestAPIを統合したMQL5強化学習エージェントの開発(第3回):MQL5で自動手番とテストスクリプトを作成する RestAPIを統合したMQL5強化学習エージェントの開発(第3回):MQL5で自動手番とテストスクリプトを作成する
この記事では、MQL5関数とユニットテストを統合した、Pythonによる三目並べの自動手番の実装について説明します。目標は、MQL5でのテストを通じて、対戦のインタラクティブ性を向上させ、システムの信頼性を確保することです。このプレゼンテーションでは、対戦ロジックの開発、統合、実地テストについて説明し、最後にダイナミックな対戦環境と堅牢な統合システムを作成します。