English Русский 中文 Español Deutsch Português
DoEasyライブラリの時系列(第41部): 複数銘柄・複数期間指標の例

DoEasyライブラリの時系列(第41部): 複数銘柄・複数期間指標の例

MetaTrader 5 | 24 8月 2020, 11:11
2 022 0
Artyom Trishkin
Artyom Trishkin

内容


概念

以前の2つの記事では、指標を操作するライブラリの機能に取り組みました。特に、ライブラリ時系列の履歴データの正しいダウンロードと現在のデータのリアルタイム更新を実装しました。前の記事では、画面にデータを表示するためのデータ構造体に指標バッファを設定しました。単一の構造体は、単一の描画された指標バッファを表します。複数の描画バッファを実装する場合、各バッファは単一の構造体によって定義され、各バッファ構造体は配列に配置されます。

本稿では、構造体内の指標バッファの操作の概念をさらに洗練し、指定されたペアの1つを指定されたチャート期間でローソク足価格チャートを描画する複数銘柄・複数期間指標を作成します。さらに、指標バッファのクラスを作成する必要性が徐々に明らかになります。

ライブラリは、メッセージのクラスを備えており、ライブラリが表示するメッセージの言語を選択できるだけでなく、指定した言語のいずれかでライブラリメッセージを表示するためのカスタム言語をいくつでも簡単に追加できます。現在、入力の説明を翻訳するための言語を選択する手段はありません。コンパイル後、すべての入力の説明は、ユーザがプログラムに入力の説明を書き込むために適用した言語で表示されます。
ここでは、プログラム入力の記述に使用する言語を選択する機能を実装する際の選択の自由はあまりありません。単一の言語を使用するか、必要なコンパイル言語ごとに同様の入力セットを作成するだけです。

2番目のオプションを選択して、列挙定数の説明の2つの可能な言語(ロシア語と英語)での入力に必要な列挙を特徴とする別のファイルを作成します。したがって、ユーザは列挙定数の説明をロシア語から必要な言語に翻訳する必要があります。マーケットサービスは、製品を公開するのに英語が必要なので常に英語のままにしておく必要があります。

ライブラリクラスとデータの改善

ライブラリファイル内のデータの場所を再構成してみましょう。

OnCalculate()ハンドラからライブラリに現在のバーデータを渡すために使用される構造は、\MQL5\Include\DoEasy\Defines.mqhにあります。

//+------------------------------------------------------------------+
//| Structures                                                       |
//+------------------------------------------------------------------+
struct SDataCalculate
  {
   int         rates_total;                                 // size of input timeseries
   int         prev_calculated;                             // number of handled bars at the previous call
   int         begin;                                       // where significant data starts
   double      price;                                       // current array value for calculation
   MqlRates    rates;                                       // Price structure
  } rates_data;
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+

ただし、この構造は事前定義された変数と静的な値には適用されず、「データ」の定義に適しています。したがって、Defines.mqhから削除して、\MQL5\Include\DoEasy\Datas.mqhで定義することにします。

//+------------------------------------------------------------------+
//|                                                        Datas.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "InpDatas.mqh"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define INPUT_SEPARATOR                (",")    // Separator in the inputs string
#define TOTAL_LANG                     (2)      // Number of used languages
//+------------------------------------------------------------------+
//| Structures                                                       |
//+------------------------------------------------------------------+
struct SDataCalculate
  {
   int         rates_total;                     // size of input timeseries
   int         prev_calculated;                 // number of handled bars at the previous call
   int         begin;                           // where significant data starts
   double      price;                           // current array value for calculation
   MqlRates    rates;                           // Price structure
  } rates_data;
//+------------------------------------------------------------------+
//| Arrays                                                           |
//+------------------------------------------------------------------+
string            ArrayUsedSymbols[];           // Array of used symbols' names
ENUM_TIMEFRAMES   ArrayUsedTimeframes[];        // Array of used timeframes
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+ 
//| Data sets                                                        |
//+------------------------------------------------------------------+

プログラム入力の列挙を含む別のファイルについてはすでに説明しました。ファイルはまだ作成されていませんが、Datas.mqhファイルを再度編集しなくて済むように、そのインクルードはすでに設定されています

また、配列の新しいブロックに2つの配列を追加しました。これらの配列は、ライブラリベースのプログラムから利用できます。配列には、プログラム入力で選択された、使用されている銘柄と時間枠のリストが含まれます。

次に、プログラム入力の列挙を格納するためのファイル(\MQL5\Include\DoEasy\InpDatas.mqh)を作成します。

//+------------------------------------------------------------------+
//|                                                     InpDatas.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//#define COMPILE_EN // Comment out the string for compilation in Russian 
//+------------------------------------------------------------------+
//| Input enumerations                                               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| English language inputs                                          |
//+------------------------------------------------------------------+
#ifdef COMPILE_EN
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work only with the current symbol
   SYMBOLS_MODE_DEFINES,                              // Work with a given list of symbols
   SYMBOLS_MODE_MARKET_WATCH,                         // Working with Symbols from the "Market Watch" window
   SYMBOLS_MODE_ALL                                   // Work with a complete list of Symbols
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Work only with the current timeframe
   TIMEFRAMES_MODE_LIST,                              // Work with a given list of timeframes
   TIMEFRAMES_MODE_ALL                                // Work with a complete list of timeframes
  };
//+------------------------------------------------------------------+
//| Russian language inputs                                          |
//+------------------------------------------------------------------+
#else  
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Работа только с текущим символом 
   SYMBOLS_MODE_DEFINES,                              // Работа с заданным списком символов 
   SYMBOLS_MODE_MARKET_WATCH,                         // Работа с символами из окна "Обзор рынка" 
   SYMBOLS_MODE_ALL                                   // Работа с полным списком символов 
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Работа только с текущим таймфреймом 
   TIMEFRAMES_MODE_LIST,                              // Работа с заданным списком таймфреймов 
   TIMEFRAMES_MODE_ALL                                // Работа с полным списком таймфреймов 
  };
#endif 
//+------------------------------------------------------------------+

ここではすべてが簡単です。マクロ置換を設定します存在しない場合、コンパイルは英語の説明の列挙を使用して実行されます。マクロ置換が存在しない場合(宣言のある文字列はコメント化されています)、コンパイルは、ロシア語(または列挙定数用にユーザが設定した他の言語)で記述された定数列挙で実行されます。

必要に応じて、新しい列挙がファイルに追加されます。

すべての銘柄時系列のオブジェクトをリストに追加するクラスのメソッドは存在しないポインタにアクセスするエラーを時折引き起こすので、CTimeSeriesクラスの\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqhで修正します。

//+------------------------------------------------------------------+
//| Add the specified timeseries list to the list                    |
//+------------------------------------------------------------------+
bool CTimeSeriesDE::AddSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0)
  {
   bool res=false;
   CSeriesDE *series=new CSeriesDE(this.m_symbol,timeframe,required);
   if(series==NULL)
      return res;
   this.m_list_series.Sort();
   if(this.m_list_series.Search(series)==WRONG_VALUE)
      res=this.m_list_series.Add(series);
   if(!res)
      delete series;
   series.SetAvailable(true);
   return res;
  }
//+------------------------------------------------------------------+

オブジェクトをリストに追加するエラーが発生したら「シリーズ」オブジェクトを削除して使用フラグを設定するためにアクセスします。この場合、オブジェクトへのポインタがすでに削除されているため、エラーが発生します。

これを修正するには、コードでオブジェクトをリストに追加した結果を確認する前にフラグを設定します。

   if(this.m_list_series.Search(series)==WRONG_VALUE)
      res=this.m_list_series.Add(series);
   series.SetAvailable(true);
   if(!res)
      delete series;
   return res;
  }
//+------------------------------------------------------------------+

指定された時系列リストとすべての時系列リストを更新するメソッドでは、「新しいバー」イベントを正しいイベント時間(新しいバーの開始時間)でイベントのリストに配置できるとは限りません。時々、時間はゼロに等しくなります。

これを修正するには、時間を格納するための新しい変数を作成しますプログラムタイプが「指標」で、現在の銘柄とチャート期間で作業が実行されている場合OnCalculate()から受け取った価格の構造から変数に時間を書き込みますそれ以外の場合時系列オブジェクトのLastBarDate()メソッドによって返された値から時間を取得しますすべての銘柄時系列のすべてのオブジェクトイベントのリストにイベントを追加するときに、取得された時間を使用します

//+------------------------------------------------------------------+
//| Update a specified timeseries list                               |
//+------------------------------------------------------------------+
void CTimeSeriesDE::Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
  {
//--- Reset the timeseries event flag and clear the list of all timeseries events
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the timeseries from the list by its timeframe
   CSeriesDE *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0 || !series_obj.IsAvailable())
      return;
//--- Update the timeseries list
   series_obj.Refresh(data_calculate);
   datetime time=
     (
      this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
      data_calculate.rates.time : 
      series_obj.LastBarDate()
     );
//--- If the timeseries object features the New bar event
   if(series_obj.IsNewBar(time))
     {
      //--- send the "New bar" event to the control program chart
      series_obj.SendEvent();
      //--- set the values of the first date in history on the server and in the terminal
      this.SetTerminalServerDate();
      //--- add the "New bar" event to the list of timeseries events
      //--- in case of successful addition, set the event flag for the timeseries
      if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol()))
         this.m_is_event=true;
     }
  }
//+------------------------------------------------------------------+
//| Update all timeseries lists                                      |
//+------------------------------------------------------------------+
void CTimeSeriesDE::RefreshAll(SDataCalculate &data_calculate)
  {
//--- Reset the flags indicating the necessity to set the first date in history on the server and in the terminal
//--- and the timeseries event flag, and clear the list of all timeseries events
   bool upd=false;
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- In the loop by the list of all used timeseries,
   int total=this.m_list_series.Total();
   for(int i=0;i<total;i++) 
     {
      //--- get the next timeseries object by the loop index
      CSeriesDE *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || !series_obj.IsAvailable() || series_obj.DataTotal()==0)
         continue;
      //--- update the timeseries list
      series_obj.Refresh(data_calculate);
      datetime time=
        (
         this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
         data_calculate.rates.time : 
         series_obj.LastBarDate()
        );
      //--- If the timeseries object features the New bar event
      if(series_obj.IsNewBar(time))
        {
         //--- send the "New bar" event to the control program chart,
         series_obj.SendEvent();
         //--- set the flag indicating the necessity to set the first date in history on the server and in the terminal
         upd=true;
         //--- add the "New bar" event to the list of timeseries events
         //--- in case of successful addition, set the event flag for the timeseries
         if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol()))
            this.m_is_event=true;
        }
     }
//--- if the flag indicating the necessity to set the first date in history on the server and in the terminal is enabled,
//--- set the values of the first date in history on the server and in the terminal
   if(upd)
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

すべての時系列を更新するには、現在の銘柄と残りの時系列の更新が呼び出される場所を分離する必要があります。その他の時系列はタイマーで更新され、現在の銘柄の時系列はOnCalculate()で更新されます。これは、新しいティックの到着時にOnCalculate()で現在の銘柄時系列の更新が呼び出されるため、新しいティックを検索するタイマーで現在の銘柄時系列が過度に使用されるのを防ぐために行われます。

タイマーを使用するには、時系列コレクションクラスファイル( \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh)で、別のメソッドを宣言します

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

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

メソッドは、現在の銘柄を除くすべての時系列を更新するメソッドを呼び出します(メソッドの実装)。

//+------------------------------------------------------------------+
//| Update all timeseries except the current one                     |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::RefreshAllExceptCurrent(SDataCalculate &data_calculate)
  {
//--- Reset the flag of an event in the timeseries collection and clear the event list
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- In the loop by all symbol timeseries objects in the collection,
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next symbol timeseries object
      CTimeSeriesDE *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      //--- if the timeseries symbol is equal to the current chart symbol or
      //--- if there is no new tick on a timeseries symbol, move to the next object in the list
      if(timeseries.Symbol()==::Symbol() || !timeseries.IsNewTick())
         continue;
      //--- Update all symbol timeseries
      timeseries.RefreshAll(data_calculate);
      //--- If the event flag enabled for the symbol timeseries object,
      //--- get events from symbol timeseries, write them to the collection event list
      //--- and set the event flag in the collection
      if(timeseries.IsEvent())
         this.m_is_event=this.SetEvents(timeseries);
     }
  }
//+------------------------------------------------------------------+

ライブラリサービス関数ファイル(\MQL5\Include\DoEasy\Services\DELib.mqh)に、最初に指定されたチャート期間の1本のバーの中に2番目に指定された期間のバーの数を返す関数を追加します。

//+-------------------------------------------------------------------------+
//| Return the number of bars of one period in a single bar of another one  |
//+-------------------------------------------------------------------------+
int NumberBarsInTimeframe(ENUM_TIMEFRAMES timeframe,ENUM_TIMEFRAMES period=PERIOD_CURRENT)
  {
   return PeriodSeconds(timeframe)/PeriodSeconds(period==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)Period() : period);
  }
//+------------------------------------------------------------------+

PeriodSeconds()関数は期間の秒数を返すので、別の(より長い)期間の単一のバー内の単一の(より短い)期間のバーの数を定義するには、長い期間の秒数を短い期間の秒数で割るだけで十分です。これがまさにここで行うことです。

プログラムで使用される銘柄リストを設定できます。リストは、\MQL5\Include\DoEasy\Collections\SymbolsCollection.mqhの銘柄コレクションクラスのSetUsedSymbols()メソッドのライブラリで設定されます。プログラムで現在の銘柄なしで使用されている銘柄のリストを設定すると、ライブラリは、現在の銘柄を除く時系列コレクションの銘柄設定で指定されたすべての銘柄の時系列を作成します。しかし、データを画面上に配置するときに常にアクセスされるため、これが必要です。したがって、これを修正する必要があります。

銘柄コレクションクラスの SetUsedSymbols()メソッドで、現在の銘柄をリストに追加します。現在の銘柄が、プログラム設定でユーザが指定した作業中の銘柄のリストに存在しない場合は、リストに追加されます。すでに存在する場合は、同じ名前の新しい銘柄は追加されません。
//+------------------------------------------------------------------+
//| Set the list of used symbols                                     |
//+------------------------------------------------------------------+
bool CSymbolsCollection::SetUsedSymbols(const string &symbol_used_array[])
  {
   ::ArrayResize(this.m_array_symbols,0,1000);
   ::ArrayCopy(this.m_array_symbols,symbol_used_array);
   this.m_mode_list=this.TypeSymbolsList(this.m_array_symbols);
   this.m_list_all_symbols.Clear();
   this.m_list_all_symbols.Sort(SORT_BY_SYMBOL_INDEX_MW);
   //--- Use only the current symbol
   if(this.m_mode_list==SYMBOLS_MODE_CURRENT)
     {
      string name=::Symbol();
      ENUM_SYMBOL_STATUS status=this.SymbolStatus(name);
      return this.CreateNewSymbol(status,name,this.SymbolIndexInMW(name));
     }
   else
     {
      bool res=true;
      //--- Use the pre-defined symbol list
      if(this.m_mode_list==SYMBOLS_MODE_DEFINES)
        {
         int total=::ArraySize(this.m_array_symbols);
         for(int i=0;i<total;i++)
           {
            string name=this.m_array_symbols[i];
            ENUM_SYMBOL_STATUS status=this.SymbolStatus(name);
            bool add=this.CreateNewSymbol(status,name,this.SymbolIndexInMW(name));
            res &=add;
            if(!add) 
               continue;
           }
         //--- Create the new current symbol (if it is already in the list, it is not re-created)
         res &=this.CreateNewSymbol(this.SymbolStatus(NULL),NULL,this.SymbolIndexInMW(NULL));
         return res;
        }
      //--- Use the full list of the server symbols
      else if(this.m_mode_list==SYMBOLS_MODE_ALL)
        {
         return this.CreateSymbolsList(false);
        }
      //--- Use the symbol list from the Market Watch window
      else if(this.m_mode_list==SYMBOLS_MODE_MARKET_WATCH)
        {
         this.MarketWatchEventsControl(false);
         return true;
        }
     }
   return false;
  }
//+------------------------------------------------------------------+

CEngineライブラリのメインオブジェクトの\MQL5\Include\DoEasy\Engine.mqhで、3つのprivateメソッドを宣言します

//--- Set the list of used symbols in the symbol collection and create the collection of symbol timeseries
   bool                 SetUsedSymbols(const string &array_symbols[]);
private:
//--- Write all used symbols and timeframes to the ArrayUsedSymbols and ArrayUsedTimeframes arrays
   void                 WriteSymbolsPeriodsToArrays(void);
//--- Check the presence of a (1) symbol in the ArrayUsedSymbols array, (2) the presence of a timeframe in the ArrayUsedTimeframes array
   bool                 IsExistSymbol(const string symbol);
   bool                 IsExistTimeframe(const ENUM_TIMEFRAMES timeframe);
public:
//--- Create a resource file

Datas.mqhファイルで以前に宣言された配列に銘柄と時間枠のリストを書き込むためのメソッド、および使用されている銘柄名の配列での銘柄の存在のフラグと、使用されている時間枠の配列での時間枠の存在を返すためのメソッドが必要です。

適切な配列に銘柄と時間枠の存在フラグを返すメソッドを実装します。

//+------------------------------------------------------------------+
//| Check if a symbol is present in the array                        |
//+------------------------------------------------------------------+
bool CEngine::IsExistSymbol(const string symbol)
  {
   int total=::ArraySize(ArrayUsedSymbols);
   for(int i=0;i<total;i++)
      if(ArrayUsedSymbols[i]==symbol)
         return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Check if a timeframe is present in the array                     |
//+------------------------------------------------------------------+
bool CEngine::IsExistTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   int total=::ArraySize(ArrayUsedTimeframes);
   for(int i=0;i<total;i++)
      if(ArrayUsedTimeframes[i]==timeframe)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

ループ内の次の配列要素を適切な配列で取得し、メソッドに渡された値と比較します。次の配列要素の値がメソッドに渡されたものと一致する場合、trueを返します。ループ全体が完了した場合、配列内の要素値とメソッドに渡された要素値の間に一致は見つからなかったのでfalseを返します。

使用された銘柄と時間枠を配列に書き込むメソッドを実装します。

//+------------------------------------------------------------------+
//| Write all used symbols and timeframes                            |
//| to the ArrayUsedSymbols and ArrayUsedTimeframes arrays           |
//+------------------------------------------------------------------+
void CEngine::WriteSymbolsPeriodsToArrays(void)
  {
//--- Get the list of all created timeseries (created by the number of used symbols)
   CArrayObj *list_timeseries=this.GetListTimeSeries();
   if(list_timeseries==NULL)
      return;
//--- Get the total number of created timeseries
   int total_timeseries=list_timeseries.Total();
   if(total_timeseries==0)
      return;
//--- Set the size of the array of used symbols equal to the number of created timeseries, while
//--- the size of the array of used timeframes is set equal to the maximum possible number of timeframes in the terminal
   if(::ArrayResize(ArrayUsedSymbols,total_timeseries,1000)!=total_timeseries || ::ArrayResize(ArrayUsedTimeframes,21,21)!=21)
      return;
//--- Set both arrays to zero
   ::ZeroMemory(ArrayUsedSymbols);
   ::ZeroMemory(ArrayUsedTimeframes);
//--- Reset the number of added symbols and timeframes to zero and,
//--- in a loop by the total number of timeseries,
   int num_symbols=0,num_periods=0;
   for(int i=0;i<total_timeseries;i++)
     {
      //--- get the next object of all timeseries of a single symbol
      CTimeSeriesDE *timeseries=list_timeseries.At(i);
      if(timeseries==NULL || this.IsExistSymbol(timeseries.Symbol()))
         continue;
      //--- increase the number of used symbols and (num_symbols variable), and
      //--- write the timeseries symbol name to the array of used symbols by the num_symbols-1 index
      num_symbols++;
      ArrayUsedSymbols[num_symbols-1]=timeseries.Symbol();
      //--- Get the list of all its timeseries from the object of all symbol timeseries
      CArrayObj *list_series=timeseries.GetListSeries();
      if(list_series==NULL)
         continue;
      //--- In the loop by the total number of symbol timeseries,
      int total_series=list_series.Total();
      for(int j=0;j<total_series;j++)
        {
         //--- get the next timeseries object
         CSeriesDE *series=list_series.At(j);
         if(series==NULL || this.IsExistTimeframe(series.Timeframe()))
            continue;
         //--- increase the number of used timeframes and (num_periods variable), and
         //--- write the timeseries timeframe value to the array of used timeframes by num_periods-1 index
         num_periods++;
         ArrayUsedTimeframes[num_periods-1]=series.Timeframe();
        }
     }
//--- Upon the loop completion, change the size of both arrays to match the exact number of added symbols and timeframes
   ::ArrayResize(ArrayUsedSymbols,num_symbols,1000);
   ::ArrayResize(ArrayUsedTimeframes,num_periods,21);
  }
//+------------------------------------------------------------------+

このメソッドは、プログラムで使用される各銘柄について作成されたすべての時系列を表示し、使用された銘柄と時間枠の配列に、時系列コレクションからのデータを入力します。すべてのメソッドコードは詳細にコメントされており、簡単に理解できます。ご質問がある場合は、下のコメント欄でお気軽にお問い合わせください。

時系列更新メソッドのブロックで、現在の銘柄時系列を除くすべての時系列を更新するためのprotectedメソッドを追加します

//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of a specified symbol,
//--- (3) all timeseries of all symbols, (4) all timeseries except the current one
   void                 SeriesRefresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(symbol,timeframe,data_calculate);                          }
   void                 SeriesRefresh(const string symbol,SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(symbol,data_calculate);                                    }
   void                 SeriesRefresh(SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(data_calculate);                                           }
protected:
   void                 SeriesRefreshAllExceptCurrent(SDataCalculate &data_calculate)
                          { this.m_time_series.GetObject().RefreshAllExceptCurrent(data_calculate);               }
public  
//--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period

このようなメソッドの必要性については、以前に検討しました。ここでは、メソッドは、上記で検討したのと同じ名前の時系列コレクションクラスメソッドを呼び出すだけです。

クラスタイマーの時系列コレクションを処理するブロックで、このメソッドを呼び出し、現在の時系列を除くすべての時系列を更新します

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//
// here I have removed some code not needed for the current example
//
   //--- Timeseries collection timer
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- If the pause is over, work with the timeseries list (update all except the current one)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
     }
//--- If this is a tester, work with collection events by tick
   else
     {
//
// here I have removed some code not needed for the current example
//
     }
  }

Calculateイベントハンドラ(CEngineライブラリのメインオブジェクトのOnCalculate()メソッド内)で、 すべての時系列がまだ作成されていない場合はゼロを返し使用されているすべての時系列が完全に作成されている場合はrates_totalを返します。

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

以前は、現在のバー価格構造を介してメソッドに渡されたrates_totalがすぐに返されていましたが、時系列同期を正しく処理するには、メソッドから返された値を管理する必要があります。履歴全体の再計算を開始するためにゼロが返されますが、rates_totalはまだ計算されていないデータを計算するためにのみ使用されます(通常、これは0(現在のバーの計算)または1(新しいバーを開く瞬間の以前および現在のバーの計算)です)。

すべての使用済み銘柄のすべての時系列を作成するメソッドで、すべての使用銘柄と時系列の配列への書き込みを追加します。

//+------------------------------------------------------------------+
//| Create all applied timeseries of all used symbols                |
//+------------------------------------------------------------------+
bool CEngine::SeriesCreateAll(const string &array_periods[],const int rates_total=0,const uint required=0)
  {
//--- Set the flag of successful creation of all timeseries of all symbols
   bool res=true;
//--- Get the list of all used symbols
   CArrayObj* list_symbols=this.GetListAllUsedSymbols();
   if(list_symbols==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_GET_SYMBOLS_ARRAY));
      return false;
     }
   //--- In the loop by the total number of symbols
   for(int i=0;i<list_symbols.Total();i++)
     {
      //--- get the next symbol object
      CSymbol *symbol=list_symbols.At(i);
      if(symbol==NULL)
        {
         ::Print(DFUN,"index ",i,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
         continue;
        }
      //--- In the loop by the total number of used timeframes,
      int total_periods=::ArraySize(array_periods);
      for(int j=0;j<total_periods;j++)
        {
         //--- create the timeseries object of the next symbol.
         //--- Add the timeseries creation result to the res variable
         ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_periods[j]);
         res &=this.SeriesCreate(symbol.Name(),timeframe,rates_total,required);
        }
     }
//--- Write all used symbols and timeframes to the ArrayUsedSymbols and ArrayUsedTimeframes arrays
   this.WriteSymbolsPeriodsToArrays();
//--- Return the result of creating all timeseries for all symbols
   return res;
  }
//+------------------------------------------------------------------+

プログラムのライブラリ初期化関数からメソッドを呼び出した後、必要に応じて、プログラムで使用する2つの配列(使用されているすべての銘柄の配列と使用されているすべての時系列の配列)が準備されます。この方法は上記で検討されています。

これで、ライブラリクラスの改善は終わりです。

次に、ライブラリが複数銘柄・複数期間モードの指標でどのように機能するかを確認するためのテスト指標を作成します。
指標は、4つの使用銘柄とすべての可能な時系列を設定する機能を提供します。指標が使用する銘柄と時間枠は、ボタンを使用して選択されます。チャートには、設定で指定された銘柄名を持つ最大4つのボタンが表示されます。使用可能な時間枠を表すボタンのリストは、ボタンが押された銘柄の反対側に表示されます。
一度に押すことができるのは、1つの銘柄ボタンとその銘柄の1つの時間枠ボタンだけです。

これにより、指標が動作する銘柄とチャートの指標サブウィンドウにデータが表示される時間枠を選択できます。先走ると、手続き型でボタンの操作を実装するのは、私にとって非常に不便であることがわかりました。したがって、ボタンのステータスを管理するコードには、改善の余地がたくさんあります。とにかく、複数銘柄・複数期間指標で時系列操作をテストするには、それでも十分です。結局のところ、これは単なるテスト指標です。

テスト指標の作成

以前のテスト指標と現在開発中の指標の背後にある考え方は、ライブラリの時系列を使用して指標でその動作をテストおよび確認することだけでなく、指標バッファ構造体を実行してテストすることにもあります。構造体の使用に関する得られた知識に基づいて、一連の指標バッファクラスを配置します。ここでは、ローソク足スタイルの描画バッファにするバッファ構造体を補足します。

テスト指標を作成するには、前の記事のEAを\MQL5\Experts\TestDoEasy\Part41\TestDoEasyPart41.mq5として保存します。

まず、別のウィンドウで指標を描画するように指定し、必要なすべての指標バッファを記述し、使用される銘柄の最大数を示すマクロ置換(したがって描画される指標バッファ数)をもう1つ追加します。

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart41.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_separate_window
#property indicator_buffers 21      // 5 arrays (Open[] High[] Low[] Close[] Color[]) * 4 drawn buffers + 1 BufferTime[] calculated buffer
#property indicator_plots   4       // 1 candlesticks buffer consisting of 5 arrays (Open[] High[] Low[] Close[] Color[]) * 4 symbols
//--- plot Pair1
#property indicator_label1  "Pair 1"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrLimeGreen,clrRed,clrDarkGray
//--- plot Pair2
#property indicator_label2  "Pair 2"
#property indicator_type2   DRAW_COLOR_CANDLES
#property indicator_color2  clrDeepSkyBlue,clrFireBrick,clrDarkGray
//--- plot Pair3
#property indicator_label3  "Pair 3"
#property indicator_type3   DRAW_COLOR_CANDLES
#property indicator_color3  clrMediumPurple,clrDarkSalmon,clrGainsboro
//--- plot Pair4
#property indicator_label4  "Pair 4"
#property indicator_type4   DRAW_COLOR_CANDLES
#property indicator_color4  clrMediumAquamarine,clrMediumVioletRed,clrGainsboro

//--- classes

//--- enums

//--- defines
#define PERIODS_TOTAL   (21)              // Total amount of available chart periods
#define SYMBOLS_TOTAL   (4)               // Maximum number of drawn symbol buffers
//--- structures

指標バッファ数が21に等しいのはなぜでしょうか。
答えは簡単です。DRAW_COLOR_CANDLES描画スタイルは、それに関連付けられた5つの配列があることを意味します。

  1. 始値配列
  2. 高値配列
  3. 安値配列
  4. 終値配列
  5. カラー配列

指標では銘柄の最大数(4)が使用されるため、5つの関連付けられた配列を持つ4つの描画バッファは、20の指標バッファとなります。バーの時間を保存するには、もう1つのバッファが必要です。時間は関数に渡されます。合計は21の指標バッファで、そのうち4つが描画されます。

ローソク足のバッファ構造体を記述します。

//--- structures
struct SDataBuffer                        // Candlesticks buffer structure
  {
private:
   ENUM_TIMEFRAMES   m_buff_timeframe;    // Buffer timeframe
   string            m_buff_symbol;       // Buffer symbol
   int               m_buff_open_index;   // The index of the indicator buffer related to the Open[] array
   int               m_buff_high_index;   // The index of the indicator buffer related to the High[] array
   int               m_buff_low_index;    // The index of the indicator buffer related to the Low[] array
   int               m_buff_close_index;  // The index of the indicator buffer related to the Close[] array
   int               m_buff_color_index;  // The index of the color buffer related to the Color[] array
   int               m_buff_next_index;   // The index of the next free indicator buffer
   bool              m_used;              // The flag of using the buffer in the indicator
   bool              m_show_data;         // The flag of displaying the buffer on the chart before enabling/disabling its display
public:
   double            Open[];              // The array assigned as INDICATOR_DATA by the Open indicator buffer
   double            High[];              // The array assigned as INDICATOR_DATA by the High indicator buffer
   double            Low[];               // The array assigned as INDICATOR_DATA by the Low indicator buffer
   double            Close[];             // The array assigned as INDICATOR_DATA by the Close indicator buffer
   double            Color[];             // The array assigned as INDICATOR_COLOR_INDEX by the Color indicator buffer
//--- Set indices for the drawn OHLC and Color buffers
   void              SetIndexes(const int index_first)
                       {
                        this.m_buff_open_index=index_first;
                        this.m_buff_high_index=index_first+1;
                        this.m_buff_low_index=index_first+2;
                        this.m_buff_close_index=index_first+3;
                        this.m_buff_color_index=index_first+4;
                        this.m_buff_next_index=index_first+5;
                       }
//--- Methods of setting and returning values of the private structure members
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)   { this.m_buff_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); }
   void              SetSymbol(const string symbol)                  { this.m_buff_symbol=symbol;        }
   void              SetUsed(const bool flag)                        { this.m_used=flag;                 }
   void              SetShowDataFlag(const bool flag)                { this.m_show_data=flag;            }
   int               IndexOpenBuffer(void)                     const { return this.m_buff_open_index;    }
   int               IndexHighBuffer(void)                     const { return this.m_buff_high_index;    }
   int               IndexLowBuffer(void)                      const { return this.m_buff_low_index;     }
   int               IndexCloseBuffer(void)                    const { return this.m_buff_close_index;   }
   int               IndexColorBuffer(void)                    const { return this.m_buff_color_index;   }
   int               IndexNextBuffer(void)                     const { return this.m_buff_next_index;    }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_buff_timeframe;     }
   string            Symbol(void)                              const { return this.m_buff_symbol;        }
   bool              IsUsed(void)                              const { return this.m_used;               }
   bool              GetShowDataFlag(void)                     const { return this.m_show_data;          }
   void              Print(void);
  };
//--- Display structure data to the journal
void SDataBuffer::Print(void)
  {
   string array[8];
   array[0]="Buffer "+this.Symbol()+" "+TimeframeDescription(this.Timeframe())+":";
   array[1]=" Open buffer index: "+(string)this.IndexOpenBuffer();
   array[2]=" High buffer index: "+(string)this.IndexHighBuffer();
   array[3]=" Low buffer index: "+(string)this.IndexLowBuffer();
   array[4]=" Close buffer index: "+(string)this.IndexCloseBuffer();
   array[5]=" Color buffer index: "+(string)this.IndexColorBuffer();
   array[6]=" Next buffer index: "+(string)this.IndexNextBuffer();
   array[7]=" Used: "+(string)(bool)this.IsUsed();
   for(int i=0;i<ArraySize(array);i++)
      ::Print(array[i]);
  }
//--- input variables

この構造体には、適切なOHLCおよびカラー配列に関連するバッファインデックスの値を格納するための変数があります。必要なバッファにインデックスでいつでもアクセスできます。新しい指標バッファを構造体配列にバインドするための次の空きインデックスは、IndexNextBuffer()メソッドを使用してm_buff_next_index変数から常に取得でき、現在の構造体でカラーバッファに続くインデックスを返します。

コードを見ると、この構造体には、構造体のprivateセクションで定義されたすべての値を設定および返すためのすべてのメソッドと、操作ログにすべての構造データを印刷するメソッドがあります。OHLCおよびカラーバッファインデックスデータ、バインド用の次の空きインデックス新しい配列、バッファデータを使用するフラグを使用して、配列を生成します。次に、このすべてのループデータが配列から操作ログに渡されます。

例えば、このメソッドは、指標設定で設定された4つの描画バッファのデータを操作ログに送信するために使用されます(チャートにはAUDUSDバッファが表示されます)。

2020.04.08 21:55:21.528 Buffer EURUSD H1:
2020.04.08 21:55:21.528  Open buffer index: 0
2020.04.08 21:55:21.528  High buffer index: 1
2020.04.08 21:55:21.528  Low buffer index: 2
2020.04.08 21:55:21.528  Close buffer index: 3
2020.04.08 21:55:21.528  Color buffer index: 4
2020.04.08 21:55:21.528  Next buffer index: 5
2020.04.08 21:55:21.528  Used: false
2020.04.08 21:55:21.530 Buffer AUDUSD H1:
2020.04.08 21:55:21.530  Open buffer index: 5
2020.04.08 21:55:21.530  High buffer index: 6
2020.04.08 21:55:21.530  Low buffer index: 7
2020.04.08 21:55:21.530  Close buffer index: 8
2020.04.08 21:55:21.530  Color buffer index: 9
2020.04.08 21:55:21.530  Next buffer index: 10
2020.04.08 21:55:21.530  Used: true
2020.04.08 21:55:21.532 Buffer EURAUD H1:
2020.04.08 21:55:21.532  Open buffer index: 10
2020.04.08 21:55:21.532  High buffer index: 11
2020.04.08 21:55:21.532  Low buffer index: 12
2020.04.08 21:55:21.532  Close buffer index: 13
2020.04.08 21:55:21.532  Color buffer index: 14
2020.04.08 21:55:21.532  Next buffer index: 15
2020.04.08 21:55:21.532  Used: false
2020.04.08 21:55:21.533 Buffer EURGBP H1:
2020.04.08 21:55:21.533  Open buffer index: 15
2020.04.08 21:55:21.533  High buffer index: 16
2020.04.08 21:55:21.533  Low buffer index: 17
2020.04.08 21:55:21.533  Close buffer index: 18
2020.04.08 21:55:21.533  Color buffer index: 19
2020.04.08 21:55:21.533  Next buffer index: 20
2020.04.08 21:55:21.533  Used: false

ご覧のように、後続の各ローソク足バッファの始値バッファインデックスは、前のローソク足バッファの「次のバッファインデックス」インデックスと一致します。次の空きバッファのインデックスは20です。このインデックスは、計算された指標バッファなど、次のインデックスを割り当てるために使用できます。これは、現在のチャートのバー時間を保存する計算済みバッファに対して行われることとまったく同じです。

指標入力のブロックを追加します。

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

Defines.mqhでは、銘柄を使用するモードを選択するためのENUM_SYMBOLS_MODE列挙型には、2つの不要なモード(「気配値表示ウィンドウ銘柄の操作」と「銘柄の完全なリストの操作」)があります。

//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work with the current symbol only 
   SYMBOLS_MODE_DEFINES,                              // Work with the specified symbol list 
   SYMBOLS_MODE_MARKET_WATCH,                         // Work with the Market Watch window symbols 
   SYMBOLS_MODE_ALL                                   // Work with the full symbol list 
  };
//+------------------------------------------------------------------+

...次に、設定で2つのモードを選択しないようにするには、sinput修飾子をコメント化して 、InpModeUsedSymbols変数をexternalでなくします。これで、指標内の銘柄を操作するモードは、常に「指定された銘柄リストを操作」と等しくなり、 InpUsedSymbols入力で指定されたリストの最初の4つの銘柄が使用されます。

指標バッファの定義とグローバル変数のブロックを書きましょう。

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

ローソク足のバッファ構造の配列を描画バッファとして宣言しました。これは、4つの同様のバッファを定義するよりもはるかに便利です。さらに、ボタンと一致する配列内の位置のインデックスで各バッファにアクセスする方がはるかに便利です。最初のボタンでバッファを選択する必要がある場合は、配列内の最初のバッファを選択する、最後のボタンに割り当てられたバッファを選択する必要がある場合は、配列内の最後のバッファを選択する、などの必要があります。

指標のOnCalculate()ハンドラの事前定義されたtime[]配列から指標関数でバー時間を渡すには、計算バッファの1つ(時間バッファ)が必要です。

ほぼすべての記事で、テストEAと指標で使用しているグローバル変数を含むブロックはすでにお馴染みです。すべての変数にはコメントが付いているため、ここでそれらを徹底的に分析しても意味がありません。
指標の計算に必要なバーの最小数は、バーが時系列の計算に十分であるかどうかを定義して、より長い時間枠の指標データがより短い時間枠に正しく表示されるようにします。

たとえば、М15の場合、表示データはН1チャートから取得されますが、1つの1時間のバーには4つの15分バーが含まれているため、すべてのバーを正しく表示するには少なくとも4つのバーが必要です。

適用される期間に応じて、現在のチャートバーの必要な数の計算は、以前に考慮された、ライブラリサービス関数のDELib.mqhファイルに含まれている、NumberBarsInTimeframe()関数によって実行されます。

指標を手続き型のスタイルで作成するときに直面した困難については既に説明しました。ボタンとバッファの状態を検索、設定、および監視するための追加の補助関数を作成する必要がありました。ボタンとバッファがオブジェクトとして記述されれば、それらのプロパティへのアクセスとモードの設定が大幅に簡略化されます。しかし、まだ変更はしません。手続き型のテストを導入する方が早いようです。さらに、テスト指標には一時オブジェクトは必要ありません。それらは後で役に立たなくなります。

新しく開発された補助関数について考えてみましょう。

以下は、描画された指標バッファの状態を設定する関数です。

//+------------------------------------------------------------------+
//| Set the state for drawn buffers                                  |
//+------------------------------------------------------------------+
void SetPlotBufferState(const int buffer_index,const bool state)
  {
//--- Depending on a passed status, define whether data should be displayed in the data window (state==true) or not (state==false)
   PlotIndexSetInteger(buffer_index,PLOT_SHOW_DATA,state);
//--- Create the buffer description consisting of a symbol and timeframe and set the buffer description by its buffer_index index
   string params=Buffers[buffer_index].Symbol()+" "+TimeframeDescription(Buffers[buffer_index].Timeframe());
   string label=params+" Open;"+params+" High;"+params+" Low;"+params+" Close";
   PlotIndexSetString(buffer_index,PLOT_LABEL,(state ? label : NULL));
//--- If the buffer is active (drawn), set a short name for the indicator with the displayed symbol and timeframe
   if(state)
      IndicatorSetString(INDICATOR_SHORTNAME,engine.Name()+" "+Buffers[buffer_index].Symbol()+" "+TimeframeDescription(Buffers[buffer_index].Timeframe()));
  }  
//+------------------------------------------------------------------+

この関数は、ステータスを設定する必要があるバッファのインデックスを渡します。ステータスは入力によっても渡されます。

いくつかの関連する配列を表示する必要がある指標のバッファを操作するときは注意が必要です。

たとえば、指標バッファに2つの配列が必要な場合、バッファに関連する配列のインデックスは0と1に等しくなります。これらの値はSetIndexBuffer()関数で設定されます。2つのデータ配列を使用して1つの描画バッファを適用しても、描画バッファへのアクセスを理解するのに問題はありません。プロパティにアクセスするには、インデックス0のバッファを指定するだけです。

ただし、2つの配列を使用して2つ以上の描画バッファが必要な場合は、2番目、3番目、および後続の描画バッファにアクセスするために使用するインデックスについて誤解する可能性があります。

それぞれ2つの配列を持つ3つの描画バッファの例と、描画バッファとその配列のインデックスの数を考えてみましょう。
  • 描画バッファ1 — 描画バッファ0インデックス
    • 配列1 — バッファ0インデックス
    • 配列2 — バッファ1インデックス
  • 描画バッファ2 — 描画バッファ1インデックス
    • 配列1 — バッファ2インデックス
    • 配列2 — バッファ3インデックス
  • 描画バッファ3 — 描画バッファ2インデックス
    • 配列1 — バッファ4インデックス
    • 配列2 — バッファ5インデックス

3つの描画バッファには6つの配列があるように見える場合があります。2番目の描画配列にアクセスするには、インデックス2にアクセスする必要があります(0と1は最初のバッファ配列によって占有されているため)。しかし、これはそうではありません。2番目の描画バッファにアクセスするには、各描画バッファのバッファ(インデックス1)によって割り当てられたすべての配列にアクセスするのではなく、描画バッファのインデックスにアクセスする必要があります。

したがって、SetIndexBuffer()関数を使用して配列をバッファにバインドするには、指標バッファとして使用するすべての配列のシリアル番号を指定する必要があります。ただし、PlotIndexGetInteger()関数を使用して描画バッファからデータを取得するか、PlotIndexSetDouble()を使用して描画バッファのデータを設定するには、PlotIndexSetInteger()およびPlotIndexSetString()関数は、配列番号ではなく、各描画バッファのインデックスを設定します。現在の例では、インデックスは、最初に描画されたバッファの場合は0、2番目の場合は1、3番目の場合は2です。これは考慮に入れられるべきです。

以下は、設定で指定された銘柄を使用するフラグを返す関数です。

//+------------------------------------------------------------------+
//| Return the flag of using a symbol specified in the settings      |
//+------------------------------------------------------------------+
bool IsUsedSymbolByInput(const string symbol)
  {
   int total=ArraySize(array_used_symbols);
   for(int i=0;i<total;i++)
      if(array_used_symbols[i]==symbol)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

銘柄が使用されている銘柄の配列に存在する場合、関数はtrueを返し、それ以外の場合はfalseを返します。時々、使用された銘柄のリストで現在の銘柄を指定しない場合がありますが、それは常に存在します。そのデータは内部ライブラリ計算を実行するために必要です。この関数は、現在の銘柄が設定で指定されておらず、スキップする必要があることを示すフラグを返します。

以下は、銘柄によって描画バッファのインデックスを返す関数です。

//+------------------------------------------------------------------+
//| Return the structure drawn buffer index by symbol                |
//+------------------------------------------------------------------+
int IndexBuffer(const string symbol)
  {
   int total=ArraySize(Buffers);
   for(int i=0;i<total;i++)
      if(Buffers[i].Symbol()==symbol)
         return i;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

関数は銘柄名を受け取り、そのバッファインデックスが返されます。すべてのバッファによるループで、銘柄のあるバッファを検索し、一致した場合はループインデックスを返します。そのような銘柄のバッファがない場合は、-1を返します。

以下は、次の描画指標バッファを割り当てることができる最初の空きインデックスの番号を返す関数です。

//+------------------------------------------------------------------+
//| Return the first free index of the drawn buffer                  |
//+------------------------------------------------------------------+
int FirstFreePlotBufferIndex(void)
  {
   int num=WRONG_VALUE,total=ArraySize(Buffers);
   for(int i=0;i<total;i++)
      if(Buffers[i].IndexNextBuffer()>num)
         num=Buffers[i].IndexNextBuffer();
   return num;
  }
//+------------------------------------------------------------------+

バッファ構造体配列から描画されたすべてのバッファによるループで、次の空きバッファの値を確認します。
以前の値を超えている場合は、新しい値を覚えておきます。ループが完了したら、num変数から書き込まれた値を返します。

以下は、ボタンとバッファの状態をインストールおよび検索するための補助アクションを実行するために作成された関数です。

//+------------------------------------------------------------------+
//| Return the button status                                         |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Set the button status                                            |
//+------------------------------------------------------------------+
void SetButtonState(const string button_name,const bool state)
  {
//--- Set the button status and its color depending on the status
   ObjectSetInteger(0,button_name,OBJPROP_STATE,state);
   if(state)
      ObjectSetInteger(0,button_name,OBJPROP_BGCOLOR,C'220,255,240');
   else
      ObjectSetInteger(0,button_name,OBJPROP_BGCOLOR,C'240,240,240');
//--- If not in the tester, 
//--- set the status to the terminal global variable
   if(!engine.IsTester())
      GlobalVariableSet((string)ChartID()+"_"+button_name,state);
  }
//+------------------------------------------------------------------+
//| Set the symbol button status                                     |
//+------------------------------------------------------------------+
void SetButtonSymbolState(const string button_symbol_name,const bool state)
  {
//--- Set the symbol button status
   SetButtonState(button_symbol_name,state);
//--- Detect wrong names if the timeframe button status is not specified
//--- Write the button status to the global variable only if its name contains no "PERIOD_CURRENT" substring
   if(StringFind(button_symbol_name,"PERIOD_CURRENT")==WRONG_VALUE)
      GlobalVariableSet((string)ChartID()+"_"+button_symbol_name,state);
//--- Set the visibility for all period buttons corresponding to the symbol button
   SetButtonPeriodVisible(button_symbol_name,state);
  }
//+------------------------------------------------------------------+
//| Set the period button status                                     |
//+------------------------------------------------------------------+
void SetButtonPeriodState(const string button_period_name,const bool state)
  {
//--- Set the button status and write it to the terminal global variable
   SetButtonState(button_period_name,state);
   GlobalVariableSet((string)ChartID()+"_"+button_period_name,state);
  }
//+------------------------------------------------------------------+
//| Set the "visibility" of period buttons for the symbol button     |
//+------------------------------------------------------------------+
void SetButtonPeriodVisible(const string button_symbol_name,const bool state_symbol)
  {
//--- In the loop by the amount of used timeframes
   int total=ArraySize(array_used_periods);
   for(int j=0;j<total;j++)
     {
      //--- create the name of the next period button
      string butt_name_period=button_symbol_name+"_"+EnumToString(ArrayUsedTimeframes[j]);
      //--- Set the status and visibility for the period button depending on the symbol button status
      ObjectSetInteger(0,butt_name_period,OBJPROP_TIMEFRAMES,(engine.IsTester() ? OBJ_ALL_PERIODS : (state_symbol ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS)));
     }   
  }
//+------------------------------------------------------------------+
//| Reset the states of the remaining symbol buttons                 |
//+------------------------------------------------------------------+
void ResetButtonSymbolState(const string button_symbol_name)
  {
//--- In the loop by all chart objects,
   for(int i=ObjectsTotal(0,0)-1;i>WRONG_VALUE;i--)
     {
      //--- get the name of the next object
      string name=ObjectName(0,i,0);
      //--- If this is a pressed button, or the object does not belong to the indicator, or this is a period button, move on to the next one
      if(name==button_symbol_name || StringFind(name,prefix)==WRONG_VALUE || StringFind(name,"_PERIOD_")>0)
         continue;
      //--- Reset the symbol button status by object name
      SetButtonSymbolState(name,false);
     }
  }
//+------------------------------------------------------------------+
//| Reset the states of the remaining symbol period buttons          |
//+------------------------------------------------------------------+
void ResetButtonPeriodState(const string button_period_name,const string symbol)
  {
//--- In the loop by all chart objects,
   for(int i=ObjectsTotal(0,0)-1;i>WRONG_VALUE;i--)
     {
      //--- get the name of the next object
      string name=ObjectName(0,i,0);
      //--- If this is a pressed button, or the object does not belong to the indicator, or this is not a period button, or the button does not belong to the symbol, move on to the next one
      if(name==button_period_name || StringFind(name,prefix)==WRONG_VALUE || StringFind(name,"_PERIOD_")==WRONG_VALUE || StringFind(name,symbol)==WRONG_VALUE)
         continue;
      //--- Reset the period button status by object name
      SetButtonPeriodState(name,false);
     }
  }
//+---------------------------------------------------------------------------+
//| Return the name of the pressed period button corresponding to the symbol  |
//+---------------------------------------------------------------------------+
string GetNamePressedTimeframe(const string button_symbol_name,const string symbol)
  {
//--- In the loop by all chart objects,
   for(int i=ObjectsTotal(0,0)-1;i>WRONG_VALUE;i--)
     {
      //--- get the name of the next object
      string name=ObjectName(0,i,0);
      //--- If this is a pressed button, or the object does not belong to the indicator, or this is not a period button, or the button does not belong to the symbol, move on to the next one
      if(name==button_symbol_name || StringFind(name,prefix)==WRONG_VALUE || StringFind(name,"_PERIOD_")==WRONG_VALUE || StringFind(name,symbol)==WRONG_VALUE)
         continue;
      //--- If the button is pressed, return the name of the pressed button graphic object
      if(ButtonState(name))
         return name;
     }
//--- Return NULL if no symbol period buttons are pressed
   return NULL;
  }
//+------------------------------------------------------------------+
//| Set the buffer states, 'true' - only for the specified one       |
//+------------------------------------------------------------------+
void SetAllBuffersState(const string symbol)
  {
   int total=ArraySize(Buffers);
//--- Get the specified buffer index
   int index=IndexBuffer(symbol);
//--- In a loop by the number of drawn buffers
   for(int i=0;i<total;i++)
     {
      //--- if the loop index is equal to the specified buffer index,
      //--- set the flag of its usage to 'true', otherwise - to 'false'
      //--- forcibly set the flag indicating that pressing the button for the buffer has already been handled
      Buffers[i].SetUsed(i!=index ? false : true);
      Buffers[i].SetShowDataFlag(false);
     }
  }
//+------------------------------------------------------------------+

1つの銘柄ボタンと、銘柄ボタンに対応する1つの期間ボタンしか押すことができないため、ボタンの押下を処理する関数がわずかに変更されました。

//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
//--- Convert button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
      //--- Get the index of the drawn buffer by timeframe, its symbol and index
   int index=StringFind(button,"_PERIOD_");
   string symbol=StringSubstr(button,5,index-5);
   int buffer_index=IndexBuffer(symbol);
//--- Create the button name for the terminal's global variable
   string name_gv=(string)ChartID()+"_"+prefix+button;
//--- Get the button status (pressed/released). If not in the tester,
//--- write the status to the button global variable (1 or 0)
   bool state=ButtonState(button_name);
   if(!engine.IsTester())
      GlobalVariableSet(name_gv,state);
//--- Set the button color depending on its status, 
//--- write its status to the buffer structure depending on the button status (used/not used)
//--- initialize the buffer corresponding to the button timeframe by the buffer index received earlier
   if(StringFind(button_name,"_PERIOD_")==WRONG_VALUE)
     {
      SetButtonSymbolState(button_name,state);
      ResetButtonSymbolState(button_name);
     }
   else
     {
      SetButtonPeriodState(button_name,state);
      ResetButtonPeriodState(button_name,symbol);
     }
//--- Get the timeframe from the pressed symbol timeframe button
   string pressed_period=GetNamePressedTimeframe(button_name,symbol);
   ENUM_TIMEFRAMES timeframe=
     (
      StringFind(button,"_PERIOD_")==WRONG_VALUE ? 
      TimeframeByDescription(StringSubstr(pressed_period,StringFind(pressed_period,"_PERIOD_")+8)) :
      TimeframeByDescription(StringSubstr(button,StringFind(button,"_PERIOD_")+8))
     );
//--- Set the states of all buffers, 'true' - for the pressed button symbol buffer, the rest are 'false'
   SetAllBuffersState(symbol);
//--- Set the displayed timeframe for the buffer
   Buffers[buffer_index].SetTimeframe(timeframe);
//--- If the button pressing is not handled yet
   if(Buffers[buffer_index].GetShowDataFlag()!=state)
     {
      //--- Initialize all indicator buffers
      InitBuffersAll();
      //--- If the buffer is active, fill it with historical data
      if(state)
         BufferFill(buffer_index);
      //--- Set the flag indicating that pressing the button has already been handled
      Buffers[buffer_index].SetShowDataFlag(state);
     }

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

以下は、ボタンパネルを作成する関数です。

//+------------------------------------------------------------------+
//| Create the buttons panel                                         |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=20,const int shift_y=0)
  {
   int total_symbols=ArraySize(array_used_symbols);
   int total_periods=ArraySize(ArrayUsedTimeframes);
   uint ws=48,hs=18,w=26,h=16,shift_h=2,x=InpButtShiftX+1, y=InpButtShiftY+h+1;
   //--- In the loop by the number of used symbols,
   for(int i=0;i<SYMBOLS_TOTAL;i++)
     {
      //--- create the name of the next symbol button
      string butt_name_symbol=prefix+"BUTT_"+array_used_symbols[i];
      //--- create the next symbol button with a shift calculated as
      //--- ((button height + 2) * loop index)
      uint ys=y+(hs+shift_h)*i;
      if(ButtonCreate(butt_name_symbol,x,ys,ws,hs,array_used_symbols[i],clrGray))
        {
         bool state_symbol=(engine.IsTester() && i==0 ? true : false);
         //--- If not in the tester,
         if(!engine.IsTester())
           {
            //--- set the name of the terminal global variable for storing the symbol button status
            string name_gv_symbol=(string)ChartID()+"_"+butt_name_symbol;
            //--- if there is no global variable with the symbol name, create it set to 'false',
            if(!GlobalVariableCheck(name_gv_symbol))
               GlobalVariableSet(name_gv_symbol,false);
            //--- get the symbol button status from the terminal global variable
            state_symbol=GlobalVariableGet(name_gv_symbol);
           }
         //--- Set the status for the symbol button
         SetButtonState(butt_name_symbol,state_symbol);
         
         //--- In the loop by the amount of used timeframes
         for(int j=0;j<total_periods;j++)
           {
            //--- create the name of the next period button
            string butt_name_period=butt_name_symbol+"_"+EnumToString(ArrayUsedTimeframes[j]);
            uint yp=ys-(hs-h)/2;
            //--- create the next period button with a shift calculated as
            //--- (symbol button width + (period button width + 1) * loop index)
            if(ButtonCreate(butt_name_period,x+ws+2+(w+1)*j,yp,w,h,TimeframeDescription(ArrayUsedTimeframes[j]),clrGray))
               ObjectSetInteger(0,butt_name_period,OBJPROP_TIMEFRAMES,(engine.IsTester() ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS));
            else
              {
               Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_name_period,"\"");
               return false;
              }
            bool state_period=(engine.IsTester() && ArrayUsedTimeframes[j]==Period() ? true : false);
            //--- If not in the tester,
            if(!engine.IsTester())
              {
               //--- set the name of the terminal global variable for storing the period button status
               string name_gv_period=(string)ChartID()+"_"+butt_name_period;
               //--- if there is no global variable with the period name, create it set to 'false',
               if(!GlobalVariableCheck(name_gv_period))
                  GlobalVariableSet(name_gv_period,false);
               //--- get the period button status from the terminal global variable
               state_period=GlobalVariableGet(name_gv_period);
              }
            //--- Set the status and visibility for the period button depending on the symbol button status
            SetButtonState(butt_name_period,state_period);
            ObjectSetInteger(0,butt_name_period,OBJPROP_TIMEFRAMES,(engine.IsTester() ? OBJ_ALL_PERIODS : (state_symbol ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS)));
           }   
        }
      else
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_name_symbol,"\"");
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+

以下は、描画指標バッファを初期化する関数です。

//+------------------------------------------------------------------+
//| Initialize the timeseries and the appropriate buffers by index   |
//+------------------------------------------------------------------+
bool InitBuffer(const int buffer_index)
  {
//--- Leave if the wrong index is passed
   if(buffer_index==WRONG_VALUE)
      return false;
//--- Initialize drawn OHLC buffers using the "empty" value, while Color is initialized using zero
   ArrayInitialize(Buffers[buffer_index].Open,EMPTY_VALUE);
   ArrayInitialize(Buffers[buffer_index].High,EMPTY_VALUE);
   ArrayInitialize(Buffers[buffer_index].Low,EMPTY_VALUE);
   ArrayInitialize(Buffers[buffer_index].Close,EMPTY_VALUE);
   ArrayInitialize(Buffers[buffer_index].Color,0);
//--- Set the flag of the buffer display in the data window by index
   SetPlotBufferState(buffer_index,Buffers[buffer_index].IsUsed());
   return true;
  }
//+------------------------------------------------------------------+
//| Initialize all timeseries and the appropriate buffers            |
//+------------------------------------------------------------------+
void InitBuffersAll(void)
  {
//--- Initialize the next buffer in the loop by the total number of chart periods
   int total=ArraySize(Buffers);
   for(int i=0;i<total;i++)
      InitBuffer(i);
  }
//+------------------------------------------------------------------+

以下は、すべてのアクティブな指標バッファの単一のバーを計算する関数です。

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

以下は、渡されたバーオブジェクトの値を指定された描画バッファに書き込む関数です。

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


次に、ボタンが作成され、すべての指標バッファが準備される指標のOnInit()ハンドラについて考えてみましょう。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialize DoEasy library
   OnInitDoEasy();

//--- Set indicator global variables
   prefix=engine.Name()+"_";
   //--- Get the index of the maximum used timeframe in the array,
   //--- calculate the number of bars of the current period fitting in the maximum used period
   //--- Use the obtained value if it exceeds 2, otherwise use 2
   int index=ArrayMaximum(ArrayUsedTimeframes);
   int num_bars=NumberBarsInTimeframe(ArrayUsedTimeframes[index]);
   min_bars=(index>WRONG_VALUE ? (num_bars>2 ? num_bars : 2) : 2);
//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

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

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

//--- indicator buffers mapping
   //--- In the loop by the total number of all symbols
   int total_symbols=ArraySize(array_used_symbols);
   for(int i=0;i<SYMBOLS_TOTAL;i++)
     {
      //--- get the next symbol
      //--- if the loop index is less than the size of used symbols array, the symbol name is taken from the array,
      //--- otherwise, this is an empty (unused) buffer, and the buffer symbol name is "EMPTY "+loop index
      string symbol=(i<total_symbols ? array_used_symbols[i] : "EMPTY "+string(i+1));
      //--- Increase the size of the buffer structures array, set the buffer symbol,
      ArrayResize(Buffers,ArraySize(Buffers)+1,SYMBOLS_TOTAL);
      Buffers[i].SetSymbol(symbol);
      //--- set the values of all indices for binding the indicator buffers with the structure arrays and
      //--- specify the next buffer index
      int index_first=(i==0 ? i : Buffers[i-1].IndexNextBuffer());
      Buffers[i].SetIndexes(index_first);
      
      //--- Setting the drawn buffer according to the button status
      //--- Set the symbol button status. The first button is active in the tester
      bool state_symbol=(engine.IsTester() && i==0 ? true : false);
      //--- Set the name of the symbol button corresponding to the buffer with the loop index and its timeframe
      string name_butt_symbol=prefix+"BUTT_"+Buffers[i].Symbol();
      string name_butt_period=name_butt_symbol+"_PERIOD_"+TimeframeDescription(Buffers[i].Timeframe());
      //--- If not in the tester, while the chart features the button with the specified name,
      if(!engine.IsTester() && ObjectFind(ChartID(),name_butt_symbol)==0)
        {
         //--- set the name of the terminal global variable for storing the button status
         string name_gv_symbol=(string)ChartID()+"_"+name_butt_symbol;
         string name_gv_period=(string)ChartID()+"_"+name_butt_period;
         //--- get the symbol button status from the terminal global variable
         state_symbol=GlobalVariableGet(name_gv_symbol);
        }
      
      //--- Get the timeframe from pressed symbol timeframe buttons
      string pressed_period=GetNamePressedTimeframe(name_butt_symbol,symbol);
      //--- Convert button name into its string ID
      string button=StringSubstr(name_butt_symbol,StringLen(prefix));
      ENUM_TIMEFRAMES timeframe=
        (
         StringFind(button,"_PERIOD_")==WRONG_VALUE ? 
         TimeframeByDescription(StringSubstr(pressed_period,StringFind(pressed_period,"_PERIOD_")+8)) :
         TimeframeByDescription(StringSubstr(button,StringFind(button,"_PERIOD_")+8))
        );
      
      //--- Set the values for all structure fields
      Buffers[i].SetTimeframe(timeframe);
      Buffers[i].SetUsed(state_symbol);
      Buffers[i].SetShowDataFlag(state_symbol);
      
      //--- Bind drawn indicator buffers by the buffer index equal to the loop index with the structure bar price arrays (OHLC)
      SetIndexBuffer(Buffers[i].IndexOpenBuffer(),Buffers[i].Open,INDICATOR_DATA);
      SetIndexBuffer(Buffers[i].IndexHighBuffer(),Buffers[i].High,INDICATOR_DATA);
      SetIndexBuffer(Buffers[i].IndexLowBuffer(),Buffers[i].Low,INDICATOR_DATA);
      SetIndexBuffer(Buffers[i].IndexCloseBuffer(),Buffers[i].Close,INDICATOR_DATA);
      //--- Bind the color buffer by the buffer index equal to the loop index with the corresponding structure arrays
      SetIndexBuffer(Buffers[i].IndexColorBuffer(),Buffers[i].Color,INDICATOR_COLOR_INDEX);
      //--- set the "empty value" for all structure buffers, 
      PlotIndexSetDouble(Buffers[i].IndexOpenBuffer(),PLOT_EMPTY_VALUE,EMPTY_VALUE);
      PlotIndexSetDouble(Buffers[i].IndexHighBuffer(),PLOT_EMPTY_VALUE,EMPTY_VALUE);
      PlotIndexSetDouble(Buffers[i].IndexLowBuffer(),PLOT_EMPTY_VALUE,EMPTY_VALUE);
      PlotIndexSetDouble(Buffers[i].IndexCloseBuffer(),PLOT_EMPTY_VALUE,EMPTY_VALUE);
      PlotIndexSetDouble(Buffers[i].IndexColorBuffer(),PLOT_EMPTY_VALUE,0);
      //--- set the drawing type
      PlotIndexSetInteger(i,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES);
      //--- Depending on the button status, set the graphical series name
      //--- and specify whether the buffer data in the data window is displayed or not
      SetPlotBufferState(i,state_symbol);
      //--- set the direction of indexing of all structure buffers as in the timeseries
      ArraySetAsSeries(Buffers[i].Open,true);
      ArraySetAsSeries(Buffers[i].High,true);
      ArraySetAsSeries(Buffers[i].Low,true);
      ArraySetAsSeries(Buffers[i].Close,true);
      ArraySetAsSeries(Buffers[i].Color,true);
      
      //--- Print data on the next buffer in the journal
      //Buffers[i].Print();
     }
   //--- Bind the calculated indicator buffer by the FirstFreePlotBufferIndex() buffer index with the BufferTime[] array of the indicator
   //--- set the direction of indexing the BufferTime[] calculated buffer as in the timeseries
   int buffer_temp_index=FirstFreePlotBufferIndex();
   SetIndexBuffer(buffer_temp_index,BufferTime,INDICATOR_CALCULATIONS);
   ArraySetAsSeries(BufferTime,true);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


指標のOnCalculate()ハンドラを見てみましょう。

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

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
   
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      EventsHandling();             // Working with events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Set OnCalculate arrays as timeseries
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);

//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- If limit > 1, there are changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      InitBuffersAll();
     }
//--- Prepare data

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

提供されているすべての関数のコードコメントで、各関数で実行されることをすべて説明しようとしました。
わかりやすければ幸いです。ご質問がある場合は、下のコメント欄でお気軽にお問い合わせください。

残りの関数は、前のバージョンからの指標によって継承されます。それらはほとんど変更されていません。
完全な指標コードは、以下に添付されているファイルで確認できます。

指標をコンパイルし、EURUSD M15チャートで起動します。


最初の4つの銘柄を表す4つのボタンがあります。表示期間を選択するボタンは、いずれかのボタンを押すまで表示されます。銘柄ボタンが押されるとすぐに、期間選択ボタンのリストが開きます。期間を選択すると、選択した銘柄と期間のローソク足がチャートに表示されます。選択したボタンのステータスがターミナルのグローバル変数に書き込まれます。指標を再起動するか、別の銘柄ボタンを押してから前の銘柄ボタンに戻ると、すでに選択されている以前に使用された期間ボタンとともに、期間ボタンが表示されます。

指標バッファを構造体に格納して構築する概念を確認しましたが、指標から使用するにはまだあまり便利ではありません。したがって、次の記事から始めて、より簡単で便利な指標開発を可能にする指標バッファクラスを開発します。

過去2回の記事で、ライブラリ時系列を使用して複数銘柄・複数期間指標を簡単に開発する方法について説明してきました。

次の段階

次の記事から、指標バッファクラスを開発します。

現在のバージョンのライブラリのすべてのファイルは、テスト用EAファイルと一緒に以下に添付されているので、テストするにはダウンロードしてください。
質問、コメント、提案はコメント欄にお願いします。テスト指標はMQL5向けに開発されたのでご注意ください
添付ファイルはMetaTrader 5のみを対象としています。現在のライブラリバージョンはMetaTrader 4ではテストされていません。
現在のバッファ描画タイプ(DRAW_COLOR_CANDLES)はMetaTrader には対応していませんが、指標バッファクラスを作成するときに、MetaTrader 4にいくつかのMQL5機能を実装しようと思います。

目次に戻る

シリーズのこれまでの記事:

DoEasyライブラリの時系列(第35部): バーオブジェクトと銘柄の時系列リスト
DoEasyライブラリの時系列(第36部): すべての使用銘柄期間の時系列オブジェクト
DoEasyライブラリの時系列(第37部): すべての使用銘柄期間の時系列オブジェクト
DoEasyライブラリの時系列(第38部): 時系列コレクション-リアルタイムの更新とプログラムからのデータへのアクセス
DoEasyライブラリの時系列(第39部): ライブラリに基づいた指標 - データイベントと時系列イベントの準備
DoEasyライブラリの時系列(第40部): ライブラリに基づいた指標 - 実時間でのデータ更新

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

添付されたファイル |
MQL5.zip (3709.42 KB)
トレードシグナルの多通貨監視(その5: 複合シグナル トレードシグナルの多通貨監視(その5: 複合シグナル
トレーディングシグナルモニターの作成に関連する第5回の記事では、コンポジットシグナルについて考え、必要な関数を実装していきます。 以前のバージョンでは、RSI、WPR、CCIなどのシンプルなシグナルを使用していましたが、カスタムインジケータを使用する可能性も考慮します。
DoEasyライブラリの時系列(第40部): ライブラリに基づいた指標 - 実時間でのデータ更新 DoEasyライブラリの時系列(第40部): ライブラリに基づいた指標 - 実時間でのデータ更新
本稿では、DoEasyライブラリに基づく単純な複数期間指標の開発について検討します。時系列クラスを改善して、任意の時間枠からデータを受け取り、現在のチャート期間に表示します。
DoEasyライブラリの時系列(第42部): 抽象指標バッファオブジェクトクラス DoEasyライブラリの時系列(第42部): 抽象指標バッファオブジェクトクラス
この記事では、DoEasyライブラリの指標バッファクラスの開発を開始します。さまざまな種類の指標バッファの開発の基礎として使用される抽象バッファの基本クラスを作成します。
DoEasyライブラリの時系列(第39部): ライブラリに基づいた指標 - データイベントと時系列イベントの準備 DoEasyライブラリの時系列(第39部): ライブラリに基づいた指標 - データイベントと時系列イベントの準備
本稿では、DoEasyライブラリを適用して複数の銘柄の複数期間の指標を作成する方法について説明します。指標内で機能するライブラリクラスを準備し、指標のデータソースとして使用される時系列の作成をテストします。時系列イベントの作成と送信も実装します。