1つのツールで複数のチャートを異なる時間枠で同期させる

Dmitriy Gizlyk | 4 6月, 2018

概論

エルダーの時代から今日に至るまで、トレーダーは様々な時間枠でチャートを分析して、取引の決定を下しています。世界のトレンドを表示するオブジェクトが、古い時間枠のチャートに表示され、それから直近の時間枠に表示されるオブジェクト近辺の価格動向を分析するという状況を、誰もがよく知っているものだと私は思います。このような分析の過程で、以前に作成されたオブジェクトに修正を加えることができます。MetaTrader 5の既存のツールは、堆積したオブジェクトを保存しながら、時間枠を変更することによって、この作業を1つのチャートで実行することを可能にしています。しかしながら、同時に複数のチャートで価格を把握する必要がある場合にはどうすればよいでしょうか?

このような場合は、テンプレートを使用することができますが、少なくとも1つのオブジェクトが変更されるたびに、テンプレートを再度保存して、チャートのそれぞれに適用する必要があります。この記事では、このプロセスを自動化し、インジケータにチャートを同期させる機能を追加していこうと思います。


1. 問題提起

私たちのインジケータの主なタスクは、MetaTrader 5で開いているチャートの同期です。この場合、プログラムはツールに対して必要なチャートを決定する必要があります。また同時に、プログラムは、私達が関心を持つすべてのチャート上の、すべてのグラフィカルオブジェクトのステータスを常に監視しなければなりません。プログラムは1つのチャート上で行われたオブジェクトの各変更を、他のチャート上でも繰り返す必要があります。

グラフィックオブジェクトのステータスは、2つの方法で監視することができます。一つ目は周波数を決定し、適用されたすべてのオブジェクトを周期的にチェックして同期させることです。このアプローチの利点は、1つチャートにつきプログラムのコピーが1つだけ必要な点です。しかし、2つの問題があります。

一見、両方の問題は、同期頻度を高め、プログラム変数またはディスク上のファイルに、オブジェクトに関する最新の同期情報を格納することで簡単に解決されるように見えます。しかし、チャート上のオブジェクトの数が増えると、各サイクルの実行時間と格納される情報の量がともに増加するため、EAやスクリプトとは異なり、インジケータはMetaTrader 5の一般的な流れで開始されることに注意する必要があります。したがって、インジケータに過大な負荷がかかると、他のインジケータやターミナル全体の性能に遅延を起こす可能性がでてきます。

第2の方法は、変化するオブジェクトの追跡をターミナルに割り当て、ターミナルのイベントによってオブジェクトの同期を開始し、それらをOnChartEvent関数で処理することです。このアプローチにより、オブジェクトの作成または変更の直後にプログラムが反応し、遅延が最小限に抑えられます。したがって、すべての同期オブジェクトに関する情報を保存する必要も、定期的にすべてのチャートのステータスを確認する必要もありません。これにより、プログラムの負荷が大幅に軽減されます。

この2つ目の方法は完全にマッチしているように思えますが、小さな問題があり、OnChartEvent関数は、プログラムが実行されているチャートのイベントだけを呼び出します。しかし、これも全ての変更が行われるマスターチャートを決定することで、私達の足止めにはなりません。インジケータのコピーが1つあれば足りるでしょう。しかし、オブジェクトを変更するために1つのチャートに制限されることは望ましくありません。私達には、各チャートでインジケータのコピーを起動させる必要があります。この作業は、ChartIndicatorAdd関数のおかげで、独立して行うことも、自動化することもできます。

上記をすべて考慮した上で、私はプログラムの実施のために2番目の方法を選びました。このようにして、インジケータの作業は2つのブロックに分けることができます。

  1. インジケータが起動すると、開いているチャートがシンボルでフィルタリングされます。対応するツールの開いているチャート上にインジケータがあるかどうかがチェックされます。現在のチャートのすべてのオブジェクトが、選択したチャートに複製されます。
  2. 初期化プロセスのチャート

  3. チャートイベントの処理。グラフィックオブジェクトの作成または変更のイベントが出現すると、プログラムはチャートから変更されたオブジェクトに関する情報を読み取り、以前に作成されたリストのすべてのチャートにこのデータを転送します。

イベント処理のプロセス。

私たちのプログラムのプロセスで、ユーザーはチャートの開閉を行うことができます。したがって、チャートリストの実用性を維持するために、私は一定の周期を持ったタイマーの最初のプロセスを起動させることを提案したいと思います。


2. チャートによる作業の構成

最初のプロセスは、チャートへのインジケータの複製です。この問題を解決するために、クラスCCloneIndyを作成します。この変数に、ツール/インジケータ名、そしてインジケータを呼び出す方法が保存されます。パブリッククラスには、必要なチャートを選別し、そこへ元のチャートの識別子を渡し、選択したチャートの配列を返すSearchCharts関数があります。

class CCloneIndy
  {
private:
   string            s_Symbol;
   string            s_IndyName;
   string            s_IndyPath;

public:
                     CCloneIndy();
                    ~CCloneIndy();
   bool              SearchCharts(long chart,long &charts[]);

protected:
   bool              AddChartToArray(const long chart,long &charts[]);
   bool              AddIndicator(const long master_chart,const long slave_chart);
  };

クラスの初期化時に、元のデータが変数に保存され、インジケータの略称を指定します。インジケータのコピーの実行時に必要なパラメータを取得する為にこれが必要になります。

CCloneIndy::CCloneIndy()
  {
   s_Symbol=_Symbol;
   s_IndyName=MQLInfoString(MQL_PROGRAM_NAME);
   s_IndyPath=MQLInfoString(MQL_PROGRAM_PATH);
   int pos=StringFind(s_IndyPath,"\\Indicators\\",0);
   if(pos>=0)
     {
      pos+=12;
      s_IndyPath=StringSubstr(s_IndyPath,pos);
     }
   IndicatorSetString(INDICATOR_SHORTNAME,s_IndyName);
  }

2.1. ツールによってチャートを選択する関数

必要なチャートを選択する関数がどのように機能するかを詳しく見ていきましょう。最初に、関数パラメータ内で送信されたマスターチャートの識別子を確認します。これが有効でない場合、関数は直ちにfalseを返します。識別子が指定されていない場合は、現在のチャートの識別子をパラメータに割り当てます。次に、保存されたツール名がマスターチャートのシンボルと一致しているかどうかを確認します。不一致がある場合は、チャートをフィルタリングするためのツール名を再登録します。

bool CCloneIndy::SearchCharts(long chart,long &charts[])
  {
   switch((int)chart)
     {
      case -1:
        return false;
        break;
      case 0:
        chart=ChartID();
        break;
      default:
        if(s_Symbol!=ChartSymbol(chart))
           s_Symbol=ChartSymbol(chart);
        break;
     }

次のステップでは、開いているすべてのチャートの選別を行います。テストしたチャートの識別子がマスタチャートの識別子と等しい場合は、次のステップに進みます。

   long check_chart=ChartFirst();
   while(check_chart!=-1)
     {
      if(check_chart==chart)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

次に、分析されたチャートのシンボルが、探しているものと一致するかどうかを確認します。ツールが検索条件を満たさない場合は、次のチャートに進みます。

      if(ChartSymbol(check_chart)!=s_Symbol)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

その後、チェックするチャートにインジケータがあるかどうかを確認します。すでにある場合は、チャートの識別子を配列に保存し、次に移ります。

      int handl=ChartIndicatorGet(check_chart,0,s_IndyName);
      if(handl!=INVALID_HANDLE)
        {
         AddChartToArray(check_chart,charts);
         check_chart=ChartNext(check_chart);
         continue;
        }

チェックしているチャートにまだインジケータがない場合は、マスターチャートとチェックしているチャートの識別子を指定して、呼び出しをする関数を起動します。操作に失敗した場合は、次のチャートに進み、次回の関数呼び出し時に、このグラフへのインジケータのアタッチを試みます。

      if(!AddIndicator(chart,check_chart))
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

インジケータがグラフに正常にアタッチされた場合には、このグラフの識別子を配列に追加します。この時マスターチャートからチェックするチャートへ全てのオブジェクトを複製する関数が起動します。

      AddChartToArray(check_chart, charts);
      check_chart=ChartNext(check_chart);
     }
//---
   return true;
  }

ループの最後に関数を終了し、trueを返します。

2.2. インジケータを呼び出す関数

インジケータをチャートに設定する関数について説明します。パラメータで、マスターチャートや受信チャートの識別子を受け取ります。それらの妥当性は、関数の始めにチェックされます。識別子は有効であり、違うものでなければいけません。

bool CCloneIndy::AddIndicator(const long master_chart,const long slave_chart)
  {
   if(master_chart<0 || slave_chart<=0 || master_chart==slave_chart)
      return false;

次に、マスターチャート上でインジケータハンドルを取得します。無効な場合は、結果をfalseにして関数を終了します。

   int master_handle=ChartIndicatorGet(master_chart,0,s_IndyName);
   if(master_handle==INVALID_HANDLE)
      return false;

ハンドルが有効な場合、新しいチャートに同様のインジケータを呼び出すためのパラメータを取得します。パラメータが取得できないエラーがでた場合、関数は結果をfalseとして終了します。

   MqlParam params[];
   ENUM_INDICATOR type;
   if(IndicatorParameters(master_handle,type,params)<0)
      return false;

次のステップでは、パラメータ配列の最初のセルに、初期化時に保存されたインジケータを呼び出す為のパスを記録し、二つ目にインジケータが動作するチャートの識別子を入れます。受信チャートの時間枠を認識し、インジケータを呼び出します。インジケータの呼び出しに失敗した場合、関数は結果をfalseとして終了します。

   params[0].string_value=s_IndyPath;
   params[1].integer_value=slave_chart;
   ENUM_TIMEFRAMES Timeframe=ChartPeriod(slave_chart);
   int slave_handle=IndicatorCreate(s_Symbol,Timeframe,type,ArraySize(params),params);
   if(slave_handle<0)
      return false;

関数の終わりに、取得されたハンドルによって受信チャートへインジケータを追加します。

   return ChartIndicatorAdd(slave_chart,0,slave_handle);
  }

おそらく読者の中には、なぜすぐに受信チャートにマスターチャートのハンドルでインジケータを設置できないのかという疑問が浮かぶのではないでしょうか。この質問に対する答えは簡単です。これを行うには、チャートとインジケータがツールと時間枠に対応していなければなりません。私たちのタスクによって、チャートの時間枠は異なってきます。

クラスのソースコードの詳細は、添付ファイルをご参照ください。

3.グラフィックオブジェクトを扱うクラス

私たちのプログラムの次のプロセスは、イベント処理とグラフィックオブジェクトに関するデータを他のチャートへ転送することです。コードを書く前に、チャート間でデータを転送する方法を明確にする必要があります。

MQL5ツールを使用すると、グラフィックオブジェクトを扱う関数でチャートの識別子を指定することによって、チャートのプログラムが、別のチャートでオブジェクトの作成および変更を行うことができるようになります。これは、少数のチャートやグラフィックオブジェクトを扱うのに適しています。

しかし、別の方法もあります。以前は、チャート上のオブジェクトの変更を追跡する為のイベントを使用していました。私たちはすでに、私たちの関心があるすべてのチャートにインジケータのコピーを追加するコードを書きました。様々なチャート上におけるインジケータ間で変化したオブジェクトのデータを引き渡すのに、イベントモデルを使用しない理由はありません。チャート上にあるインジケータにオブジェクトを扱う操作を与えます。これにより、すべてのインジケータ間でのオブジェクトを扱う作業の割当てや、非同期モデルの作成が可能になります。

これは聞こえは良いですが、ご存知の通り、OnChartEvent関数は4つのパラメータしか取得しません。

オブジェクトに関するすべての情報をこれらの4つのパラメータに入れるにはどうしたらよいでしょうか。私達はイベント識別子を引き渡し、オブジェクトに関するすべての情報をstring型のパラメータに書き込むことにします。オブジェクトに関する情報をstring型の1つの変数に集めるために、『端末間のデータ交換にクラウドストレージを使用する』の記事の技術を採用したいと思います。

グラフィックオブジェクトに関するデータを収集し、チャートに表示するCCloneObjectsクラスを作成します。

class CCloneObjects
  {
private:
   string            HLineToString(long chart, string name, int part);
   string            VLineToString(long chart, string name, int part);
   string            TrendToString(long chart, string name, int part);
   string            RectangleToString(long chart, string name, int part);
   bool              CopySettingsToObject(long chart,string name,string &settings[]);

public:
                     CCloneObjects();
                    ~CCloneObjects();
//---
   string            CreateMessage(long chart, string name, int part);
   bool              DrawObjects(long chart, string message);
  };

このクラスの関数の動作についてはこちらに詳述されているので、ここでさらに説明を書く必要はないと思います。しかし、1つのニュアンスに注意を払う必要があります。EventChartCustom関数によるカスタムイベントの生成時に、paramパラメータの長さは63文字に制限されることです。したがって、オブジェクトに関するデータの他のチャートへの転送時に、メッセージを2つに分割します。この為に、データの必要な部分を指定する為のパラメータが、メッセージを作成する関数に追加されました。一例として、トレンドラインの情報を収集する関数のコードを以下に書きます。

string CCloneObjects::TrendToString(long chart,string name, int part)
  {
   string result = NULL;
   if(ObjectFind(chart,name)!=0)
      return result;
   
   switch(part)
     {
      case 0:
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=0="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,0),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,0))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=1="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,1),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=1="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,1))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_LEFT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_LEFT))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_RIGHT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_RIGHT))+"|";
        break;
      default:
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_COLOR)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_COLOR))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_WIDTH)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_WIDTH))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_STYLE)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_STYLE))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_BACK)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_BACK))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TEXT)+"="+ObjectGetString(chart,name,OBJPROP_TEXT)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TOOLTIP)+"="+ObjectGetString(chart,name,OBJPROP_TOOLTIP);
        break;
     }
   return result;
  }

すべての関数のコードの詳細については、記事に添付したファイルから参照することができます。


4.インジケータを収集する

全ての準備が完了したので、次はグラフィックオブジェクトの追跡と複製の為のインジケータを収集します。私達のインジケータのパラメータは、チャートの識別子1つだけになります。

sinput long    Chart =  0;

インジケータの起動時に、このパラメータの値は常にゼロのままでなければなりません。読者はきっとこのような質問をするのではないでしょうか。なぜ決して変わらないパラメータを出力するのかと。

インジケータがプログラムから呼び出され、他のチャートにアタッチされると、その値が変化します。

実際のところ、ChartID関数は、インジケータがアタッチされているチャートの識別子が返されるのではなく、インジケータが呼び出されたチャートの識別子を返します。これは、MetaTrader 5のインジケータの処理の特性によるものです。1つのインジケータと同じインジケータで、1つのツールと時間枠で何回か呼び出される場合、最初に呼び出されるのは1回だけになります。それをさらに呼び出すと、他のチャートからでも、既に実行されているインジケータを呼び出します。次に、インジケータはそのチャート上で動作し、そのチャートに関する情報を返します。したがって、CCloneIndyクラスのインジケータインスタンスを呼び出すと、インジケータの新しいコピーが起動し、インジケータの最初のインスタンスが実行されたチャートに関する情報が返されます。これを避けるためには、各インジケータインスタンスに、動作をするチャートを具体的に指定する必要があります。

インジケータのコードをより詳細に見ていきましょう。グローバル変数ブロックでは、次のように宣言します。

CCloneIndy    *CloneIndy;
CCloneObjects *CloneObjects;
long           l_Chart;
long           ar_Charts[];

OnInit関数では、チャートやオブジェクトを扱う為のクラスのインスタンスを初期化します。

int OnInit()
  {
//--- indicator Create classes
   CloneIndy   =  new   CCloneIndy();
   if(CheckPointer(CloneIndy)==POINTER_INVALID)
      return INIT_FAILED;
   CloneObjects=  new CCloneObjects();
   if(CheckPointer(CloneObjects)==POINTER_INVALID)
      return INIT_FAILED;

動作しているチャートの識別子を初期化します。

   l_Chart=(Chart>0 ? Chart : ChartID());

ツールで開いているチャートの検索を行います。同時に、検出されたチャートに必要に応じてインジケータのコピーが追加されます。

   CloneIndy.SearchCharts(l_Chart,ar_Charts);

関数の最後に、タイマーを10秒間隔で初期化します。タイマーの唯一のタスクは、オブジェクトを複製するためのチャートのリストを更新することです。

   EventSetTimer(10);
//---
   return(INIT_SUCCEEDED);
  }

OnCalculate関数では、何も実行されません。上で述べた通り、私達のインジケータはイベントモデル上で設定されています。したがって、インジケータのすべての機能はOnChartEvent関数に集中します。関数の初めに、補助的なローカル変数を宣言します。

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   string message1=NULL;
   string message2=NULL;
   int total=0;

次に、switchステートメントで、着信イベントに応じて分岐操作を作成します。

最初の操作ブロックは、オブジェクトの作成または変更に関する情報を収集し、他のチャートに転送します。これは、チャート上にオブジェクトを作成、変更、または移動するイベントによって呼び出されます。これらのイベントが発生すると、インジケータはオブジェクトの状態を示す2つのメッセージを作成し、ループを実行して配列からの識別子を使用してすべてのチャートにそれらを送信します。

   switch(id)
     {
      case CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_OBJECT_DRAG:
        message1=CloneObjects.CreateMessage(l_Chart,sparam,0);
        message2=CloneObjects.CreateMessage(l_Chart,sparam,1);
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
          {
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message1);
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message2);
          }
        break;

次のブロックは、オブジェクトがチャートから削除されたときに起動されます。この場合、名前だけでオブジェクトを削除することができ、既にsparam変数にその名前があるので、別のメッセージを準備する必要はありません。したがって、すぐに他のチャートにメッセージを送信するサイクルを開始します。

      case CHARTEVENT_OBJECT_DELETE:
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,sparam);
        break;

次の2つのブロックは、他のチャートから受信したメッセージを処理するためのブロックです。オブジェクトの作成や変更に関する情報を受け取ると、そのオブジェクトをチャートに出力する関数が呼び出されます。関数のパラメータでは、動いているチャートの識別子と受信したメッセージを送信します。

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DRAG:
        CloneObjects.DrawObjects(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;

オブジェクトの削除に関する情報を受け取ると、動いているチャート上の同様のオブジェクトを削除する関数が呼び出されます。

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DELETE:
        ObjectDelete(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;
     }
  }

インジケータのコードと使用したクラスについての詳細は添付ファイルからご参照いただくことができます。


まとめ

この記事では、ターミナルのチャート間でグラフィックオブジェクトをリアルタイムで自動的にコピーするインジケータを構築する技術を紹介しました。同時に、ターミナルで開いているチャート間の双方向のデータ交換が実装されています。この技術は、同期するチャートの数を制限しません。またユーザーは、同期化されたチャートのいずれかでグラフィカルオブジェクトを作成、変更、および削除することができます。インジケータの動作は下のビデオで見ることができます。



リンク

  1. 端末間のデータ交換にクラウドストレージを使用する

記事で使用されているプログラム:

#
 名前
タイプ 
説明 
1 ChartObjectsClone.mq5  インディケータ  チャート間でデータを交換するインジケータ
2 CloneIndy.mqh  クラスライブラリ  ツールでのチャートを選択する為のクラス
3 CloneObjects.mqh  クラスライブラリ  グラフィックオブジェクトを扱うクラス
4 ChartObjectsClone.mqproj    プロジェクト記述ファイル