EDITオブジェクトを用いて曜日ごとの集計表を描画する方法
「年」を指定するマスを作成
「EDITオブジェクトのパラメーターの定義・設定方法」では、ファイルの新規作成時にEDITオブジェクトのパラメーターで表の位置などを定義する方法を解説しました。
参考記事:EDITオブジェクトのパラメーターの定義・設定方法
続いて、OnInit関数内で表の横位置を示すX位置を、1列目から順番に定義していきます。
1列目は「POSX」のままで、2列目はPOSXに1列目の幅分(WIDTH_TITLE)を加えます。3列目は、2列目のx[1]にWIDTH_DATAを、4列目は3列目のx[2]にWIDTH_DATAを足します。
int x[4];
x[0] = POSX;
x[1] = x[0] + WIDTH_TITLE;
x[2] = x[1] + WIDTH_DATA;
x[3] = x[2] + WIDTH_DATA;
そして、EditCreateを用いて1列目に「年」を指定するマスを作ります。Xの位置は「x[0]」で、Yの位置は「POSY」のまま、幅は1列目なので「WIDTH_TITLE」、高さは共通で「HEIGHT」。初期値は「2020」で、最後は編集可能にするために「false」とします。
EditCreate("Year", x[0], POSY, WIDTH_TITLE, HEIGHT, "2020", false);
EditCreateのパラメーター「ability to edit」の初期値は「false」になっているので、そこを「true」に変更しておきましょう。これで、このマス以外は編集不可となります。
read_only = true, // ability to edit
この状態で、後々オブジェクトをまとめて消したりしたいので、ファイル上部のプロパティ「#property indicator_chart_window」の下に「#define」を使って「PREFIX」を定義します。
#define PREFIX MQLInfoString(MQL_PROGRAM_NAME) + "_"
PREFIXは接頭辞という意味で、指定した文字列で始まる名前のオブジェクトをチャートから削除します。オブジェクトの名前の頭に毎回PREFIXをつけたいので、ファイル下部にある「Create Edit object」のパラメーター指定の下に次の式を記入します。
name = PREFIX + name;
ただ、EditCreateカッコ内の「object name」のコードに、変数の値を変更できないようにする「const」修飾子が入っていて、このままでは置き換えられないので、「const string」の「const」を消去します。
bool EditCreate(string name = "Edit", // object name
これをコンパイルしてチャートにセットすると、編集可能なEDITが一つできることが分かります。
陽線、陰線、十字線のマスを追加
続いて、2020と書かれたマスの隣に陽線、陰線、十字線のマスを作っていきます。陽線はXの位置を「x[1]」、幅を「WIDTH_DATA」、色を「Red」に、陰線はXの位置を「x[2]」、幅を「WIDTH_DATA」、色を「Blue」に、十字線はXの位置を「x[3]」、幅を「WIDTH_DATA」、色を「Yellow」に設定します。
EditCreate("Up", x[1], POSY, WIDTH_DATA, HEIGHT, "陽線", true, clrRed);
EditCreate("Down", x[2], POSY, WIDTH_DATA, HEIGHT, "陰線", true, clrDodgerBlue);
EditCreate("Cross", x[3], POSY, WIDTH_DATA, HEIGHT, "十字線", true, clrYellow);
そして、「Custom indicator initialization function」の下に「Custom indicator deinit function」を設けて、OnDeinit関数を用いた次の式を記述します。
void OnDeinit(const int reason)
{
if (reason == REASON_PARAMETERS || reason == REASON_RECOMPILE || reason == REASON_REMOVE)
ObjectsDeleteAll(0, PREFIX);
}
これは、もしパラメーターを変更したり、コンパイルしたり、チャートからインジケーターを削除したりしたときに、この「PREFIX」が含まれるオブジェクトは全部消すという式です。これでコンパイルすると、陽線、陰線、十字線のマスが追加されます。
月曜日~金曜日の表を追加
今度は、曜日の分だけ下に表を追加していきます。曜日は月曜日~金曜日までの5行を追加します。縦の位置はPOSY+高さ(HEIGHT)×「i+1」とします。
for (int i = 0; i < 5; i++) {
int y = POSY + HEIGHT * (i + 1);
最初は曜日で、次に陽線、陰線、十字線の集計結果にそれぞれ番号を振って、オブジェクトを作っていきます。横位置や幅は年のマスと同じで、縦位置は「y」に置き換えます。陽線、陰線、十字線のテキスト表示は、あとで集計データが入るので無しに、文字色は白にしたいので初期設定にします。
EditCreate("DOW" + (string)i, x[0], y, WIDTH_TITLE, HEIGHT, dow);
EditCreate("Up" + (string)i, x[1], y, WIDTH_DATA, HEIGHT, "");
EditCreate("Down" + (string)i, x[2], y, WIDTH_DATA, HEIGHT, "");
EditCreate("Cross" + (string)i, x[3], y, WIDTH_DATA, HEIGHT, "");
ただ、曜日に関しては別で定義する必要があります。その際に利用するのが「ENUM_DAY_OF_WEEK」という列挙型で、0(日曜日)~6(土曜日)までの数値で順番に曜日を表します。
for文で指定した「i」は0~4までなので、これに1を足すと、ちょうど曜日とマッチします。こうすることで、曜日の列挙型に変換されることになります。その変換されたものを「EnumToString」を用いて曜日の名称に変換します。曜日の名称は最初の3文字のみでも意味が分かるので、文字の頭から3文字だけ表示するようにしましょう。
string dow = StringSubstr(EnumToString((ENUM_DAY_OF_WEEK)(i + 1)), 0, 3);
これで表自体は完成です。コンパイルしてチャートにセットすると、曜日の行が加わった表が表示されます。
曜日ごとの陽線・陰線・十字線をカウントして表示
月曜日~金曜日それぞれの陽線・陰線・十字線の数をカウントして集計する「Calc」という関数を作って実行させます。
まずは、検索する範囲を決めるために「年」を示す文字を読み取ります。そして、文字列を時間に変換するStringToTime関数を使って、開始時間に変換します。FX市場は1月1日が休みなので、開始時間は「1月2日00:00」としておけば問題ないでしょう。
string year = ObjectGetString(0, PREFIX + "Year", OBJPROP_TEXT);
datetime timeStart = StringToTime(year + ".01.02 00:00");
続いて、時間を表示中の時間足のバーのシフト数に変換するため、「iBarShift」関数を使います。パラメーターは、表示中の通貨ペア、時間足を指定します。下記の式で検索を開始するバーが定義されます。
int iStart = iBarShift(NULL, 0, timeStart);
もしその足の年が、設定された年よりも小さい場合はそれ以上処理をせず、もし大きい場合はそこで終わりとします。また、その足の曜日が日曜日だったり土曜日だったりした場合も、それ以上処理しないようにします。
そして、for文の上で、それぞれの足で陽線か陰線かをカウントする配列を定義していきます。陽線だった場合は「up」をカウント、同様に陰線だった場合は「down」をカウント、どちらでもなかった場合は「cross」をカウントします。月曜日の数字は1ですが、配列的には0番目にしたいので「-1」としています。これでデータがそれぞれ格納されます。
int up[5] = {0, 0, 0, 0, 0}, down[5] = {0, 0, 0, 0, 0}, cross[5] = {0, 0, 0, 0, 0};
for (int i = iStart; i >= 0; i--) {
if (TimeYear(Time[i]) < (int)year) continue;
if (TimeYear(Time[i]) > (int)year) break;
int dow = TimeDayOfWeek(Time[i]);
if (dow == SUNDAY || dow == SATURDAY) continue;
if (Close[i] > Open[i]) up[dow - 1]++;
else if (Close[i] < Open[i]) down[dow - 1]++;
else cross[dow - 1]++;
}
次に、格納されたデータを、既にある表の文字の情報に上書きするために、次の式を加えます。
for (int i = 0; i < 5; i++) {
ObjectSetString(0, PREFIX + "Up" + (string)i, OBJPROP_TEXT, (string)up[i]);
ObjectSetString(0, PREFIX + "Down" + (string)i, OBJPROP_TEXT, (string)down[i]);
ObjectSetString(0, PREFIX + "Cross" + (string)i, OBJPROP_TEXT, (string)cross[i]);
}
このCalc関数を「OnCalculate」配下に置いてコンパイルすると、陽線、陰線、十字線の本数が表示されます。
陽線、陰線、十字線の割合を表示
補足情報として、陽線、陰線、十字線の割合を本数の後ろに表示するようにしましょう。
カウントした陽線、陰線、十字線の本数をそれぞれトータル本数で割って全体に占める割合を求め、パーセント表示に変えるために100を掛けます。ここでは、陽線、陰線、十字線のトータル本数が0でなかった場合、計算結果をカッコでくくって小数点以下1位まで表示するようにします。
for (int i = 0; i < 5; i++) {
int total = up[i] + down[i] + cross[i];
string ratioUP = "-", ratioDown = "-", ratioCross = "-";
if (total != 0) {
ratioUP = "(" + DoubleToString(100.0 * up[i] / total, 1) + "%)";
ratioDown = "(" + DoubleToString(100.0 * down[i] / total, 1) + "%)";
ratioCross = "(" + DoubleToString(100.0 * cross[i] / total, 1) + "%)";
}
ObjectSetString(0, PREFIX + "Up" + (string)i, OBJPROP_TEXT, (string)up[i] + ratioUP);
ObjectSetString(0, PREFIX + "Down" + (string)i, OBJPROP_TEXT, (string)down[i] + ratioDown);
ObjectSetString(0, PREFIX + "Cross" + (string)i, OBJPROP_TEXT, (string)cross[i] + ratioCross);
}
上記のコードを加えてコンパイルすると、本数の後ろに割合が表示されるようになります。
なお、今回は曜日ごとに集計する内容なので、週足以上ではその集計処理が不要になります。そこで、日足以下の時間足のときに処理を行うよう条件の追加が必要です。一つ目のfor文の上に次のif文を追加しましょう。
if (_Period <= PERIOD_D1) {
最後に、「CHARTEVENT_OBJECT_ENDEDIT」を用いて、EDITオブジェクトの年のマスでテキストを編集したときにCalc関数を実行して値が反映されるようにします。また、年の編集で無意味な値が入力された際の対応として、読み取った文字を整数型に変換して戻す処理を加えます。年の値がMT4で扱える一番古い「1970」よりも小さい場合、または現在よりも大きかった場合に、今年の値が自動的に入るように変更しましょう。
if (id == CHARTEVENT_OBJECT_ENDEDIT) {
if (sparam == PREFIX + "Year") {
string year = ObjectGetString(0, sparam, OBJPROP_TEXT);
string text = (string)(int)year;
if ((int)year < 1970 || (int)year > Year()) text = (string)Year();
ObjectSetString(0, sparam, OBJPROP_TEXT, text);
Calc();
}
}
これでコンパイルすると、1970よりも小さい値や意味のないデータを入れた場合に今年の年数に変わることが分かります。これで完成です。
↓
ソースコード
今回、作成したソースコードは下記の通りです。
//+------------------------------------------------------------------+
//| UpDownTable_DOW.mq4 |
//| Copyright 2021, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, 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 int POSX = 0;
input int POSY = 15;
input int WIDTH_TITLE = 80;
input int WIDTH_DATA = 150;
input int HEIGHT = 30;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
int x[4];
x[0] = POSX;
x[1] = x[0] + WIDTH_TITLE;
x[2] = x[1] + WIDTH_DATA;
x[3] = x[2] + WIDTH_DATA;
EditCreate("Year", x[0], POSY, WIDTH_TITLE, HEIGHT, "2020", false);
EditCreate("Up", x[1], POSY, WIDTH_DATA, HEIGHT, "陽線", true, clrRed);
EditCreate("Down", x[2], POSY, WIDTH_DATA, HEIGHT, "陰線", true, clrDodgerBlue);
EditCreate("Cross", x[3], POSY, WIDTH_DATA, HEIGHT, "十字線", true, clrYellow);
for (int i = 0; i < 5; i++) {
int y = POSY + HEIGHT * (i + 1);
string dow = StringSubstr(EnumToString((ENUM_DAY_OF_WEEK)(i + 1)), 0, 3);
EditCreate("DOW" + (string)i, x[0], y, WIDTH_TITLE, HEIGHT, dow);
EditCreate("Up" + (string)i, x[1], y, WIDTH_DATA, HEIGHT, "");
EditCreate("Down" + (string)i, x[2], y, WIDTH_DATA, HEIGHT, "");
EditCreate("Cross" + (string)i, x[3], y, WIDTH_DATA, HEIGHT, "");
}
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinit function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if (reason == REASON_PARAMETERS || reason == REASON_RECOMPILE || 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[])
{
//---
Calc();
//--- 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_OBJECT_ENDEDIT) {
if (sparam == PREFIX + "Year") {
string year = ObjectGetString(0, sparam, OBJPROP_TEXT);
string text = (string)(int)year;
if ((int)year < 1970 || (int)year > Year()) text = (string)Year();
ObjectSetString(0, sparam, OBJPROP_TEXT, text);
Calc();
}
}
}
//+------------------------------------------------------------------+
//| Calculate function |
//+------------------------------------------------------------------+
void Calc()
{
string year = ObjectGetString(0, PREFIX + "Year", OBJPROP_TEXT);
datetime timeStart = StringToTime(year + ".01.02 00:00");
int iStart = iBarShift(NULL, 0, timeStart);
int up[5] = {0, 0, 0, 0, 0}, down[5] = {0, 0, 0, 0, 0}, cross[5] = {0, 0, 0, 0, 0};
if (_Period <= PERIOD_D1) {
for (int i = iStart; i >= 0; i--) {
if (TimeYear(Time[i]) < (int)year) continue;
if (TimeYear(Time[i]) > (int)year) break;
int dow = TimeDayOfWeek(Time[i]);
if (dow == SUNDAY || dow == SATURDAY) continue;
if (Close[i] > Open[i]) up[dow - 1]++;
else if (Close[i] < Open[i]) down[dow - 1]++;
else cross[dow - 1]++;
}
}
for (int i = 0; i < 5; i++) {
int total = up[i] + down[i] + cross[i];
string ratioUP = "-", ratioDown = "-", ratioCross = "-";
if (total != 0) {
ratioUP = "(" + DoubleToString(100.0 * up[i] / total, 1) + "%)";
ratioDown = "(" + DoubleToString(100.0 * down[i] / total, 1) + "%)";
ratioCross = "(" + DoubleToString(100.0 * cross[i] / total, 1) + "%)";
}
ObjectSetString(0, PREFIX + "Up" + (string)i, OBJPROP_TEXT, (string)up[i] + ratioUP);
ObjectSetString(0, PREFIX + "Down" + (string)i, OBJPROP_TEXT, (string)down[i] + ratioDown);
ObjectSetString(0, PREFIX + "Cross" + (string)i, OBJPROP_TEXT, (string)cross[i] + ratioCross);
}
}
//+------------------------------------------------------------------+
//| Create Edit object |
//+------------------------------------------------------------------+
bool EditCreate(string name = "Edit", // object name
const int x = 0, // X coordinate
const int y = 0, // Y coordinate
const int width = 50, // width
const int height = 18, // height
const string text = "Text", // text
const bool read_only = true, // ability to edit
const color clr = clrWhite, // text color
const color back_clr = clrBlack, // background color
const color border_clr = clrWhite, // border color
const long chart_ID = 0, // chart's ID
const int sub_window = 0, // subwindow index
const string font = "メイリオ", // font
const int font_size = 14, // font size
const ENUM_ALIGN_MODE align = ALIGN_CENTER, // alignment type
const ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, // chart corner for anchoring
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
{
name = PREFIX + name;
//--- reset the error value
ResetLastError();
//--- create edit field
if(!ObjectCreate(chart_ID, name, OBJ_EDIT, sub_window, 0, 0)) {
Print(__FUNCTION__,
": failed to create \"Edit\" object! Error code = ", GetLastError());
return(false);
}
//--- set object coordinates
ObjectSetInteger(chart_ID, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(chart_ID, name, OBJPROP_YDISTANCE, y);
//--- set object size
ObjectSetInteger(chart_ID, name, OBJPROP_XSIZE, width);
ObjectSetInteger(chart_ID, name, OBJPROP_YSIZE, height);
//--- 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 type of text alignment in the object
ObjectSetInteger(chart_ID, name, OBJPROP_ALIGN, align);
//--- enable (true) or cancel (false) read-only mode
ObjectSetInteger(chart_ID, name, OBJPROP_READONLY, read_only);
//--- set the chart's corner, relative to which object coordinates are defined
ObjectSetInteger(chart_ID, name, OBJPROP_CORNER, corner);
//--- set text color
ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
//--- set background color
ObjectSetInteger(chart_ID, name, OBJPROP_BGCOLOR, back_clr);
//--- set border color
ObjectSetInteger(chart_ID, name, OBJPROP_BORDER_COLOR, border_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(自動売買)を学びたい方へオススメコンテンツ

OANDAではEA(自動売買)を稼働するプラットフォームMT4/MT5の基本的な使い方について、画像や動画付きで詳しく解説しています。MT4/MT5のインストールからEAの設定方法までを詳しく解説しているので、初心者の方でもスムーズにEA運用を始めることが可能です。またOANDAの口座をお持ちであれば、独自開発したオリジナルインジケーターを無料で利用することもできます。EA運用をお考えであれば、ぜひ口座開設をご検討ください。
本ホームページに掲載されている事項は、投資判断の参考となる情報の提供を目的としたものであり、投資の勧誘を目的としたものではありません。投資方針、投資タイミング等は、ご自身の責任において判断してください。本サービスの情報に基づいて行った取引のいかなる損失についても、当社は一切の責を負いかねますのでご了承ください。また、当社は、当該情報の正確性および完全性を保証または約束するものでなく、今後、予告なしに内容を変更または廃止する場合があります。なお、当該情報の欠落・誤謬等につきましてもその責を負いかねますのでご了承ください。