English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5 クックブック: 価格の乖離を分析するマルチシンボルインジケーターの開発

MQL5 クックブック: 価格の乖離を分析するマルチシンボルインジケーターの開発

MetaTrader 5 | 23 12月 2015, 13:23
783 0
Anatoli Kazharski
Anatoli Kazharski

はじめに

この記事では、特定の期間における価格の分離を分析するためのマルチシンボルインジケーターの開発を紹介します。そのトピックは、「MQL5クックブック:MQL5のマルチシンボルボラティリティインジケーターの開発」というマルチカレンシーインジケーターのプログラミングに関する以前の記事にてすでに紹介されています。なので、今回は新しい特徴や劇的に変更された機能についてのみ紹介します。マルチカレンシーインジケーターのプログラミングに詳しくなければ、以前の記事をまずお読みいただくことをお勧めします。

この記事では以下の質問に答えていきます。

  • チャートの属性の変更
  • CHARTEVENT_OBJECT_DRAG(チャートオブジェクトのドラッグ)とCHARTEVENT_CHART_CHANGE (チャートのサイズの再調整と、プロパティダイアログウィンドウを用いたチャートの属性の修正) などのイベントのハンドリング
  • 一色以上を用いたインジケーターバッファーの描画
  • チャートのHigh・Lowを設定するために視覚可能範囲におけるインジケーターバッファーのHigh・Lowを定義します。
  • シリーズの反転

結果としての、インジケーターにおけるコード量はかなり多く、1500行にもなります。したがって、個別ファイルにすべての関数を分配し、メインプロジェクトファイルにリンクします。外部ファイルにおいて3つの関数カテゴリを作成します。

  • Checks.mqh - 様々なチェックを実行し、使用可能なデータをダウンロードする関数
  • Objects.mqh - グラフィカルオブジェクトを管理する関数
  • Chart.mqh - チャートの属性を管理する機能

上記のカテゴリに当てはまらないすべての関数はメインファイルに残されます。


インジケーターの開発

Next proceed to the programming of indicator. First we need to create a new project. このために、Metatrader 5\MQL5\Indicatorsディレクトリの中に、インジケーターと同じ名前のフォルダを作成し、その中にインクルードファイルを配置するIncludeフォルダを作成します。次に、インジケーターフォルダーにメインファイルを作成してください。これは、*.mq5拡張子のついたテキストファイルを作成するか、テンプレートによってMQL5 Wizardを用いるこことで実行されます。さらに、プログラムのメイン機能 OnInit()OnDeinit()OnCalculate() に加えて、 OnChartEvent()OnTimer()用います。

以前の記事のように、現在のシンボルに加えて外部パラメーターに定義された5つのシンボルのデータを表示します。しかし、今回はいくつかの公式によって計算された値の代わりに、チャートの生の価格データを出力します。ユーザーは、ドロップダウンリストから外部パラメーターのデータ表現方法を選択できます。Line, BarsCandlesticksです。

一色の線でデータを表示しなければならないのであれば、インジケータープロパティ(#property).<のシンボルの数に等しいバッファーの数を明記するだけで十分です。しかし、バーややキャンドルを描く二つの種類があるため、二つの色のタイプのためにより多くのバッファーが必要です。それぞれを描画する4つのバッファーやそれぞれの要素に色をセットするためのバッファーです。

それぞれのために、プログラムプロパティセクションにて色を明記する必要があります。このために、コンマで別れたリストを単純化しましょう。1色モードにて使用された色が最初に来ます。2色モードにて上昇バーやロウソク足に用いられます。2番目の色は、下降バーやロウソク足にのみ用いられます。

これらすべてのパラメーターは以下にい紹介しています。

#property indicator_chart_window // Indicator is in the main window
#property indicator_buffers 25   // Number of buffers for indicator calculation
#property indicator_plots   5    // Number of plotting series
//--- Indicator buffers colors
#property indicator_color1  clrDodgerBlue,C'0,50,100'
#property indicator_color2  clrLimeGreen,C'20,80,20'
#property indicator_color3  clrGold,C'160,140,0'
#property indicator_color4  clrAqua,C'0,140,140'
#property indicator_color5  clrMagenta,C'130,0,130'

#defineの宣言文にて、定数を宣言し、#includeコマンドラインを用いて、上記にてすでに紹介されている関数を持つファイルとキャンバスを扱うためのStandard libraryのクラスをインクルードしましょう。

//--- Constants 
#define RESET           0 // Returning the indicator recalculation command to the terminal
#define SYMBOLS_COUNT   5 // Number of symbols
//--- Include the class for working with the canvas
#include <Canvas\Canvas.mqh>
//--- Include the class for working with the canvas
#include "Include/Checks.mqh"
#include "Include/Chart.mqh"
#include "Include/Objects.mqh"

ENUM_DRAWTYPEENUM_START_POINT列挙型を、価格データや外部パラメーターの価格の乖離開始地点の描画種類を選択するためのドロップダウンリストを作成するために追加します。

//--- Drawing type of the price data
enum ENUM_DRAWTYPE
  {
   LINE   =0,  // Line
   BARS   =1,  // Bars
   CANDLES=2   // Candlesticks
  };
//--- Mode of the price divergence starting point
enum ENUM_START_POINT
  {
   VERTICAL_LINE=0,  // Vertical line
   MONTH        =1,  // Month
   WEEK         =2,  // Week
   DAY          =3,  // Day
   HOUR         =4   // Hour
  };

データのレンダリング方法は、すでに上記で紹介されているので、価格の乖離開始地点についてもう少し紹介します。

すべてで5つの種類があります: Vertical line, Month, Week, DayHourです。Vertical lineモードにおいては、垂直線がチャートにインジケーターにロードした際に追加されます。この線をドラッグして、全シンボルの価格が一つの地点にて合致するバーを明記します。現在のシンボルにおける特定のバーのオープン価格は、この合致の参照点とされます。そのほかのモードでは、プログラムに特定の期間の最初に価格が合致する時期を教えます。すなわち、各月の初め、各週の初め、各日の初め、各時間の初めなどです。

インジケーターの入力パラメーターのリストを以下にてご覧になれます。

//--- External parameters
input  ENUM_DRAWTYPE    DrawType             =CANDLES;       // Drawing type
input  ENUM_START_POINT StartPriceDivergence =VERTICAL_LINE; // Start of price divergence
input  bool             TwoColor             =false;         // Two-color bars/candlesticks
sinput string dlm01=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
input  string           Symbol02             ="GBPUSD";      // Symbol 2
input  bool             Inverse02            =false;         // Inverse symbol 2
input  string           Symbol03             ="AUDUSD";      // Symbol 3
input  bool             Inverse03            =false;         // Inverse symbol 3
input  string           Symbol04             ="NZDUSD";      // Symbol 4
input  bool             Inverse04            =false;         // Inverse symbol 4
input  string           Symbol05             ="USDCAD";      // Symbol 5
input  bool             Inverse05            =false;         // Inverse symbol 5
input  string           Symbol06             ="USDCHF";      // Symbol 6
input  bool             Inverse06            =false;         // Inverse symbol 6

シンボルは、2から数字が割り振られます。1は、チャートの現在のシンボルに割り振られています。

反転は、各インクルードされたシンボルことに適用されます。反転は、シンボルデータが上下逆に描画されるという意味です。これは、分析されたシンボルのリストが現在の通貨ペアを含み、その同じ通貨がベース通貨とカウンター通貨の両方である際に役に立ちます。例えば、EURUSD通貨ペアにて、USドルがカウンター通貨で、USDCHF通貨ペアでは、それがベース通貨である場合です。もしそのチャートの現在のシンボルがEURUSDであれば、 USDCHFの反転をオンにし、分析のために価格の表示をよりよくします。

以下は、グローバル変数と配列のリストです:

//--- Structure of the indicator buffers arrays
struct buffers
  {
   double            open[];   // Open prices buffer
   double            high[];   // High prices buffer
   double            low[];    // Low prices buffer
   double            close[];  // Close prices buffer
   double            icolor[]; // Buffer to determine the color of element
  };
buffers           buffer_data[SYMBOLS_COUNT];
//--- Load the class
CCanvas           canvas;
//--- Variables/arrays for copying data from OnCalculate()
int               OC_rates_total     =0; // Size of input time series
int               OC_prev_calculated =0; // Bars processed at the previous call
datetime          OC_time[];             // Opening time
double            OC_open[];             // Open prices
double            OC_high[];             // High prices
double            OC_low[];              // Low prices
double            OC_close[];            // Close prices
long              OC_tick_volume[];      // Tick volumes
long              OC_volume[];           // Real volumes
int               OC_spread[];           // Spread

//--- For the purpose of storing and checking the time of the first bar in the terminal
datetime          series_first_date[SYMBOLS_COUNT];
datetime          series_first_date_last[SYMBOLS_COUNT];
//--- Time array of the bar from which we will start drawing
datetime          limit_time[SYMBOLS_COUNT];
//--- Symbol names array
string            symbol_names[SYMBOLS_COUNT];
//--- Array of symbol inverse flags
bool              inverse[SYMBOLS_COUNT];
//--- Colors of indicator lines
color             line_colors[SYMBOLS_COUNT]={clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta};
//--- String representing the lack of the symbol
string            empty_symbol="EMPTY";
//--- Chart properties
int               window_number              =WRONG_VALUE;               // Indicator window number
int               chart_width                =0;                         // Chart width
int               chart_height               =0;                         // Chart height
int               last_chart_width           =0;                         // Last saved chart width
int               last_chart_height          =0;                         // Last saved chart height
int               chart_center_x             =0;                         // Horizontal center of chart
int               chart_center_y             =0;                         // Vertical center of chart
color             color_bar_up               =clrRed;                    // Up bar color
color             color_bar_down             =C'100,0,0';                // Down bar color
string            indicator_shortname        ="MS_PriceDivergence";      // Short name of the indicator
string            prefix                     =indicator_shortname+"_";   // Prefix for objects
//--- Name of vertical line of the price divergence starting point
string            start_price_divergence=prefix+"start_price_divergence";
//--- Canvas properties
string            canvas_name             =prefix+"canvas";          // Canvas name
color             canvas_background       =clrBlack;                 // Canvas background color
uchar             canvas_opacity          =190;                      // Opacity
int               font_size               =16;                       // Font size
string            font_name               ="Calibri";                // Font
ENUM_COLOR_FORMAT clr_format              =COLOR_FORMAT_ARGB_RAW;    // Color components should be correctly set by the user
//--- Canvas messages
string            msg_prepare_data        ="Preparing data! Please wait...";
string            msg_not_synchronized    ="Unsynchronized data! Please wait...";
string            msg_load_data           ="";
string            msg_sync_update         ="";
string            msg_last                ="";
//---
ENUM_TIMEFRAMES   timeframe_start_point  =Period();    // Timeframe for the price divergence starting point
datetime          first_period_time      =NULL;        // Time of the first specified period on chart
double            divergence_price       =0.0;         // Price of the price divergence starting point
datetime          divergence_time        =NULL;        // Time of the price divergence starting point
double            symbol_difference[SYMBOLS_COUNT];    // Difference in price relative to the current symbol
double            inverse_difference[SYMBOLS_COUNT];   // Difference that is formed when calculating inversion

次に、インジケーターの初期化中に使用される関数を紹介します。一般的に以前の記事からOnInit()と比較して大きな変更はありません。

インジケーターが使用する部分にチェックを入れましょう。重要な点は、現在アーミナル開発者がストラテジーテスターのチャートの属性を管理する機能を全て実装していないため、インジケーターがストラテジーテスターの外のみにて使用されるように制限します。これを実装するために、簡単な関数 - CheckTesterMode()を作成します。Checks.mqhファイルにロードされます:

//+------------------------------------------------------------------+
//| Checks if indicator is used in Strategy Tester                   |
//+------------------------------------------------------------------+
bool CheckTesterMode()
  {
//--- Report that indicator is not intended to be used in Strategy Tester
   if(MQLInfoInteger(MQL_TESTER) || 
      MQLInfoInteger(MQL_VISUAL_MODE) || 
      MQLInfoInteger(MQL_OPTIMIZATION))
     {
      Comment("Currently, the <- "+MQLInfoString(MQL_PROGRAM_NAME)+" -> indicator is not intended to be used in Strategy Tester!");
      return(false);
     }
//---
   return(true);
  }

別の新しい関数SetBarsColors()は、現在のシンボルのバーやロウソク足の色を設定するためのものです。Chart.mqhファイルに位置しています。

//+------------------------------------------------------------------+
//| Sets colors for the current symbol bars                          |
//+------------------------------------------------------------------+
void SetBarsColors()
  {
//--- Color for the up bar, shadows and body borders of bull candlesticks
   ChartSetInteger(0,CHART_COLOR_CHART_UP,color_bar_up);
//--- Body color of a bull candlestick
   ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,color_bar_up);
//--- Line chart color and color of "Doji" Japanese candlesticks
   ChartSetInteger(0,CHART_COLOR_CHART_LINE,color_bar_up);
//--- For two-color mode
   if(TwoColor)
     {
      //--- Color for the down bar, shadows and body borders of bear candlesticks
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_down);
      //--- Body color of a bear candlestick
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_down);
     }
//--- If two-color mode is turned off
   else
     {
      //--- Color for the down bar, shadows and body borders of bear candlesticks
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_up);
      //--- Body color of a bear candlestick
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_up);
     }
  }

初期化中にStartPriceDivergence外部パラメーターにてどのモードを選択するか決める必要があります。Vertical lineが選択されれば、timeframe_start_pointグローバル変数が現在のタイムフレームである標準の値で設定されます。さもなければ、選択されたタイムフレームが適用されます。このために、InitStartPointTF() 関数を作成しましょう:

//+------------------------------------------------------------------+
//| Identifies timeframe for the price starting point mode           |
//+------------------------------------------------------------------+
void InitStartPointTF()
  {
//--- Exit if vertical line mode is selected
   if(StartPriceDivergence==VERTICAL_LINE)
      return;
//--- Otherwise define the timeframe
   switch(StartPriceDivergence)
     {
      case MONTH : timeframe_start_point=PERIOD_MN1; break;
      case WEEK  : timeframe_start_point=PERIOD_W1;  break;
      case DAY   : timeframe_start_point=PERIOD_D1;  break;
      case HOUR  : timeframe_start_point=PERIOD_H1;  break;
     }
  }

以前の記事のものと異なりCheckInputParameters()関数はこのようになります:

//+------------------------------------------------------------------+
//| Checks input parameters for correctness                          |
//+------------------------------------------------------------------+
bool CheckInputParameters()
  {
//--- For all other modes except the 'Vertical Line'
   if(StartPriceDivergence!=VERTICAL_LINE)
     {
      //--- If the current period is greater than or equal to the specified period of the price divergence starting point, report of it and exit
      if(PeriodSeconds()>=PeriodSeconds(timeframe_start_point))
        {
         Print("Current timeframe should be less than one specified in the Start Price Divergence parameter!");
         Comment("Current timeframe should be less than one specified in the Start Price Divergence parameter!");
         return(false);
        }
     }
//---
   return(true);
  }

配列は以前の記事のように初期化されます。配列の名前と数のみが変更されました。

//+------------------------------------------------------------------+
//| First initialization of arrays                                   |
//+------------------------------------------------------------------+
void InitArrays()
  {
   ArrayInitialize(limit_time,NULL);
   ArrayInitialize(symbol_difference,0.0);
   ArrayInitialize(inverse_difference,0.0);
   ArrayInitialize(series_first_date,NULL);
   ArrayInitialize(series_first_date_last,NULL);
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      ArrayInitialize(buffer_data[s].open,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].high,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].low,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].close,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].icolor,EMPTY_VALUE);
     }
  }
//+------------------------------------------------------------------+
//| Initializes array of symbols                                     |
//+------------------------------------------------------------------+
void InitSymbolNames()
  {
   symbol_names[0]=AddSymbolToMarketWatch(Symbol02);
   symbol_names[1]=AddSymbolToMarketWatch(Symbol03);
   symbol_names[2]=AddSymbolToMarketWatch(Symbol04);
   symbol_names[3]=AddSymbolToMarketWatch(Symbol05);
   symbol_names[4]=AddSymbolToMarketWatch(Symbol06);
  }
//+------------------------------------------------------------------+
//| Initializes array of inversions                                  |
//+------------------------------------------------------------------+
void InitInverse()
  {
   inverse[0]=Inverse02;
   inverse[1]=Inverse03;
   inverse[2]=Inverse04;
   inverse[3]=Inverse05;
   inverse[4]=Inverse06;
  }

SetIndicatorProperties()関数において重要な変更がなされています。実際、これは完全に新しい関数です。どのデータ描画モードが選択されるかによって、一致する属性が初期化中に設定されます。

//+------------------------------------------------------------------+
//| Sets indicator properties                                        |
//+------------------------------------------------------------------+
void SetIndicatorProperties()
  {
//--- Set the short name
   IndicatorSetString(INDICATOR_SHORTNAME,indicator_shortname);
//--- Set the number of decimal digits
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//---  In the 'Line' mode we need only one buffers that displays the open price
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         SetIndexBuffer(s,buffer_data[s].close,INDICATOR_DATA);
     }
//--- In other modes we use all prices for drawing 
//    bars/candlesticks and additional buffer for the two-color mode
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         static int buffer_number=0;
         SetIndexBuffer(buffer_number,buffer_data[s].open,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].high,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].low,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].close,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].icolor,INDICATOR_COLOR_INDEX);
         buffer_number++;
        }
     }
//--- Set labels for the current timeframe
//    In the 'Line' mode only opening price is used
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetString(s,PLOT_LABEL,symbol_names[s]+",Close");
     }
//--- In other modes all prices of bars/candlesticks
//    ";" is used as a separator
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         PlotIndexSetString(s,PLOT_LABEL,
                            symbol_names[s]+",Open;"+
                            symbol_names[s]+",High;"+
                            symbol_names[s]+",Low;"+
                            symbol_names[s]+",Close");
        }
     }
//--- Set the type of lines for indicator buffers
//--- Line
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE);
//--- Bars
   if(DrawType==BARS)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_BARS);
//--- Candlesticks
   if(DrawType==CANDLES)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES);

//--- Set the type of lines for data of current symbol
//--- Line
   if(DrawType==LINE)
      ChartSetInteger(0,CHART_MODE,CHART_LINE);
//--- Bars
   if(DrawType==BARS)
      ChartSetInteger(0,CHART_MODE,CHART_BARS);
//--- Candlesticks
   if(DrawType==CANDLES)
      ChartSetInteger(0,CHART_MODE,CHART_CANDLES);

//--- Set the line width
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1);
//--- Set the line color for the 'Line' mode
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]);
//--- Display data in Data Window only for existing symbols
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      if(symbol_names[s]!=empty_symbol)
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,true);
      else
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,false);
     }
//--- Empty value for plotting where nothing will be drawn
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }

そして、最後にOnInit()にて使用される新しい関数SetDivergenceLine()です。Vertical lineモードにて価格乖離開始地点を操作するための垂直の緑線をセットします。

//+------------------------------------------------------------------+
//| Sets vertical line for price divergence starting point           |
//+------------------------------------------------------------------+
void SetDivergenceLine()
  {
//--- If there is no vertical line yet, set it
   if(StartPriceDivergence==VERTICAL_LINE && ObjectFind(0,start_price_divergence)<0)
      //--- Place a vertical line on the true bar
      CreateVerticalLine(0,0,TimeCurrent()+PeriodSeconds(),start_price_divergence,
                         2,STYLE_SOLID,clrGreenYellow,true,true,false,"","\n");
//--- For all other modes except the 'Vertical Line'
   if(StartPriceDivergence!=VERTICAL_LINE)
      DeleteObjectByName(start_price_divergence);
  }

以下は、 OnInit()関数内の上記で紹介された全ての内容になります。個別の関数やファイルに分割されると、プログラムのコードを読むのにとても便利になります。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Check if indicator is currently being used in Strategy Tester
   if(!CheckTesterMode())
      return(INIT_FAILED);
//--- Set the color for bars/candlesticks
   SetBarsColors();
//--- Define the timeframe for the price divergence starting point
   InitStartPointTF();
//--- Check input parameters for correctness
   if(!CheckInputParameters())
      return(INIT_PARAMETERS_INCORRECT);
//--- Set the timer at 1-second intervals
   EventSetMillisecondTimer(1000);
//--- Set the font to be displayed on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Initialization of arrays
   InitArrays();
//--- Initialize the array of symbols 
   InitSymbolNames();
//--- Initialize the array of inversions
   InitInverse();
//--- Set indicator properties
   SetIndicatorProperties();
//--- Set vertical line of the price divergence start
   SetDivergenceLine();
//--- Clear the comment
   Comment("");
//--- Refresh the chart
   ChartRedraw();
//--- Initialization completed successfully
   return(INIT_SUCCEEDED);
  }

OnCalculate()関数のそのプログラムコードはほとんど変更されず残っています。以前の記事では、データが使用できるかの全てのチェックが終了すると、そのプログラムはまず補助配列を固定し、準備されたデータをインジケーターバッファーに格納しました。今回は、全ての処理を一つのループで調整してみます。

データを確認し、ロードする関数をより厳格にしました。取得したい値はそれぞれ特定の試行回数にて渡されます。値が取得されれば、そのループは停止します。そして、期間の初めを決定する必要のあるモード(月、週、日、時間)があるので、より高いタイムフレームにて期間の開始時間を取得します。そのため、LoadAndFormDataHighTF()に類似した名前を持つLoadAndFormData()に似た追加の関数を作成しました。そのコードは元のととても似ているので、ここには投稿しません。

現在のタイムフレームにおいてデータを使用できるかの確認は、CheckAvailableData()という関数にて実行されます。

//+------------------------------------------------------------------+
//| Checks the amount of available data for all symbols              |
//+------------------------------------------------------------------+
bool CheckAvailableData()
  {
   int attempts=100;
   
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If this symbol is available
      if(symbol_names[s]!=empty_symbol)
        {
datetime time[];                    // Array for checking the number of bars
   int      total_period_bars   =0;    // Number of bars of the current period
   datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal
         //--- Get the first date of the current time frame data in the terminal
         terminal_first_date=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE);
         //--- Get the number of available bars from the date specified
         total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent());
         //--- Check the readiness of bar data
         for(int i=0; i<attempts; i++)
           {
            //--- Copy the specified amount of data
            if(CopyTime(symbol_names[s],Period(),0,total_period_bars,time))
              {
               //--- If the required amount has been copied, terminate the loop
               if(ArraySize(time)>=total_period_bars)
                  break;
              }
           }
         //--- If the amount of data copied is not sufficient, one more attempt is required
         if(ArraySize(time)==0 || ArraySize(time)<total_period_bars)
           {
            msg_last=msg_prepare_data;
            ShowCanvasMessage(msg_prepare_data);
            OC_prev_calculated=0;
            return(false);
           }
        }
     }
//--- Exit if current mode is vertical line of the price divergence starting point
   if(StartPriceDivergence==VERTICAL_LINE)
      return(true);
   else
     {
      datetime time[];                    // Array for checking the number of bars
      int      total_period_bars   =0;    // Number of bars of the current period
      datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal
      //--- Get the first date of the current time frame data in the terminal
      for(int i=0; i<attempts; i++)
         if((terminal_first_date=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE))>0)
            break;
      //--- Get the number of available bars from the date specified
      for(int i=0; i<attempts; i++)
         if((total_period_bars=(int)SeriesInfoInteger(Symbol(),timeframe_start_point,SERIES_BARS_COUNT))>0)
            break;
      //--- Check the readiness of bar data
      for(int i=0; i<attempts; i++)
         //--- Copy the specified amount of data
         if(CopyTime(Symbol(),timeframe_start_point,
            terminal_first_date+PeriodSeconds(timeframe_start_point),TimeCurrent(),time)>0)
            break;
      //--- If the amount of data copied is not sufficient, one more attempt is required
      if(ArraySize(time)<=0 || total_period_bars<=0)
        {
         msg_last=msg_prepare_data;
         ShowCanvasMessage(msg_prepare_data);
         OC_prev_calculated=0;
         return(false);
        }
     }
//---
   return(true);
  }

FillIndicatorBuffers()関数は、現在のタスクにおいてかなり複雑になっています。これはいくつかのモードがあり、それぞれが独自のアクションを必要とするためです。実際、全てが4つのステップに分けられています。

  • 特定のシンボルにおけるデータを取得する。
  • より高いタイムフレームにおけるデータを取得し、全てのシンボルの価格が合致する価格レベルと時間を決定する。
  • 値を計算し、インジケーターバッファーに格納する。
  • 計算された値の確認

その関数のコードは、詳細なコメント付きで以下に提供されています:

//+------------------------------------------------------------------+
//| Fills indicator buffers                                          |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(int i,int s,datetime const &time[])
  {
   MqlRates    rates[];             // Data structure
   double      period_open[];       // Opening price for bar at the price divergence starting point
   datetime    period_time[];       // Time of the price divergence starting point
   int         attempts=100;        // Number of copying attempts
   datetime    high_tf_time=NULL;   // Time of higher timeframe's bar

//--- Exit if we are out of "true" bars zone
   if(time[i]<limit_time[s])
      return;
//--- Reset the last error
   ResetLastError();
//--- Get data of current bar for the specified symbol
   for(int j=0; j<attempts; j++)
      if(CopyRates(symbol_names[s],Period(),time[i],1,rates)==1)
        { ResetLastError(); break; }
//--- Exit if failed to get data
   if(ArraySize(rates)<1 || GetLastError()!=0)
      return;
//--- If the current time is before the first timeframe's time or
//    bar time is not equal to the bar time of the current symbol or
//    empty values are fetched
   if(rates[0].time==NULL || 
      time[i]!=rates[0].time || 
      time[i]<first_period_time || 
      rates[0].low==EMPTY_VALUE || 
      rates[0].open==EMPTY_VALUE ||
      rates[0].high==EMPTY_VALUE ||
      rates[0].close==EMPTY_VALUE)
     {
      //--- Write empty value
      if(DrawType!=LINE)
        {
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
        }
      buffer_data[s].close[i]=EMPTY_VALUE;
      return;
     }
//--- If current mode is vertical line of the price divergence starting point
   if(StartPriceDivergence==VERTICAL_LINE)
     {
      //--- Get the time of the line
      divergence_time=(datetime)ObjectGetInteger(0,start_price_divergence,OBJPROP_TIME);
      //--- Get the time of the first bar
      first_period_time=time[0];
     }
//--- For all other modes, we will keep track the beginning of period
   else
     {
      //--- If we are here for the first time, store data of the first bar of higher timeframe
      if(divergence_time==NULL)
        {
         ResetLastError();
         //--- Get opening time of the first bar of higher timeframe
         for(int j=0; j<attempts; j++)
            if(CopyTime(Symbol(),timeframe_start_point,time[0]+PeriodSeconds(timeframe_start_point),1,period_time)==1)
              { ResetLastError(); break; }
         //--- Exit if failed to get price/time
         if(ArraySize(period_time)<1 || GetLastError()!=0)
            return;
         //--- Otherwise store time of the first bar of higher timeframe
         else
            first_period_time=period_time[0];
        }
      //--- If current bar's time on the current timeframe is before the first bar's time on higher timeframe
      if(time[i]<first_period_time)
         high_tf_time=first_period_time;
      //--- Otherwise we will receive data of the last bar of the higher timeframe with respect to the current bar on the current timeframe
      else
         high_tf_time=time[i];
      //--- Reset the last error
      ResetLastError();
      //--- Get the opening price of the first bar of the higher timeframe
      for(int j=0; j<attempts; j++)
         if(CopyOpen(Symbol(),timeframe_start_point,high_tf_time,1,period_open)==1)
           { ResetLastError(); break; }
      //--- Get opening time of the first bar of higher timeframe
      for(int j=0; j<attempts; j++)
         if(CopyTime(Symbol(),timeframe_start_point,high_tf_time,1,period_time)==1)
           { ResetLastError(); break; }
      //--- Exit if failed to get price/time
      if(ArraySize(period_open)<1 || ArraySize(period_time)<1 || GetLastError()!=0)
         return;
      //--- If the current timeframe's time is before the first period's time or
      //    time of specified period is not equal to the one in memory
      if(time[i]<first_period_time || divergence_time!=period_time[0])
        {
         symbol_difference[s]  =0.0; // Zero out difference in symbol prices
         inverse_difference[s] =0.0; // Zero our difference of inversion
         //--- Store time of the price divergence starting point
         divergence_time=period_time[0];
         //--- Store price of the price divergence starting point
         divergence_price=period_open[0];
         //--- Set vertical line in the beginning of the price divergence start
         CreateVerticalLine(0,0,period_time[0],start_price_divergence+"_"+TimeToString(divergence_time),
                            2,STYLE_SOLID,clrWhite,false,false,true,TimeToString(divergence_time),"\n");
        }
     }
//--- If current mode is 'Vertical Line' and bar's time is less than line's time
   if(StartPriceDivergence==VERTICAL_LINE && time[i]<divergence_time)
     {
      //--- Keep zero values of difference
      symbol_difference[s]  =0.0;
      inverse_difference[s] =0.0;
      //--- For the 'Line' drawing mode only opening price is used
      if(DrawType==LINE)
         buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
      //--- For all other modes all prices are used
      else
        {
         buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
         buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
         buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
         buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
         //--- Set color for the current element of indicator buffer
         SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
        }
     }
//--- For all other modes
   else
     {
      //--- If inversion of symbol data is required
      if(inverse[s])
        {
         //--- If new period has started, recalculate variables
         if(symbol_difference[s]==0.0)
           {
            //--- For the 'Vertical Line' mode
            if(StartPriceDivergence==VERTICAL_LINE)
              {
               //--- Calculate the difference
               symbol_difference[s]  =rates[0].open-OC_open[i];
               inverse_difference[s] =OC_open[i]-(-OC_open[i]);
              }
            //--- For all other modes
            else
              {
               //--- Calculate the difference
               symbol_difference[s]  =rates[0].open-divergence_price;
               inverse_difference[s] =divergence_price-(-divergence_price);
              }
           }
         //--- In the 'Line' mode only opening price is used
         if(DrawType==LINE)
            buffer_data[s].close[i]=-(rates[0].close-symbol_difference[s])+inverse_difference[s];
         //--- For all other modes all prices are used
         else
           {
            buffer_data[s].low[i]   =-(rates[0].low-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].open[i]  =-(rates[0].open-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].high[i]  =-(rates[0].high-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].close[i] =-(rates[0].close-symbol_difference[s])+inverse_difference[s];
            //--- Set color for the current element of indicator buffer
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
      //--- If inversion is not used, then we need to calculate only the difference between symbol prices at the beginning of period
      else
        {
         //--- If new period has started
         if(symbol_difference[s]==0.0)
           {
            //--- For the 'Vertical Line' mode
            if(StartPriceDivergence==VERTICAL_LINE)
               symbol_difference[s]=rates[0].open-OC_open[i];
            //--- For all other modes
            else
               symbol_difference[s]=rates[0].open-divergence_price;
           }
         //--- For the 'Line' drawing mode only opening price is used
         if(DrawType==LINE)
            buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
         //--- For all other modes all prices are used
         else
           {
            buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
            buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
            buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
            buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
            //--- Set color for the current element of indicator buffer
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
     }
//--- Verification of the calculated values
//    In the 'Line' mode only opening price is used
   if(DrawType==LINE)
     {
      //--- If the current time is before the first timeframe's time or
      //    bar time is not equal to the bar time, write empty value
      if(time[i]!=rates[0].time || time[i]<first_period_time)
         buffer_data[s].close[i]=EMPTY_VALUE;
     }
//--- For all other modes all prices are used
   else
     {
      //--- If the current time is before the first timeframe's time or
      //    bar time is not equal to the bar time of the current symbol or
      //    empty values are fetched
      if(rates[0].time==NULL || 
         time[i]!=rates[0].time || 
         time[i]<first_period_time || 
         rates[0].low==EMPTY_VALUE || 
         rates[0].open==EMPTY_VALUE ||
         rates[0].high==EMPTY_VALUE ||
         rates[0].close==EMPTY_VALUE)
        {
         //--- Write empty value
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
         buffer_data[s].close[i] =EMPTY_VALUE;
        }
     }
  }

上記の関数を勉強する際に、別のカスタム関数SetBufferColorIndex()に気づくでしょう。この関数はインジケーターカラーバッファーに色を設定します。

//+------------------------------------------------------------------+
//| Sets the color for buffer element by condition                   |
//+------------------------------------------------------------------+
void SetBufferColorIndex(int i,int symbol_number,double close,double open)
  {
//--- For two-color mode, check condition
   if(TwoColor)
     {
      //--- If the closing price is more than the opening price, this is up bar, so we use the first color
      if(close>open)
         buffer_data[symbol_number].icolor[i]=0;
      //--- otherwise it is down bar, so we use the second color
      else
         buffer_data[symbol_number].icolor[i]=1;
     }
//--- For one-color mode we use the first color for all bars/candlesticks
   else
      buffer_data[symbol_number].icolor[i]=0;
  }

インジケーターバッファーが格納されれば、チャートウィンドウにて見ることのできるすべての値から最大・最小値を決定する必要があります。MQL5により、チャートウィンドウの最初の視覚可能なバーと視覚可能なバーの数を取得できます。別のカスタム関数CorrectChartMaxMin()にてこれらの機能から恩恵を受けることができます。その関数のコードの流れはいくつかのステップに分けられます。

  • 最初と最後の視覚可能バーの数の決定
  • 現在のシンボルにおける視覚可能な最大・最小値の決定
  • 全てのシンボルの配列における最大・最小値の決定
  • チャートの属性における最大・最小値の設定

Chart.mqhファイルにあるCorrectChartMaxMin()関数のコードが以下にあります。

//+------------------------------------------------------------------+
//| Corrects chart's high/low with respect to all buffers            |
//+------------------------------------------------------------------+
void CorrectChartMaxMin()
  {
   double low[];                  // Array of lows
   double high[];                 // Array of highs
   int    attempts          =10;  // Number of attempts
   int    array_size        =0;   // Array size for drawing
   int    visible_bars      =0;   // Number of visible bars
   int    first_visible_bar =0;   // Number of the first visible bar
   int    last_visible_bar  =0;   // Number of the last visible bar
   double max_price         =0.0; // Highest price
   double min_price         =0.0; // Lowest price
   double offset_max_min    =0.0; // Offset from chart's high/low
//--- Reset the last error
   ResetLastError();
//--- Number of visible bars
   visible_bars=(int)ChartGetInteger(0,CHART_VISIBLE_BARS);
//--- Number of the first visible bar
   first_visible_bar=(int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR);
//--- Number of the last visible bar
   last_visible_bar=first_visible_bar-visible_bars;
//--- Exit in case of error
   if(GetLastError()!=0)
      return;
//--- Fix incorrect value
   if(last_visible_bar<0)
      last_visible_bar=0;
//--- Get the current symbol high/low in visible area of chart
   for(int i=0; i<attempts; i++)
      if(CopyHigh(Symbol(),Period(),last_visible_bar,visible_bars,high)==visible_bars)
         break;
   for(int i=0; i<attempts; i++)
      if(CopyLow(Symbol(),Period(),last_visible_bar,visible_bars,low)==visible_bars)
         break;
//--- Exit if failed to get data
   if(ArraySize(high)<=0 || ArraySize(low)<=0)
      return;
//--- If succeeded to get data, identify high and low in the current symbol arrays
   else
     {
      min_price=low[ArrayMinimum(low)];
      max_price=high[ArrayMaximum(high)];
     }
//--- Get high and low prices in all price arrays
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If current symbol is not present, go to the next one
      if(symbol_names[s]==empty_symbol)
         continue;
      //---
      datetime time[];         // Time array
      int      bars_count=0; // Number of bars for calculation
      //--- Set zero size for arrays
      ArrayResize(high,0);
      ArrayResize(low,0);
      //--- Get the time of the first bar visible on chart
      for(int i=0; i<attempts; i++)
         if(CopyTime(Symbol(),Period(),last_visible_bar,visible_bars,time)==visible_bars)
            break;
      //--- Exit if the amount of data is less than number of visible bars on chart
      if(ArraySize(time)<visible_bars)
         return;
      //--- If time of the first "true" bar is greater than
      //    time of the first visible bar on the chart, then
      //    get available number of bars of the current symbol in loop
      if(limit_time[s]>time[0])
        {
         //--- Get the array size
         array_size=ArraySize(time);
         //--- Get the number of bars from the first "true" one
         if((bars_count=Bars(Symbol(),Period(),limit_time[s],time[array_size-1]))<=0)
            return;
        }
      //--- Else get number of visible bars on chart
      else
         bars_count=visible_bars;
      //--- Index elements in indicator buffers as timeseries
      ArraySetAsSeries(low,true);
      ArraySetAsSeries(high,true);
      //--- Copy data from the indicator buffer
      //    All modes except 'Line'
      if(DrawType!=LINE)
        {
         ArrayCopy(low,buffer_data[s].low);
         ArrayCopy(high,buffer_data[s].high);
        }
      //--- For the 'Line' mode
      else
        {
         ArrayCopy(low,buffer_data[s].close);
         ArrayCopy(high,buffer_data[s].close);
        }
      //--- Get the array size
      array_size=ArraySize(high);
      //--- Fill empty values,
      //    so they are not considered when calculating high/low
      for(int i=0; i<array_size; i++)
        {
         if(high[i]==EMPTY_VALUE)
            high[i]=max_price;
         if(low[i]==EMPTY_VALUE)
            low[i]=min_price;
        }
      //--- Get high/low with respect to inversion
      if(inverse[s])
        {
         //--- If no errors occur, store values
         if(ArrayMaximum(high,last_visible_bar,bars_count)>=0 && 
            ArrayMinimum(low,last_visible_bar,bars_count)>=0)
           {
            max_price=fmax(max_price,low[ArrayMaximum(low,last_visible_bar,bars_count)]);
            min_price=fmin(min_price,high[ArrayMinimum(high,last_visible_bar,bars_count)]);
           }
        }
      else
        {
         //--- If no errors occur, store values
         if(ArrayMinimum(low,last_visible_bar,bars_count)>=0 && 
            ArrayMaximum(high,last_visible_bar,bars_count)>=0)
           {
            min_price=fmin(min_price,low[ArrayMinimum(low,last_visible_bar,bars_count)]);
            max_price=fmax(max_price,high[ArrayMaximum(high,last_visible_bar,bars_count)]);
           }
        }
     }
//--- Calculate offset (3%) form chart's top and bottom
   offset_max_min=((max_price-min_price)*3)/100;
//--- Turn on the fixed chart scale mode.
   ChartSetInteger(0,CHART_SCALEFIX,true);
//--- Set high/low
   ChartSetDouble(0,CHART_FIXED_MAX,max_price+offset_max_min);
   ChartSetDouble(0,CHART_FIXED_MIN,min_price-offset_max_min);
//--- Refresh the chart
   ChartRedraw();
  }

上記で記載されている関数は、垂直線のドラッグイベントを処理する際に使用されます(また、OnCalculateのインジケーター値を計算する際です):

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Event of dragging a graphical object
   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- If current mode is vertical line for the price divergence starting point, then update indicator buffers
      if(StartPriceDivergence==VERTICAL_LINE)
         OnCalculate(OC_rates_total,
                     0,
                     OC_time,
                     OC_open,
                     OC_high,
                     OC_low,
                     OC_close,
                     OC_tick_volume,
                     OC_volume,
                     OC_spread);
     }
//--- Event of resizing the chart or modifying the chart properties using the properties dialog window.
   if(id==CHARTEVENT_CHART_CHANGE)
      //--- Correct the maximum and minimum of chart with respect to the indicator buffers' values
      CorrectChartMaxMin();
  }

全ての関数が準備できました。この記事に添付されている詳しくコメントが付いたコードを勉強できます。

最終的に得たもののデモンストレーションをしましょう。GBPUSDAUDUSDNZDUSDUSDCADUSDCHF シンボルは外部パラメーターにて明記されています。以下のスクリーンショットにて、反転停止状態のVertical lineモードにおけるEURUSDの週間チャートを見ることができます。

「Vertical Line」モードにおける週間タイムフレーム

図1 - 「Vertical Line」モードにおける週間タイムフレーム

以下のスクリーンショットにて、DayモードでのM30タイムフレームを見ることができますが、今回反転はUSDをベース通貨としたシンボルにおいて可能になっています。この場合、これらはUSDCAD(水色のロウソク)と USDCHF (紫色のろうそく)です。

 "Day"モードでのM30タイムフレーム

図2 -"Day"モードでのM30タイムフレーム


結論

価格乖離のマルチカレンシー分析における役に立つ興味深いツールを作成できました。このインジケーターは今後とも改善されていきます。

お読みいただき誠にありがとうございました!

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

添付されたファイル |
MQL5クックブック - MQL5での未決注文の取り扱いとマルチカレンシーエキスパートアドバイザー MQL5クックブック - MQL5での未決注文の取り扱いとマルチカレンシーエキスパートアドバイザー
今回は、指値売り、逆指値売りなど未決注文に基づくトレーディングアルゴリズムを持つマルチカレンシーエキスパートアドバイザーを作成していきます。この記事は、以下を紹介します:特定の時間半位におけるトレーディング、未決注文の実行・修正・削除、最後のポジションが利取りや損切りにて閉じられたかのチェック、各シンボルにおける取引の履歴の管理などです。
MQL5プログラミングベージックス:リスト MQL5プログラミングベージックス:リスト
トレーディング戦略開発のためのプログラミング言語の新バージョン、MQL5は、以前のバージョン、MQL4と比較してより強力で効果的な機能を提供しています。その利点は本質的にオブジェクト指向プログラミングの機能にあります。この記事は、ノードやリストなど複雑なカスタムデータ型を用いることについて詳しく見ていきます。また、MQL5での実際的なプログラミングにてリストを用いる例を紹介します。
レンコチャートにおけるインジケーター レンコチャートにおけるインジケーター
この記事は、MQL5のレンコチャートとその実装の例を紹介します。このインジケーターの修正は、古典的なチャートとは異なります。インジケーターウィンドウ、メインチャート上の両方で構築できます。さらに、ジグザグインジケーターがあります。そのチャートの実装例をいくつかご確認ください。
スピンドルチャートインジケーター スピンドルチャートインジケーター
この記事は、スピンドルチャート描写とトレード戦略とEAにおけるその使い方に関するものです。まず、日本のロウソク足チャートとも繋がりがある、そのチャートについてみていきます。次に、MQL5言語でのソースコードベースでのインジケーターの実装について分析します。インジケーターとトレード手法に基づいてEAをテストしてみましょう。