トレードでは、エントリーできる状況が訪れるまで待つ時間が長いですよね。この時間がもったいないので、節目となる価格や意識されているラインにアラートを仕掛けておいて、アラートが鳴るまでは他の作業をしています。
これ以外で「待つ」というと、ローソク足の確定を待つ場面があります。これも無駄な待ち時間だと思うことが時々あるので、(ローソク足が確定した時にだけチャートを見るために)確定を知らせてくれるインディケーターを作りました。
ざっくり言うと、現在足が確定した時に「足が確定しました」という音声を流すインディケーターです。
ちなみに、相変わらずテストやエラーチェックを行う処理は省いています(本来は大切なことですが、自分しか使わないので横着しています)。
目次
音声ファイルの用意
まず音声ファイルを用意します。私は以下のサイトを利用しました。
音読さん
https://ondoku3.com/ja/
このサイトで、音声として読み上げたいテキストを入力すると、それを音声ファイルにしてくれます。今回は「足が確定しました」という音声ファイルを作成しました。
ところで、MT5から出る効果音の音源はwaveファイルです。一方、「音読さん」で作成されるファイルはmp3ファイルです。そのため、mp3からwaveに変換する必要があります。私はffmpegというソフトで変換しました。
完成したファイル
ファイル名 candle_fixed.wav 中身 「足が確定しました」という音声
最後に、この.wavファイルを以下のフォルダに入れました。
Cドライブ → Program Files → (MT5のフォルダ) → Sounds
コード
#property copyright "Copyright 2025, PHILOJUAN" #property version "1.00" #property indicator_chart_window #property indicator_plots 0 #property strict // ボタンのパラメーター input int buttonXdis1 = 200; // ボタンの表示位置(X軸) input int buttonYdis1 = 5; // ボタンの表示位置(Y軸) input color colorOfBorder = clrNONE; // ボタンの縁の色 input color colorOfButtonText = clrWhite; // ボタン内のテキストの色 // グローバル変数 bool notificationIsAvailable = true; // 制御変数(音声による通知を行うか否か) string prefix = "BRGZPMQ"; // オブジェクト名の接頭辞 string buttonName1 = prefix + "_NOTIFY_BUTTON"; datetime timeRecord = D'2000.01.01 00:00'; // ローソク足の発生時刻を記録する変数 // ターミナルのグローバル変数名と代入する値 (tgv は Terminal Global Variableの略) string tgvName = "MVVKKZ_CANDLE_FIXED_" + IntegerToString(ChartID()); const double TRUE = 1.0; const double FALSE = 0.0; //--------------------------------------------------------------------------------- // インジ挿入時 or TimeFrame変更時 //--------------------------------------------------------------------------------- int OnInit() { // [初期化メモ] // notificationIsAvailableは、宣言時にtrueを代入済み。 // ボタンの押下状態に関しては、CreateButton関数内でOBJPROP_STATEを // trueにしている。 // 現在足の発生時刻でtimeRecordを初期化 timeRecord = iTime(NULL, 0, 0); // 通知のON・OFFを切り替えるボタンを配置 (NotiはNotifyの略) CreateButton(buttonName1, "Noti_ON", CORNER_RIGHT_UPPER, buttonXdis1, buttonYdis1, 60, 17, 7, colorOfButtonText, colorOfBorder, clrGray); // ターミナルのグローバル変数の値を元に、ボタンや変数の状態を復元 if(GlobalVariableGet(tgvName) == FALSE) { ObjectSetInteger(0, buttonName1, OBJPROP_STATE, false); ChangeNotifyAndButtonText(buttonName1); } return INIT_SUCCEEDED; } //--------------------------------------------------------------------------------- // インジ除去時 or TimeFrame変更時 //--------------------------------------------------------------------------------- void OnDeinit(const int reason) { // ボタンを削除(オブジェクトを増やした場合に備えて、prefixで消去) ObjectsDeleteAll(0, prefix); ChartRedraw(); } //--------------------------------------------------------------------------------- // Tick受信時 //--------------------------------------------------------------------------------- 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[]) { // 通知設定がOFFの場合、以降のプロセスを省略する if(notificationIsAvailable == false) return 0; // 最新足の発生時刻 datetime generatedTime = time[rates_total - 1]; // timeRecordの更新 (ローソク足1本分よりも長い時間が経過していた場合) if(generatedTime - timeRecord > PeriodSeconds(_Period)) { timeRecord = generatedTime; return 0; } // [目的] 通知をONにしたままMT5を終了し、しばらく経ってから起動した場合、 // 起動と同時にwaveファイルが再生される。これを回避する。 // 新たなローソク足が発生したら、waveファイルを再生する if(timeRecord != generatedTime) { PlaySound("candle_fixed.wav"); timeRecord = generatedTime; } return 0; } //--------------------------------------------------------------------------------- // ボタン押下時 //--------------------------------------------------------------------------------- void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && sparam == buttonName1) { // timeRecordの更新 timeRecord = iTime(NULL, 0, 0); // ボタンの状態に応じて各種変更を行う ChangeNotifyAndButtonText(buttonName1); } } //---------------------------------------------------------------------------- // ボタンの押下状態に応じて、変数の内容やボタンテキストを変更する関数 //---------------------------------------------------------------------------- void ChangeNotifyAndButtonText(string objName) { // ボタンの押下状態を取得 long buttonState = ObjectGetInteger(0, objName, OBJPROP_STATE); // 通知のON・OFFを変更 notificationIsAvailable = buttonState ? true : false; // ターミナルのグローバル変数にボタンの押下状態を保存 double num = buttonState ? TRUE : FALSE; GlobalVariableSet(tgvName, num); // ボタン内のテキストを変更 string onText = "Noti_ON"; string offText = "Noti_OFF"; ObjectSetString (0, objName, OBJPROP_TEXT, buttonState ? onText : offText); ChartRedraw(); } //---------------------------------------------------------------------------- // ボタンを作成する関数 //---------------------------------------------------------------------------- void CreateButton(string name, // オブジェクト名 string caption, // ボタン内に表示するテキスト int corner, // 基点(ボタンを配置する時に基準とする場所) int xshift, // 基点からの距離(横方向) int yshift, // 基点からの距離(縦方向) int xsize, // ボタンのサイズ(横) int ysize, // ボタンのサイズ(縦) int font_Size, // ボタン内に表示するテキストのサイズ color textColor, // ボタン内に表示するテキストの色 color bo_color, // ボタンの縁の色 color bg_Color) // ボタンの色 { ObjectCreate( 0, name, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, corner); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xshift); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yshift); ObjectSetInteger(0, name, OBJPROP_XSIZE, xsize); ObjectSetInteger(0, name, OBJPROP_YSIZE, ysize); ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, bo_color); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg_Color); ObjectSetString (0, name, OBJPROP_TEXT, caption); ObjectSetString (0, name, OBJPROP_FONT, "Arial"); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_Size); ObjectSetInteger(0, name, OBJPROP_COLOR, textColor); ObjectSetInteger(0, name, OBJPROP_STATE, true); // このインジではボタン設置時に trueにしている。 // ObjectSetInteger(0, name, OBJPROP_STATE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetInteger(0, name, OBJPROP_ZORDER, 1); }
OnChartEvent関数にある「timeRecordの更新」
1週間後には忘れてしまいそうなので、OnChartEvent関数の中にある次の行の目的を書いておきます。
// timeRecordの更新 timeRecord = iTime(NULL, 0, 0);
チャート上に配置したボタンをクリックして音声通知をONにすると、ローソク足がまだ形成途中なのに(確定していないのに)waveファイルが再生されるケースがありました。これを回避することが目的です。
<状況>
「通知をOFFにした時点のローソク足」の次足が確定するまでの間に通知をONにすると、即座に(Tick受信時に)waveファイルが再生されます。
文字で読むと分かりにくいので、図を描きました。

A足時点で音声通知をOFFにします。それから、A足の次に登場したB足が確定する前に(B足が伸びたり縮んだりしている間に)音声通知をONにします。すると、ONにした後、最初のTickを受信した時点で「足が確定しました」と音声が流れます。
一方、C足が登場してからONにした場合は「ローソク足が確定していないのに音声が流れる」という事態は生じません。
<原因>
原因は次のとおりです(1分足を例にしています)。
1分足チャートで、11時20分台(11:20:00 ~ 11:20:59)に通知をOFFにして、11時21分台(11:21:00 ~ 11:21:59)にONにした場合、変数の中身は次のようになっています。
timeRecord = 11:20:00 generatedTime = 11:21:00
よって、OnCalculate関数内の
if(timeRecord != generatedTime) { PlaySound("candle_fixed.wav"); timeRecord = generatedTime; }
が実行されます(音声ファイルが再生されます)。
一方、11時22分台(11:22:00 ~ 11:22:59)にONにした場合、変数の中身は
timeRecord = 11:20:00 generatedTime = 11:22:00
となっていますが、両者の差がローソク足1本分(60秒)よりも大きいので、OnCalculate関数内にある以下のif文が実行されます。
if(generatedTime - timeRecord > PeriodSeconds(_Period)) { timeRecord = generatedTime; return 0; }
この結果、2つの変数の中身は同じになります。
timeRecord = 11:22:00 generatedTime = 11:22:00
このため、ONにした時点の現在足が確定して次足が発生するまで(11:23:00になるまで)、音声ファイルが再生されることはありません。
<OnCalculate関数における「timeRecordの更新」>
上述の例において、11時21分台にONにした場合、時間としては最大1分59秒という幅があります。しかし、ローソク足の発生時刻は常に00秒なので(timeRecordは11:20:00 で、 generatedTimeは11:21:00)、timeRecordとgeneratedTimeの差は足1本分(60秒)です。
このため、OnCalculate関数内にある以下のif文は実行されません(timeRecordは更新されません)。
if(generatedTime - timeRecord > PeriodSeconds(_Period)) { timeRecord = generatedTime; return 0; }
かといって、このif文の「 > 」を「 >= 」にすると、ローソク足が確定した時に音声ファイルが再生されなくなります。
そこで、ひとまずボタン押下時に以下の処理を入れました。
// timeRecordの更新 timeRecord = iTime(NULL, 0, 0);
ただ、どうにもビミョーだと感じているので、もっとマシなロジックを考えたいところです。
余談
1分足を例にしてOnChartEvent関数の中でtimeRecordを更新している理由を書きましたが、1分足チャートでこのインディケーターを使うことはまずありません(^^;。