MQL4でオブジェクトを同期する方法【MT4インジケーター】

プログラミング

MQL4でインジケーターを作成する際、チャートオブジェクトを同期したい場合があるかと思います。

例えば、マルチタイムフレーム分析を想定しているときに、水平線を引いたチャート以外のチャートにも同じ水平線を表示したい等です。

筆者はオブジェクトを同期するインジケーターを販売しておりますが、作成する際に悩んだ部分がありましたので、実装方法を記載しておこうと思います。

この記事で分かること

  1. MQL4でチャートオブジェクトを同期する方法が分かる
  2. 同期する際の特有の悩みを解消できる
スポンサーリンク

ソースコードの全貌

まずはソースコードの全貌です。

コピペしてそのままご利用いただけます。(コピーライトやリンクは改変していただいて構いません)

※当ソースコードを使用して発生した損害に対する責任は一切負いません。

//+------------------------------------------------------------------+
//|                                            SyncObjectsSample.mq4 |
//|                                             エンジニア投資家の記録 |
//|                                            https://engistor.com/ |
//+------------------------------------------------------------------+
#property copyright "エンジニア投資家の記録"
#property link "https://engistor.com/"
#property version "1.00"
#property description "表示している全ての同一通貨ペアのチャートのオブジェクトを同期します。"
#property strict
#property indicator_chart_window

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
  ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
  return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 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[])
{
  return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
  if(id == CHARTEVENT_OBJECT_CREATE)
  {
    if(ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_HLINE
       && StringFind(sparam, "Horizontal Line ") != -1
       && !ExistsObjectInOtherChart(sparam))
    {
      CreateObjectForAllCharts(OBJ_HLINE, sparam);
    }
  }
}

//+------------------------------------------------------------------+
//| 全てのチャートに指定したオブジェクトを作成する
//|
//| return void
//+------------------------------------------------------------------+
void CreateObjectForAllCharts(const ENUM_OBJECT type = OBJ_HLINE // オブジェクトの種類
                              , const string name = "" // 名前
                             )
{
  if(name == "")
    return;

  double price = ObjectGetDouble(0, name, OBJPROP_PRICE, 0);

  long chart_id = ChartFirst();
  while(true)
  {
    if(chart_id != ChartID() && ChartSymbol(chart_id) == _Symbol)
    {
      if(ObjectFind(chart_id, name) == -1)
      {
        ObjectCreate(chart_id, name, type, 0, 0, price);
        ChartRedraw(chart_id);
        CopyObjectProp(0, chart_id, name);
      }
    }

    chart_id = ChartNext(chart_id);
    if(chart_id < 0)
      break;
  }
}

//+------------------------------------------------------------------+
//| オブジェクトのプロパティをコピーする
//|
//| return void
//+------------------------------------------------------------------+
void CopyObjectProp(const long chart_id_from = 0 // コピー元チャートID
                    , const long chart_id_to = 0 // コピー先チャートID
                    , const string name = "" // コピーするオブジェクト名
                   )
{
  if(chart_id_from == chart_id_to || name == "")
    return;

  color clr = (color)ObjectGetInteger(chart_id_from, name, OBJPROP_COLOR);
  ENUM_LINE_STYLE style = (ENUM_LINE_STYLE)ObjectGetInteger(chart_id_from,name, OBJPROP_STYLE);
  int width = (int)ObjectGetInteger(chart_id_from, name, OBJPROP_WIDTH);

  ObjectSetInteger(chart_id_to, name, OBJPROP_COLOR, clr);
  ObjectSetInteger(chart_id_to, name, OBJPROP_STYLE, style);
  ObjectSetInteger(chart_id_to, name, OBJPROP_WIDTH, width);

  ChartRedraw(chart_id_to);
}

//+------------------------------------------------------------------+
//| 指定したオブジェクトが他のチャートに存在するか
//|
//| return bool true: 存在する / false: 存在しない
//+------------------------------------------------------------------+
bool ExistsObjectInOtherChart(const string name)
{
  long chart_id = ChartFirst();
  while(true)
  {
    if(chart_id != ChartID() && ChartSymbol(chart_id) == _Symbol)
    {
      if(ObjectFind(chart_id, name) != -1)
      {
        return true;
      }
    }

    chart_id = ChartNext(chart_id);
    if(chart_id < 0)
      break;
  }
  return false;
}
//+------------------------------------------------------------------+

一つずつ見ていきましょう。

ヘッダー部分

まずはヘッダー部分です。

プログラムの概要や、基本的な部分を定義しております。

//+------------------------------------------------------------------+
//|                                            SyncObjectsSample.mq4 |
//|                                             エンジニア投資家の記録 |
//|                                            https://engistor.com/ |
//+------------------------------------------------------------------+

// (1)
#property copyright "エンジニア投資家の記録"
#property link "https://engistor.com/"
#property version "1.00"
#property description "表示している全ての同一通貨ペアのチャートのオブジェクトを同期します。"

// (2)
#property strict

// (3)
#property indicator_chart_window

(1)

インジケーターの説明になります。

インジケーターをチャートに適用する際にダイアログの「バージョン情報」タグに表示される情報になります。

今回のサンプルの場合、以下のように表示されます。

(2)

コンパイルモードの指定です。

厳密にコンパイルするようになります。

こちらは付けておいたほうが良いでしょう。

(3)

チャート上のどこに表示するかを指定します。

指定の方法は以下2通りあります。

  1. indicator_chart_window
  2. indicator_separate_window

    indicator_chart_window はメインウィンドウに表示する設定です。

    例えば移動平均線などのトレンド系のインジケーターはこちらに該当します。

    indicator_separate_window はサブウィンドウに表示する設定です。

    例えば RSI などのオシレーター系のインジケーターはこちらに該当します。

    OnInit

    続いて、OnInit 関数です。

    MQL4 を作成する際にデフォルトで搭載されている関数で、インジケーターをチャートに適用した際に処理される関数になります。

    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
    {
      // (1)
      ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
    
      return(INIT_SUCCEEDED);
    }

    (1)

    チャートが変更された際のイベントを検知するかどうかの設定になります。

    今回のサンプルの場合は、チャートオブジェクトが生成されるイベントを検知するように設定しております。

    OnCalculate

    続いて、OnCalculate 関数です。

    MQL4 を作成する際にデフォルトで搭載されている関数で、ティックが動くたびに処理される関数になります。

    //+------------------------------------------------------------------+
    //| 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[])
    {
      return(rates_total);
    }

    今回はティックが動いても関係ないのでデフォルトの状態のままになっております。

    当関数を使用するケースとしては、移動平均線のような価格を元に算出されるインジケーターを作成する場合は使用することになります。

    OnChartEvent

    続いて、OnChartEvent 関数です。

    MQL4 を作成する際にデフォルトで搭載されている関数で、チャートにイベントが発生した際に処理される関数になります。

    イベントとは例えばオブジェクトを生成したり削除することを指しております。

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
    {
      // (1)
      if(id == CHARTEVENT_OBJECT_CREATE)
      {
        // (2)
        if(ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_HLINE
           && StringFind(sparam, "Horizontal Line ") != -1
           && !ExistsObjectInOtherChart(sparam))
        {
          // (3)
          CreateObjectForAllCharts(OBJ_HLINE, sparam);
        }
      }
    }

    (1)

    チャートイベントがオブジェクトを生成であるかどうかを判定しております。

    オブジェクトを生成した場合のみ処理を行います。

    (2)

    ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_HLINE の部分で生成されたオブジェクトの種類が水平線かどうかを判定しております。

    sparam についてはオブジェクトの名称が入ってきます。

    例えば水平線の場合は「Horizontal Line 41745」のような値になり「41745」の部分は固有の ID のようなものが付きますので、必ず一意の値になります。

    StringFind(sparam, “Horizontal Line “) != -1 部分でオブジェクトの名称に「Horizontal Line 」が含まれているかどうかを判定しております。

    !ExistsObjectInOtherChart(sparam)  の部分で他のチャートに生成されたオブジェクトが存在していないかを判定しております。

    ここが悩みポイントだったのですが、この判定をしない場合は後述する CreateObjectForAllCharts 関数のループ処理によって他のチャートにオブジェクトを生成しますが、その際に OnChartEvent 関数が発動してしまい、余計な処理をすることになってしまいます。

    (3)

    他のすべてのチャートに同じオブジェクトを生成する処理です。

    詳細は次項で解説します。

    CreateObjectForAllCharts

    続いて、CreateObjectForAllCharts 関数です。

    独自で作成した関数になります。

    //+------------------------------------------------------------------+
    //| 全てのチャートに指定したオブジェクトを作成する
    //|
    //| return void
    //+------------------------------------------------------------------+
    void CreateObjectForAllCharts(const ENUM_OBJECT type = OBJ_HLINE // オブジェクトの種類
                                  , const string name = "" // 名前
                                 )
    {
      if(name == "")
        return;
    
      // (1)
      double price = ObjectGetDouble(0, name, OBJPROP_PRICE, 0);
    
      // (2)
      long chart_id = ChartFirst();
      while(true)
      {
        // (3)
        if(chart_id != ChartID() && ChartSymbol(chart_id) == _Symbol)
        {
          // (4)
          if(ObjectFind(chart_id, name) == -1)
          {
            // (5)
            ObjectCreate(chart_id, name, type, 0, 0, price);
            ChartRedraw(chart_id);
    
            // (6)
            CopyObjectProp(0, chart_id, name);
          }
        }
    
        // (7)
        chart_id = ChartNext(chart_id);
        if(chart_id < 0)
          break;
      }
    }

    (1)

    オブジェクトの価格を取得しています。

    今回は水平線なので、価格を1つだけ取得しておりますが、オブジェクトの種類によって取得する必要のあるものが変わります。

    垂直線の場合は時間が1つ必要になりますし、トレンドラインの場合は時間と価格が2つずつ必要になります。

    オブジェクトのプロパティを見るとどの値が何個必要かを調べることができます。

    (2)

    一番最初のチャートIDを取得しています。

    そして、開いているチャートの分だけループ処理を行います。

    (3)

    chart_id != ChartID() の部分で処理中のチャートIDがオブジェクトを生成したチャートIDと異なるかどうかを判定しております。

    オブジェクトを生成したチャートIDの場合は、すでに手動で生成したオブジェクトがあるはずなので、改めてオブジェクトを作成し直す必要がないからです。

    ChartSymbol(chart_id) == _Symbol の部分でオブジェクトを生成したチャートと処理中のチャートの通貨ペアが同じかどうかの判定をしております。

    (4)

    同名のオブジェクトが既に存在しないかどうか判定しております。

    (5)

    オブジェクトを生成しております。

    この時点で元々生成した水平線と同じ価格の水平線が他のチャートに生成されます。

    水平線の色やスタイルはまだ指定していないので、違う色やスタイルになってしまう可能性があります。

    (6)

    元々生成したオブジェクトのプロパティをコピーする処理です。

    これを実行することで水平線の色やスタイルがコピーされます。

    (7)

    次のチャートIDを取得しています。

    チャートがなくなるまでループが継続されます。

    CopyObjectProp

    続いて、CopyObjectProp 関数です。

    独自で作成した関数になります。

    //+------------------------------------------------------------------+
    //| オブジェクトのプロパティをコピーする
    //|
    //| return void
    //+------------------------------------------------------------------+
    void CopyObjectProp(const long chart_id_from = 0 // コピー元チャートID
                        , const long chart_id_to = 0 // コピー先チャートID
                        , const string name = "" // コピーするオブジェクト名
                       )
    {
      if(chart_id_from == chart_id_to || name == "")
        return;
    
      // (1)
      color clr = (color)ObjectGetInteger(chart_id_from, name, OBJPROP_COLOR);
      ENUM_LINE_STYLE style = (ENUM_LINE_STYLE)ObjectGetInteger(chart_id_from,name, OBJPROP_STYLE);
      int width = (int)ObjectGetInteger(chart_id_from, name, OBJPROP_WIDTH);
    
      // (2)
      ObjectSetInteger(chart_id_to, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(chart_id_to, name, OBJPROP_STYLE, style);
      ObjectSetInteger(chart_id_to, name, OBJPROP_WIDTH, width);
    
      ChartRedraw(chart_id_to);
    }
    

    (1)

    元々生成したオブジェクトの色、スタイル、幅を取得しております。

    (2)

    (1)で取得した色、スタイル、幅をコピー先のチャートのオブジェクトに設定します。

    ExistsObjectInOtherChart

    続いて、ExistsObjectInOtherChart 関数です。

    独自で作成した関数になります。

    //+------------------------------------------------------------------+
    //| 指定したオブジェクトが他のチャートに存在するか
    //|
    //| return bool true: 存在する / false: 存在しない
    //+------------------------------------------------------------------+
    bool ExistsObjectInOtherChart(const string name)
    {
      long chart_id = ChartFirst();
      while(true)
      {
        if(chart_id != ChartID() && ChartSymbol(chart_id) == _Symbol)
        {
          if(ObjectFind(chart_id, name) != -1)
          {
            return true;
          }
        }
    
        chart_id = ChartNext(chart_id);
        if(chart_id < 0)
          break;
      }
      return false;
    }
    //+------------------------------------------------------------------+

    開いているチャートをループで処理している部分については CreateObjectForAllCharts 関数と同じなので割愛します。

    OnChartEvent 関数の(2)の部分でも解説しましたが、当関数がないと余計な処理をしてしまうのでパフォーマンスが悪くなりますし、意図しない動きをする可能性があります。

    おわり

    以上、この記事がご参考になれば幸いです。

    コメント

    タイトルとURLをコピーしました