FX自動売買基礎と応用

簡易的なペイントツールを作成する方法【MQLプログラミングの基礎】


1.ファイルの新規作成で色とサイズのパラメーターを設定


この記事では、簡易的なペイントツールを作る方法を解説します。ツールの機能としては、次の三つを設けます。

  1. ①キーボードのPキーの操作で、ツールのON/OFFのスイッチが切り替わる。
  2. ②スイッチをONにしたときに、マウスをチャート上でドラッグすると線が描画される。
  3. ③キーボードのDキーの操作で、描画した線を消去。

まずファイルの新規作成で「カスタムインディケータ」を選択し、ファイル名を「EasyPaint」とします。そして色とサイズのパラメーターを追加しましょう。色は名前を「CLR」、タイプを「color」、初期値を「White」、サイズは名前を「SIZE」、タイプを「int」、初期値を「5」とします。「カスタムインディケータのイベントハンドラ」の画面では「OnChartEvent」にチェックを入れ、次の画面で「完了」をクリックすれば、ひな形の完成です。

「カスタムインディケータのイベントハンドラ」の画面


2.オブジェクトをまとめて削除できるように接頭辞を定義


今回はオブジェクトを使うので、ファイル上部のプロパティ「#property indicator_chart_window」の下で接頭辞「PREFIX」を定義しましょう。これは作ったオブジェクトをまとめて削除できるようするためです。「MQLInfoString(MQL_PROGRAM_NAME)」はインジケーターのファイル名を示します。


#define PREFIX MQLInfoString(MQL_PROGRAM_NAME) + "_"

そして「Custom indicator initialization function」の下に「Custom indicator deinitialization function」を設けて、OnDeinit関数を使った次のコードを記述します。これでインジケーターをチャートから削除したときに、関連オブジェクトが消えるようになります。


void OnDeinit(const int reason)
{
   if (reason == REASON_REMOVE) ObjectsDeleteAll(0, PREFIX);
}

また、ファイル上部のサイズのパラメーター「input int SIZE = 5;」の下に、スイッチのON/OFFを確認するための変数と、マウスの位置を記憶する変数を登録します。


bool paint = false;
int posX, posY;


3.Pキーに割り当てられている番号を確認


続いて、OnChartEvent関数内にスイッチの機能を作っていきます。キーボードの操作を検知してPキーを押したときにスイッチのON/OFFを切り替えるようにしたいので、まずはPキーに割り当てられている番号を調べます。押されたキーの番号を返す「CHARTEVENT_KEYDOWN」を利用して番号を出力してみましょう。


if (id == CHARTEVENT_KEYDOWN) {
   Print(sparam);
}

これでコンパイルしてチャートにセットし、キーボードのPキーを押すと、ターミナルウィンドウの「エキスパート」タブにメッセージとして「25」が出力されます。この25がPキーに割り当てられている番号です。

ターミナルウィンドウの「エキスパート」タブにメッセージとして「25」が出力される


4.Pキーを押したときにスイッチのON/OFFを切り替える


上記では、スイッチのON/OFFの切り替えに利用する、Pキーに割り当てられた番号を確認することができました。続いて、確認した番号を用いて、Pキーを押したときに実行する処理を記述していきます。

まずif文を使ってsparamが「25」のときにスイッチを切り替えるようにします。スイッチがOFFのときはpaintをONに、スイッチがONのときはpaintをOFFにします。


if (id == CHARTEVENT_KEYDOWN) {
   if (sparam == "25") {
      if (!paint) {
         paint = true;
         } else {
            paint = false;
         }
      }
      Print(sparam);
   }
}


5.Labelオブジェクトで文字を表示


今回はスイッチの状況を一目で分かりやすくするため、Labelオブジェクトで文字を大きく表示しましょう。Labelオブジェクトのサンプルコードは、MQL4リファレンスからコピーして使います。MQL4リファレンスの目次にある「Constants, Enumerations and Structures」→「Objects Constants」→「Object Types」をクリックすると、オブジェクトの一覧が表示されます。その中から「OBJ_LABEL」を選択し、あらかじめ用意されている「Create a text label」のコードをコピーして、ファイル下部に貼り付けます。

「//— reset the error value」「ResetLastError();」の2行と、「Print(__FUNCTION__,」「”: failed to create text label! Error code = “,GetLastError());」の2行は不要なので削除してください。また、上部パラメーターの「// subwindow index」「// chart corner for anchoring」「// text slope」の3行も使わないので「//」をつけてコメント扱いとし、「if(!ObjectCreate(chart_ID, name, OBJ_LABEL, sub_window, 0, 0))」の「sub_window」のところと「ObjectSetDouble(chart_ID, name, OBJPROP_ANGLE, angle);」の「angle」のところを共に「0」に、「ObjectSetInteger(chart_ID, name, OBJPROP_CORNER, corner);」の「corner」のところを「CORNER_LEFT_UPPER」に変更します。


//+------------------------------------------------------------------+
//| Create a text label                                              |
//+------------------------------------------------------------------+
bool LabelCreate(const long              chart_ID = 0,             // chart's ID
                 const string            name = "Label",           // label name
//                 const int               sub_window = 0,           // subwindow index
                 const int               x = 0,                    // X coordinate
                 const int               y = 0,                    // Y coordinate
//                 const ENUM_BASE_CORNER  corner = CORNER_LEFT_UPPER, // chart corner for anchoring
                 const string            text = "Label",           // text
                 const string            font = "Arial",           // font
                 const int               font_size = 10,           // font size
                 const color             clr = clrRed,             // color
//                 const double            angle = 0.0,              // text slope
                 const ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER, // anchor type
                 const bool              back = false,             // in the background
                 const bool              selection = false,        // highlight to move
                 const bool              hidden = true,            // hidden in the object list
                 const long              z_order = 0)              // priority for mouse click
{
//--- create a text label
   if(!ObjectCreate(chart_ID, name, OBJ_LABEL, 0, 0, 0)) {
      return(false);
   }
//--- set label coordinates
   ObjectSetInteger(chart_ID, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(chart_ID, name, OBJPROP_YDISTANCE, y);
//--- set the chart's corner, relative to which point coordinates are defined
   ObjectSetInteger(chart_ID, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
//--- set the text
   ObjectSetString(chart_ID, name, OBJPROP_TEXT, text);
//--- set text font
   ObjectSetString(chart_ID, name, OBJPROP_FONT, font);
//--- set font size
   ObjectSetInteger(chart_ID, name, OBJPROP_FONTSIZE, font_size);
//--- set the slope angle of the text
   ObjectSetDouble(chart_ID, name, OBJPROP_ANGLE, 0);
//--- set anchor type
   ObjectSetInteger(chart_ID, name, OBJPROP_ANCHOR, anchor);
//--- set color
   ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
//--- display in the foreground (false) or background (true)
   ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);
//--- enable (true) or disable (false) the mode of moving the label by mouse
   ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, selection);
   ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, selection);
//--- hide (true) or display (false) graphical object name in the object list
   ObjectSetInteger(chart_ID, name, OBJPROP_HIDDEN, hidden);
//--- set the priority for receiving the event of a mouse click in the chart
   ObjectSetInteger(chart_ID, name, OBJPROP_ZORDER, z_order);
//--- successful execution
   return(true);
}

この状態でLabelCreateを使います。線を引く処理を記述する場所は、先ほどのif文内の「paint = true;」の下です。idは0で、名前は「PREFIX + “switch”」、Xは「5」、Yは「15」、テキストは「PAINT」、フォントは太字の「Arial Black」、サイズは「20」、カラーはパラメーターで指定した色、アンカーは左上、あとは初期値とします。


LabelCreate(0, PREFIX + "Switch", 5, 15, "PAINT", "Arial Black", 20, CLR, ANCHOR_LEFT_UPPER);

また、スイッチをOFFにしたときはLabelオブジェクトを消すようにしたいので、「paint = false;」の下にObjectDeleteを使った次のコードを記述します。


ObjectDelete(0, PREFIX + "Switch");

これでコンパイルしてPキーを押すと、チャートの左上に「PAINT」と表示されることが分かります。その状態でもう一度Pキーを押せば表示が消えます。

チャートの左上に「PAINT」と表示される


6.ドラッグしてもチャートが動かないように設定


次に、スイッチがONになったときに、「MOUSE_MOVE」を使って線を描画するようにします。ただ、チャート上でマウスをドラッグして線を引こうとすると、チャート自体が動いてしまうので、それを回避する必要があります。例えば、オブジェクトをドラッグしたときにはチャートは動かないので、その性質を利用してスイッチがONになったときにダミーのオブジェクトを入れると、チャートが動かないように設定できます。まずは「posX」に「lparam」、「posY」に「dparam」を入れます。


if (id == CHARTEVENT_MOUSE_MOVE) {
   posX = (int)lparam;
   posY = (int)dparam;

そして「LabelCreate(0, PREFIX + “Switch”, 5, 15, “PAINT”, “Arial Black”, 20, CLR, ANCHOR_LEFT_UPPER);」の下に次のコードを加えましょう。名前は「Point」とし、テキストのところにスペースを表示することで、オブジェクトを見えないようにします。


LabelCreate(0, PREFIX + "Point", posX, posY, " ", "メイリオ", 10000, clrNONE, ANCHOR_CENTER, false, true);

スイッチがOFFになったときには、このダミーのオブジェクトも消したいので、「ObjectDelete(0, PREFIX + “Switch”);」の下に次のコードを追加します。


ObjectDelete(0, PREFIX + "Point");

作ったダミーのオブジェクトは、マウスの位置に合わせて移動するようにしましょう。また、ダブルクリックされたときに選択が解除されないよう、OBJPROP_SELECTEDで常に選択が効くようにします。


if (paint) {
   ObjectSetInteger(0, PREFIX + "Point", OBJPROP_XDISTANCE, posX);
   ObjectSetInteger(0, PREFIX + "Point", OBJPROP_YDISTANCE, posY);
   ObjectSetInteger(0, PREFIX + "Point", OBJPROP_SELECTED, true);
}

さらに「MOUSE_MOVE」が効くように、OnInit関数内の「//— indicator buffers mapping」の下に次のコードを入れます。


ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

これでコンパイルしてPキーを押すと、ドラッグしてもチャートが動かないようになります。

なお、ChartSetInteger()のCHART_MOUSE_SCROLLを利用することでも対応可能です。


ChartSetInteger(0, CHART_MOUSE_SCROLL, false);

上記のようにfalseにすると、チャート上をドラッグしても、チャートがスクロールしなくなります。


7.チャート上でドラッグされたときに線を描画


上記では、Pキーの操作でスイッチのON/OFFの切り替えが可能となり、マウスをドラッグしてもチャートが動かないようになりました。次に、マウスがドラッグされたとき(クリックされて動いたとき)に線を描く処理を記述します。

まずは「posY = (int)dparam;」の下で、直前のマウス座標を記憶するための変数「preX」「preY」を定義します。


static int preX, preY;  

if文を使い、マウスがクリックされ、記憶した座標と異なったときに、そこで描画処理を実行するようにします。描画するのは「●」マークにしましょう。


if (sparam == "1") {
   if (posX != preX || posY != preY) {
      string name = PREFIX + "Parint_" + (string)preX + "_" + (string)preY;
      LabelCreate(0, name, posX, posY, "●", "メイリオ", SIZE, CLR, ANCHOR_CENTER);
   }
   preX = posX;
   preY = posY; 
}

paintの処理がOFFになったときにリセットしたいので、「if (paint)」に対するelseを加えます。


} else {
   preX = 0;
   preY = 0;
}

これでコンパイルしてPキーを押すと、マウスをドラッグして線や文字が描けるようになります。

チャートに線や文字を描く


8.Dキーの操作で描画した線を消去


最後に、Dキーを押したときに、描画した線を消すようにしましょう。Dキーに割り当てられている番号は「32」です。if文で「sparam」が32のときに、オブジェクトを消去するようにします。「Print(sparam);」のところを次のように書き換えましょう。


if (sparam == "32") ObjectsDeleteAll(0, PREFIX);

これでコンパイルしてDキーを押すと、先ほど描画したオブジェクトが消えることが分かります。

描画したオブジェクトが消えたチャート

さらに、paintのスイッチもOFFになるように「paint = false;」を加えれば、簡易的なペイントツールの完成です。


if (sparam == "32") {
   ObjectsDeleteAll(0, PREFIX);
   paint = false;
}


9.ソースコード


今回、作成したソースコードは下記の通りです。


//+------------------------------------------------------------------+
//|                                                    EasyPaint.mq4 |
//|                        Copyright 2022, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#property indicator_chart_window
#define PREFIX MQLInfoString(MQL_PROGRAM_NAME) + "_"
//--- input parameters
input color    CLR = clrWhite;
input int      SIZE = 5;

bool paint = false;
int posX, posY;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if (reason == REASON_REMOVE) ObjectsDeleteAll(0, PREFIX);
}
//+------------------------------------------------------------------+
//| 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 value of prev_calculated for next call
   return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
//---
   if (id == CHARTEVENT_KEYDOWN) {
      if (sparam == "25") {
         if (!paint) {
            paint = true;
            LabelCreate(0, PREFIX + "Switch", 5, 15, "PAINT", "Arial Black", 20, CLR, ANCHOR_LEFT_UPPER);
            LabelCreate(0, PREFIX + "Point", posX, posY, " ", "メイリオ", 10000, clrNONE, ANCHOR_CENTER, false, true);
         } else {
            paint = false;
            ObjectDelete(0, PREFIX + "Switch");
            ObjectDelete(0, PREFIX + "Point");
         }
      }

      if (sparam == "32") {
         ObjectsDeleteAll(0, PREFIX);
         paint = false;
      }
   }

   if (id == CHARTEVENT_MOUSE_MOVE) {
      posX = (int)lparam;
      posY = (int)dparam;

      static int preX, preY;  
      
      if (paint) {
         ObjectSetInteger(0, PREFIX + "Point", OBJPROP_XDISTANCE, posX);
         ObjectSetInteger(0, PREFIX + "Point", OBJPROP_YDISTANCE, posY);
         ObjectSetInteger(0, PREFIX + "Point", OBJPROP_SELECTED, true);

         if (sparam == "1") {
            if (posX != preX || posY != preY) {
               string name = PREFIX + "Parint_" + (string)preX + "_" + (string)preY;
               LabelCreate(0, name, posX, posY, "●", "メイリオ", SIZE, CLR, ANCHOR_CENTER);
            }
            preX = posX;
            preY = posY;
         }
      } else {
         preX = 0;
         preY = 0;
      }
   }
}
//+------------------------------------------------------------------+
//| Create a text label                                              |
//+------------------------------------------------------------------+
bool LabelCreate(const long              chart_ID = 0,             // chart's ID
                 const string            name = "Label",           // label name
//                 const int               sub_window = 0,           // subwindow index
                 const int               x = 0,                    // X coordinate
                 const int               y = 0,                    // Y coordinate
//                 const ENUM_BASE_CORNER  corner = CORNER_LEFT_UPPER, // chart corner for anchoring
                 const string            text = "Label",           // text
                 const string            font = "Arial",           // font
                 const int               font_size = 10,           // font size
                 const color             clr = clrRed,             // color
//                 const double            angle = 0.0,              // text slope
                 const ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER, // anchor type
                 const bool              back = false,             // in the background
                 const bool              selection = false,        // highlight to move
                 const bool              hidden = true,            // hidden in the object list
                 const long              z_order = 0)              // priority for mouse click
{
//--- create a text label
   if(!ObjectCreate(chart_ID, name, OBJ_LABEL, 0, 0, 0)) {
      return(false);
   }
//--- set label coordinates
   ObjectSetInteger(chart_ID, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(chart_ID, name, OBJPROP_YDISTANCE, y);
//--- set the chart's corner, relative to which point coordinates are defined
   ObjectSetInteger(chart_ID, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
//--- set the text
   ObjectSetString(chart_ID, name, OBJPROP_TEXT, text);
//--- set text font
   ObjectSetString(chart_ID, name, OBJPROP_FONT, font);
//--- set font size
   ObjectSetInteger(chart_ID, name, OBJPROP_FONTSIZE, font_size);
//--- set the slope angle of the text
   ObjectSetDouble(chart_ID, name, OBJPROP_ANGLE, 0);
//--- set anchor type
   ObjectSetInteger(chart_ID, name, OBJPROP_ANCHOR, anchor);
//--- set color
   ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
//--- display in the foreground (false) or background (true)
   ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);
//--- enable (true) or disable (false) the mode of moving the label by mouse
   ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, selection);
   ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, selection);
//--- hide (true) or display (false) graphical object name in the object list
   ObjectSetInteger(chart_ID, name, OBJPROP_HIDDEN, hidden);
//--- set the priority for receiving the event of a mouse click in the chart
   ObjectSetInteger(chart_ID, name, OBJPROP_ZORDER, z_order);
//--- successful execution
   return(true);
}


本記事の監修者・HT FX


2013年にFXを開始し、その後専業トレーダーへ。2014年からMT4/MT5のカスタムインジケーターの開発に取り組む。ブログでは100本を超えるインジケーターを無料公開。投資スタイルは自作の秒足インジケーターを利用したスキャルピング。

EA(自動売買)を学びたい方へオススメコンテンツ

EA運用の注意点

OANDAではEA(自動売買)を稼働するプラットフォームMT4/MT5の基本的な使い方について、画像や動画付きで詳しく解説しています。MT4/MT5のインストールからEAの設定方法までを詳しく解説しているので、初心者の方でもスムーズにEA運用を始めることが可能です。またOANDAの口座をお持ちであれば、独自開発したオリジナルインジケーターを無料で利用することもできます。EA運用をお考えであれば、ぜひ口座開設をご検討ください。


本ホームページに掲載されている事項は、投資判断の参考となる情報の提供を目的としたものであり、投資の勧誘を目的としたものではありません。投資方針、投資タイミング等は、ご自身の責任において判断してください。本サービスの情報に基づいて行った取引のいかなる損失についても、当社は一切の責を負いかねますのでご了承ください。また、当社は、当該情報の正確性および完全性を保証または約束するものでなく、今後、予告なしに内容を変更または廃止する場合があります。なお、当該情報の欠落・誤謬等につきましてもその責を負いかねますのでご了承ください。

この記事をシェアする

ホーム » FX自動売買基礎と応用 » 簡易的なペイントツールを作成する方法【MQLプログラミングの基礎】