チャート上に「ライン ON/OFF」のようなボタンを置いて、
クリックひとつでインジケーターの表示を切り替えたいと思ったことはないでしょうか。
あるいは、マウスカーソルを動かしただけで、
その位置の価格がラベルに表示されるインジケーターを見て、
「これはどう作るんだろう」と感じたことはないでしょうか。
この記事では、MQL5のOnChartEvent()とマウスイベント(CHARTEVENT_MOUSE_MOVE・CHARTEVENT_OBJECT_CLICK)を使い、
OBJ_BUTTONのボタンパネルと、
カーソル位置の価格表示を実際に動くコードで作る方法を解説します。
この記事で分かること
・OnChartEvent()がどんなタイミングで何を受け取るか
・マウスイベントを受け取るための事前準備
・OBJ_BUTTONでクリックできるボタンを作る方法
・ChartXYToTimePriceでカーソル位置の価格を取得する方法
OnChartEventとは何か
まず、チャート上のイベントを受け取る土台となるOnChartEvent()から確認します。
呼ばれるタイミング(OnTick・OnCalculateとの違い)
OnTick()やOnCalculate()は、
新しいティックやバーが来たときに呼ばれます。
これに対してOnChartEvent()は、
キー入力・マウスのクリックや移動・オブジェクトの作成や削除など、
チャート上の「出来事」が起きるたびに呼ばれます。
値動きとは関係のないタイミングで発火する点が、最大の違いです。
4つの引数(id・lparam・dparam・sparam)の役割
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam);
idには、何が起きたかを表すCHARTEVENT_から始まる定数が入ります。
lparam・dparam・sparamに入る内容は、idの種類によって変わります。
たとえばCHARTEVENT_OBJECT_CLICKでは、
sparamにクリックされたオブジェクトの名前が入り、
lparam・dparamはクリック座標になります。
マウスイベントを受け取るための事前準備
マウスの移動イベント(CHARTEVENT_MOUSE_MOVE)は、
初期状態では発火しません。
OnInit()の中で、次の1行を実行して明示的に有効化する必要があります。
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
補足
・クリックイベント(CHARTEVENT_CLICK)自体は既定で発火します
・座標を伴うマウス移動の検知には別設定が必要です
・有効化を忘れるとボタンは反応するのにカーソル追従だけ動きません
OBJ_BUTTONでクリックできるボタンを作る
次に、実際にクリックできるボタンをチャート上に配置します。
ObjectCreateと位置・サイズの指定
OBJ_BUTTONはチャート座標ではなく、
画面の左上等を基準にしたスクリーン座標で配置するオブジェクトです。
ObjectCreate(0, "btnToggleLine", OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, "btnToggleLine", OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, "btnToggleLine", OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, "btnToggleLine", OBJPROP_YDISTANCE, 20); ObjectSetInteger(0, "btnToggleLine", OBJPROP_XSIZE, 140); ObjectSetInteger(0, "btnToggleLine", OBJPROP_YSIZE, 24); ObjectSetString(0, "btnToggleLine", OBJPROP_TEXT, "ライン ON/OFF"); ObjectSetInteger(0, "btnToggleLine", OBJPROP_BGCOLOR, clrSteelBlue); ObjectSetInteger(0, "btnToggleLine", OBJPROP_COLOR, clrWhite);
ObjectCreateのtime1・price1には、ダミーで0を渡しています。
OBJ_BUTTONはスクリーン座標専用のため、この2つの値は使われません。
OBJPROP_STATEでボタンの押下状態を扱う
ボタンが押されているかどうかは、OBJPROP_STATE(bool型)で取得できます。
bool pressed = ObjectGetInteger(0, "btnToggleLine", OBJPROP_STATE);
MQL5のボタンは、クリックすると押し込まれたまま表示が固定されます。
このままだと「押しっぱなし」に見えてしまうため、
判定処理のあとに自分でfalseへ戻す必要があります。
CHARTEVENT_OBJECT_CLICKでボタンのクリックを検知する
ボタンを配置しただけでは何も起きません。
クリックを検知する処理を書きます。
sparamにクリックされたオブジェクト名が入る
CHARTEVENT_OBJECT_CLICKが発生すると、
sparamにクリックされたオブジェクトの名前がそのまま渡されます。
if(id == CHARTEVENT_OBJECT_CLICK)
{
if(sparam == "btnToggleLine")
{
lineVisible = !lineVisible;
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_COLOR,
lineVisible ? clrOrangeRed : clrNONE);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_STATE, false);
ChartRedraw(0);
}
}
sparamをボタン名と比較するだけなので、
複数のボタンを同じOnChartEvent()の中で分岐して扱えます。
押しっぱなし状態を防ぐリセット処理
上のコードでは、ボタン判定の直後にOBJPROP_STATEをfalseへ戻すことで、
押しっぱなし状態を解除しています。
注意
・このリセットを忘れると見た目が押しっぱなしになります
・ただしクリック判定自体は毎回きちんと実行されています
・「2回押さないと切り替わらない」という誤解はしないよう注意してください
マウス座標から価格を取得する(ChartXYToTimePrice)
ボタンに加えて、カーソルを動かすだけで価格が分かる仕組みも作ります。
CHARTEVENT_MOUSE_MOVEで座標を受け取る
if(id == CHARTEVENT_MOUSE_MOVE)
{
int x = (int)lparam;
int y = (int)dparam;
...
}
CHARTEVENT_MOUSE_MOVEでは、lparamにX座標、
dparamにY座標がピクセル単位で渡されます(他のイベントとは型の使われ方が異なる点に注意)。
カーソル位置の価格をラベルに表示する
bool ChartXYToTimePrice(long chart_id, int x, int y,
int &sub_window, datetime &time, double &price);
座標をそのまま使うのではなく、ChartXYToTimePriceに渡すことで、
その座標が指す「時刻」と「価格」に変換できます。
int win; datetime t; double p;
if(ChartXYToTimePrice(0, x, y, win, t, p))
{
ObjectSetString(0, "lblCursorPrice", OBJPROP_TEXT,
"価格: " + DoubleToString(p, _Digits));
}
実際に動くコード全体
ボタンで水平線の表示・非表示を切り替えつつ、
マウス移動でカーソル位置の価格を表示するカスタムインジケーターです。
//+------------------------------------------------------------------+
//| MouseButtonPanel.mq5 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0
bool lineVisible = true;
int OnInit()
{
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
ObjectCreate(0, "btnToggleLine", OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_XSIZE, 140);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_YSIZE, 24);
ObjectSetString(0, "btnToggleLine", OBJPROP_TEXT, "ライン ON/OFF");
ObjectSetInteger(0, "btnToggleLine", OBJPROP_BGCOLOR, clrSteelBlue);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_COLOR, clrWhite);
ObjectCreate(0, "cursorPriceLine", OBJ_HLINE, 0, 0, SymbolInfoDouble(_Symbol, SYMBOL_BID));
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_COLOR, clrOrangeRed);
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_STYLE, STYLE_DASH);
ObjectCreate(0, "lblCursorPrice", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "lblCursorPrice", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "lblCursorPrice", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "lblCursorPrice", OBJPROP_YDISTANCE, 54);
ObjectSetString(0, "lblCursorPrice", OBJPROP_TEXT, "価格: -");
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectDelete(0, "btnToggleLine");
ObjectDelete(0, "cursorPriceLine");
ObjectDelete(0, "lblCursorPrice");
}
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);
}
void OnChartEvent(const int id, const long &lparam,
const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK && sparam == "btnToggleLine")
{
lineVisible = !lineVisible;
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_COLOR,
lineVisible ? clrOrangeRed : clrNONE);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_STATE, false);
ChartRedraw(0);
return;
}
if(id == CHARTEVENT_MOUSE_MOVE)
{
int x = (int)lparam;
int y = (int)dparam;
int win;
datetime t;
double p;
if(ChartXYToTimePrice(0, x, y, win, t, p))
{
ObjectSetString(0, "lblCursorPrice", OBJPROP_TEXT,
"価格: " + DoubleToString(p, _Digits));
if(lineVisible)
ObjectSetDouble(0, "cursorPriceLine", OBJPROP_PRICE, p);
}
}
}
MetaEditorでのコンパイル・実行手順
- MetaEditorで新規カスタムインジケーター「MouseButtonPanel」を作成し、上のコードを貼り付けてコンパイルする
- MT5のナビゲーターからチャートへドラッグ&ドロップして適用する
- チャート左上に「ライン ON/OFF」ボタンと価格ラベルが表示されることを確認する
- チャート上でマウスを動かし、価格ラベルの数値が変化することを確認する
- ボタンをクリックし、オレンジの点線(カーソル追従ライン)が消えたり表示されたりすることを確認する
よくあるエラーと注意点
ここまでのコードを自分で改造するときに、つまずきやすいポイントをまとめます。
ボタンを押してもイベントが反応しない
sparamの文字列比較は完全一致です。
ボタン名を後から変更したのに、
OnChartEvent()側の比較文字列を直していないと、一生反応しません。
オブジェクト名は定数化して1箇所にまとめておくと事故を防げます。
CHARTEVENT_MOUSE_MOVEを有効にすると重くなる
注意
・マウス移動イベントはカーソルを動かすたびに大量に発火します
・その中でヒストリカルデータの再取得や重い計算をすると動作が重くなります
・座標変換とラベル更新程度の軽い処理に留めてください
MQL4との違い
OnChartEvent()自体はMQL4にも存在し、書き方もほぼ同じです。
ただしChartXYToTimePriceはMQL5の関数であり、
MQL4には存在しません。
MQL4で同等の処理をする場合は、WindowPriceOnDropped()等、
別の一連の関数を組み合わせる必要があります。
よくある質問
OnChartEventとマウスイベントについて、よく検索される疑問に回答します。
ボタンは何個でも置けますか?
はい、オブジェクト名を変えれば何個でも配置できます。
OnChartEvent()内のsparamの比較を、
ボタンの数だけif文やswitch文で分岐させれば、それぞれ別の処理を割り当てられます。
EA(エキスパートアドバイザー)でも同じコードは使えますか?
使えます。
OnChartEvent()はインジケーター・EAどちらでも、実装できるイベント関数です。
EAにボタンを付ければ、「手動決済ボタン」のような操作パネルも同じ仕組みで作れます。
ボタンの見た目(色・サイズ)は自由に変えられますか?
OBJPROP_BGCOLOR・OBJPROP_COLOR・OBJPROP_XSIZE・OBJPROP_YSIZEなど、
この記事で使ったプロパティの値を変えるだけで、色やサイズは自由に調整できます。
【MQL5】OnChartEventとマウスイベントでクリックできるボタンパネルを作る方法:まとめ
OnChartEvent()とマウスイベントを組み合わせれば、
チャート上にクリックできるボタンや、カーソルに追従する価格表示を自分で作れます。
ポイントは、CHART_EVENT_MOUSE_MOVEの事前有効化、
CHARTEVENT_OBJECT_CLICKでのボタン判定とステート解除、
ChartXYToTimePriceでの座標→価格変換の3点です。
コメント