[MT5/MQL5] 非同期のオブジェクト関数が異常に遅い件

そろそろMT5移行を本格化しようかと思いMT5(build 2690)を導入したところ、オブジェクトを多用するインジケーターが異常に遅い問題が発生しました。プログラムから制御するオブジェクトの挙動がいちいち遅いのです。。

問題のボトルネックを調査したところ、どうやらオブジェクトの作成関数(ObjectCreate)とプロパティを変更する関数(ObjectSetInteger)の動きが遅いことがわかりました(後述しますがこれらの関数以外にもあてはまる問題かと思われます)。これらの関数は非同期関数ですぐに実行される保証はありません(以下公式ドキュメント参照)。

ObjectSetIntegerの注意事項
MQL5公式ドキュメント抜粋 (ObjectSetInteger) https://www.mql5.com/ja/docs/objects/objectsetinteger

再現調査

まずは下記のようなObjectCreate関数とObjectSetInteger関数を使う簡単なテスト用インジケーターを作って、再現調査および改善策を考えることにしました。このインジケーターは単にマウスカーソルにラベルが追随するだけのものです。問題となる非同期関数は赤字部分です。

#property indicator_chart_window
#define OBJN "TEST_LABEL"

//+------------------------------------------------------------------+
//| Custom indicator initialization function                                     |
//+------------------------------------------------------------------+
int OnInit() {
  return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//|                                                                                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
  ObjectDelete(0, OBJN);
}

//+------------------------------------------------------------------+
//| 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_MOUSE_MOVE) {
    int x = (int)lparam;
    int y = (int)dparam;

    //ラベルの作成またはカーソルに追随
    if(ObjectFind(0, OBJN) == -1) {
      CreateLabel(0, OBJN, 0, x, y, CORNER_LEFT_UPPER, "LABEL");
    } else {
      ObjectSetInteger(0, OBJN, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(0, OBJN, OBJPROP_YDISTANCE, y);
    }
  }
}

//+------------------------------------------------------------------+
//| ラベルを作成する                                                                        |
//+------------------------------------------------------------------+
bool CreateLabel(const long              chart_ID = 0,             // チャート識別子
                 const string            name = "Label",           // ラベル名
                 const int               sub_window = 0,           // サブウィンドウ番号
                 const int               x = 0,                   // X 座標
                 const int               y = 0,                   // Y 座標
                 const ENUM_BASE_CORNER  corner = CORNER_LEFT_UPPER, // アンカーに使用されるチャートのコーナー
                 const string            text = "Label",           // テキスト
                 const string            font = "Arial",           // フォント
                 const int               font_size = 10,           // フォントサイズ
                 const color             clr = clrRed,             // 色
                 const double            angle = 0.0,             // テキストの傾斜
                 const ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER, // アンカーの種類
                 const bool              back = false,             // 背景で表示する
                 const bool              selection = false,       // 強調表示して移動
                 const bool              hidden = true,           // オブジェクトリストに隠す
                 const long              z_order = 0) {           // マウスクリックの優先順位
//--- エラー値をリセットする
  ResetLastError();
//--- ラベルを作成する
  if(!ObjectCreate(chart_ID, name, OBJ_LABEL, sub_window, 0, 0)) {
    Print(__FUNCTION__,
          ": failed to create text label! Error code = ", GetLastError());
    return(false);
  }
//--- ラベル座標を設定する
  ObjectSetInteger(chart_ID, name, OBJPROP_XDISTANCE, x);
  ObjectSetInteger(chart_ID, name, OBJPROP_YDISTANCE, y);
//--- ポイント座標が相対的に定義されているチャートのコーナーを設定
  ObjectSetInteger(chart_ID, name, OBJPROP_CORNER, corner);
//--- テキストを設定する
  ObjectSetString(chart_ID, name, OBJPROP_TEXT, text);
//--- テキストフォントを設定する
  ObjectSetString(chart_ID, name, OBJPROP_FONT, font);
//--- フォントサイズを設定する
  ObjectSetInteger(chart_ID, name, OBJPROP_FONTSIZE, font_size);
//--- テキストの傾斜を設定する
  ObjectSetDouble(chart_ID, name, OBJPROP_ANGLE, angle);
//--- アンカーの種類を設定
  ObjectSetInteger(chart_ID, name, OBJPROP_ANCHOR, anchor);
//--- 色を設定
  ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
//--- 前景(false)または背景(true)に表示
  ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);
//--- マウスでラベルを移動させるモードを有効(true)か無効(false)にする
  ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, selection);
  ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, selection);
//--- オブジェクトリストのグラフィックオブジェクトを非表示(true)か表示(false)にする
  ObjectSetInteger(chart_ID, name, OBJPROP_HIDDEN, hidden);
//--- チャートのマウスクリックのイベントを受信するための優先順位を設定する
  ObjectSetInteger(chart_ID, name, OBJPROP_ZORDER, z_order);
//--- 実行成功
  return(true);
}
//+------------------------------------------------------------------+

 

こんなシンプルなインジケーターですらカーソルを動かしてから数秒待たないとラベルが追随しないほどのパフォーマンス問題が再現してしまいました。

原因はチャートキューの実行遅延にあると想定したのでプログラムはいじらず何かMT5側の調整で改善できないかと考えていたのですが、うまくいきませんでした。仕方ないので下記のプログラム修正で一応改善しました。ただ同じプログラムをMT4で動かすともう少し速いので、まだ改善の余地はあるかもしれません。

改善策

実はMQL5の公式ドキュメントに改善のヒントがあります。それは以下ドキュメント抜粋の赤下線箇所にあるChartRedraw関数の利用になります。この関数はチャートキューでの実行を待たずにオブジェクトを更新してくれます。

オブジェクト関数
MQL5公式ドキュメント抜粋 (オブジェクト関数) https://www.mql5.com/ja/docs/objects

 

果たしてテスト用インジケーターの非同期オブジェクト関数の後にChartRedraw関数を追加すると無事改善されました。

if(ObjectFind(0, OBJN) == -1) {
  CreateLabel(0, OBJN, 0, x, y, CORNER_LEFT_UPPER, "LABEL");
} else {
  ObjectSetInteger(0, OBJN, OBJPROP_XDISTANCE, x);
  ObjectSetInteger(0, OBJN, OBJPROP_YDISTANCE, y);
}
ChartRedraw(0);

まとめ

本記事の問題はMQL5の非同期オブジェクト関数全般に言える問題なので、取り上げたObjectCreate関数とObjectSetInteger関数だけではなくObjectMove関数などの非同期関数も関わってくる話だと思われます。

改善策の結論は、非同期関数の後にChartRedraw関数を実行するというものです。昔はChartRedraw関数を適宜挿入していたのですが、いつの間にか意識しなくなっていたのも個人的には問題でした。

sponsor