MQL4のイベントハンドラを体系的にまとめました。旧形式のinit()/deinit()/start()とビルド600+で導入されたOnInit()/OnDeinit()/OnTick()の違い、OnTimer()による定期処理、OnChartEvent()によるチャートイベント処理、EA・インジケーター・スクリプトの実行モデルの違い、新バー検出パターン、UninitializeReasonまで、MT4プログラム開発に必要なイベント処理のすべてをコード付きで解説します。
MQL4とMQL5のイベントハンドラの違い
| 機能 | MQL4(ビルド600+) | MQL5 |
|---|---|---|
| 初期化 | OnInit() / init() | OnInit() |
| 終了処理 | OnDeinit() / deinit() | OnDeinit() |
| ティック処理 | OnTick() / start() | OnTick() |
| タイマー | OnTimer() | OnTimer() |
| チャートイベント | OnChartEvent() | OnChartEvent() |
| 計算イベント | OnCalculate()(インジケーター) | OnCalculate() |
| 取引イベント | なし | OnTrade() / OnTradeTransaction() |
| テスターイベント | OnTester() | OnTester() / OnTesterInit() / OnTesterDeinit() |
MQL4ビルド600以降は、MQL5と同じイベントハンドラ名(OnInit/OnDeinit/OnTick)が使えます。旧形式のinit/deinit/startも互換性のために動作しますが、新規開発ではOnXxx形式を推奨します。
旧形式 vs 新形式
ビルド600(2014年)以降のMT4では、MQL5互換のOnInit() / OnDeinit() / OnTick()形式が使えます。旧形式のinit/deinit/startも動作しますが、新形式ではOnDeinitのreason引数やOnInitの定数戻り値など、より細かい制御が可能です。新規開発では必ず新形式を使いましょう。
ビルド600(2014年)以降のMT4では、MQL5互換のOnInit() / OnDeinit() / OnTick()形式が使えます。旧形式のinit/deinit/startも動作しますが、新形式ではOnDeinitのreason引数やOnInitの定数戻り値など、より細かい制御が可能です。新規開発では必ず新形式を使いましょう。
// 旧形式 — 戻り値の型が異なる
int init()
{
// 初期化処理
Print("EA初期化(旧形式)");
return(0); // 0=成功
}
int deinit()
{
// 終了処理
Print("EA終了(旧形式)");
return(0);
}
int start()
{
// ティックごとに実行
Print("ティック受信(旧形式)");
return(0);
}// 新形式 — MQL5と同じ書式
int OnInit()
{
// 初期化処理
Print("EA初期化");
return(INIT_SUCCEEDED); // 定数を使用
}
void OnDeinit(const int reason)
{
// 終了処理(理由コードを受け取れる)
Print("EA終了: reason=", reason);
}
void OnTick()
{
// ティックごとに実行
// 戻り値なし(void)
}OnInit — 初期化
EAがチャートにアタッチされた時、時間軸の変更時、再コンパイル時に1回だけ実行されます。
// グローバル変数
int PipMultiplier;
double PipSize;
string EAName = "MyEA v1.0";
int OnInit()
{
// 口座チェック
if(!IsTradeAllowed())
{
Alert(EAName, ": 自動売買が許可されていません");
return(INIT_FAILED);
}
// 通貨ペアチェック
if(Symbol() != "USDJPY" && Symbol() != "GBPJPY")
{
Alert(EAName, ": 対応通貨ペアではありません");
return(INIT_PARAMETERS_INCORRECT);
}
// 5桁ブローカー判定
if(Digits == 3 || Digits == 5)
{
PipMultiplier = 10;
PipSize = Point * 10;
}
else
{
PipMultiplier = 1;
PipSize = Point;
}
// タイマー開始(1秒間隔)
EventSetTimer(1);
// チャートコメント
Comment(EAName, " - 稼働中");
Print(EAName, " 初期化完了");
return(INIT_SUCCEEDED);
}OnInitの戻り値
| 定数 | 値 | 説明 |
|---|---|---|
| INIT_SUCCEEDED | 0 | 初期化成功 |
| INIT_FAILED | 1 | 初期化失敗(EA停止) |
| INIT_PARAMETERS_INCORRECT | — | パラメータエラー |
| INIT_AGENT_NOT_SUITABLE | — | テスターエージェント不適合 |
OnDeinit — 終了処理
void OnDeinit(const int reason)
{
// タイマー停止
EventKillTimer();
// チャート上のオブジェクトを削除
ObjectsDeleteAll(0, "MyEA_");
// チャートコメントをクリア
Comment("");
// 終了理由をログ出力
string reasonText;
switch(reason)
{
case REASON_PROGRAM: reasonText = "プログラムから終了"; break;
case REASON_REMOVE: reasonText = "チャートから削除"; break;
case REASON_RECOMPILE: reasonText = "再コンパイル"; break;
case REASON_CHARTCHANGE: reasonText = "通貨ペアまたは時間軸変更"; break;
case REASON_CHARTCLOSE: reasonText = "チャートを閉じた"; break;
case REASON_PARAMETERS: reasonText = "パラメータ変更"; break;
case REASON_ACCOUNT: reasonText = "口座変更"; break;
case REASON_TEMPLATE: reasonText = "テンプレート適用"; break;
case REASON_INITFAILED: reasonText = "OnInit失敗"; break;
case REASON_CLOSE: reasonText = "ターミナル終了"; break;
default: reasonText = "不明(" + IntegerToString(reason) + ")";
}
Print(EAName, " 終了: ", reasonText);
}UninitializeReason定数
| 定数 | 値 | 説明 |
|---|---|---|
| REASON_PROGRAM | 0 | ExpertRemove()で終了 |
| REASON_REMOVE | 1 | チャートから手動削除 |
| REASON_RECOMPILE | 2 | プログラムを再コンパイル |
| REASON_CHARTCHANGE | 3 | 通貨ペアまたは時間軸を変更 |
| REASON_CHARTCLOSE | 4 | チャートを閉じた |
| REASON_PARAMETERS | 5 | 入力パラメータを変更 |
| REASON_ACCOUNT | 6 | 別の口座に切り替え |
| REASON_TEMPLATE | 7 | テンプレートを適用 |
| REASON_INITFAILED | 8 | OnInit()が失敗 |
| REASON_CLOSE | 9 | ターミナルを閉じた |
OnTick — ティック処理
void OnTick()
{
// 新バー検出(ティックごとの重複処理を防ぐ)
static datetime lastBar = 0;
if(Time[0] == lastBar)
return; // 同じバー内では何もしない
lastBar = Time[0];
// ここから新バーの処理
// 1. ポジションチェック
int posCount = CountMyPositions();
// 2. 決済判定
CheckExitConditions();
// 3. エントリー判定
if(posCount < MaxPositions)
CheckEntrySignal();
// 4. トレーリングストップ
UpdateTrailingStop();
}- Sleep()の使用: ティック処理がブロックされ、後続のティックが失われる
- 長時間のループ処理: 他のEAのティック処理にも影響する(MT4はシングルスレッド)
- 毎ティック同じ計算を繰り返す: 新バー検出で制御し、不要な計算を省く
- Alert()の多用: ダイアログが表示されるたびに処理が停止する
- Sleep()の使用: ティック処理がブロックされ、後続のティックが失われる
- 長時間のループ処理: 他のEAのティック処理にも影響する(MT4はシングルスレッド)
- 毎ティック同じ計算を繰り返す: 新バー検出で制御し、不要な計算を省く
- Alert()の多用: ダイアログが表示されるたびに処理が停止する
OnTimer — タイマーイベント
int OnInit()
{
// 60秒間隔でタイマーイベントを発生
EventSetTimer(60);
return(INIT_SUCCEEDED);
}
void OnTimer()
{
// 定期的な処理
// - チャートコメントの更新
// - 損益の定期集計
// - 外部データの取得
double equity = AccountEquity();
double balance = AccountBalance();
double drawdown = (balance - equity) / balance * 100;
string info = "";
info += "残高: " + DoubleToStr(balance, 0) + "\n";
info += "有効証拠金: " + DoubleToStr(equity, 0) + "\n";
info += "DD: " + DoubleToStr(drawdown, 1) + "%\n";
info += "更新: " + TimeToStr(TimeCurrent(), TIME_MINUTES);
Comment(info);
}
void OnDeinit(const int reason)
{
EventKillTimer(); // タイマー停止を忘れずに
Comment("");
}OnChartEvent — チャートイベント
void OnChartEvent(
const int id, // イベントID
const long &lparam, // long型パラメータ
const double &dparam, // double型パラメータ
const string &sparam // string型パラメータ
);主要なイベントID
| 定数 | 説明 | lparam | dparam | sparam |
|---|---|---|---|---|
| CHARTEVENT_KEYDOWN | キー押下 | キーコード | — | — |
| CHARTEVENT_MOUSE_MOVE | マウス移動 | X座標 | Y座標 | マウスフラグ |
| CHARTEVENT_OBJECT_CLICK | オブジェクトクリック | X座標 | Y座標 | オブジェクト名 |
| CHARTEVENT_OBJECT_CREATE | オブジェクト作成 | — | — | オブジェクト名 |
| CHARTEVENT_OBJECT_DELETE | オブジェクト削除 | — | — | オブジェクト名 |
| CHARTEVENT_CHART_CHANGE | チャートサイズ変更 | — | — | — |
| CHARTEVENT_CUSTOM | カスタムイベント | 任意 | 任意 | 任意 |
int OnInit()
{
// ボタンオブジェクトを作成
ObjectCreate(0, "BuyButton", OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, "BuyButton", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "BuyButton", OBJPROP_YDISTANCE, 30);
ObjectSetInteger(0, "BuyButton", OBJPROP_XSIZE, 100);
ObjectSetInteger(0, "BuyButton", OBJPROP_YSIZE, 30);
ObjectSetString(0, "BuyButton", OBJPROP_TEXT, "BUY");
ObjectSetInteger(0, "BuyButton", OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, "BuyButton", OBJPROP_BGCOLOR, clrBlue);
ObjectCreate(0, "SellButton", OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, "SellButton", OBJPROP_XDISTANCE, 120);
ObjectSetInteger(0, "SellButton", OBJPROP_YDISTANCE, 30);
ObjectSetInteger(0, "SellButton", OBJPROP_XSIZE, 100);
ObjectSetInteger(0, "SellButton", OBJPROP_YSIZE, 30);
ObjectSetString(0, "SellButton", OBJPROP_TEXT, "SELL");
ObjectSetInteger(0, "SellButton", OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, "SellButton", OBJPROP_BGCOLOR, clrRed);
return(INIT_SUCCEEDED);
}
void OnChartEvent(const int id, const long &lparam,
const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK)
{
if(sparam == "BuyButton")
{
Print("BUYボタンがクリックされました");
OrderSend(Symbol(), OP_BUY, 0.01, Ask, 3, 0, 0, "Manual Buy", 0, 0, clrBlue);
ObjectSetInteger(0, "BuyButton", OBJPROP_STATE, false); // ボタンをリセット
}
else if(sparam == "SellButton")
{
Print("SELLボタンがクリックされました");
OrderSend(Symbol(), OP_SELL, 0.01, Bid, 3, 0, 0, "Manual Sell", 0, 0, clrRed);
ObjectSetInteger(0, "SellButton", OBJPROP_STATE, false);
}
}
}EA・インジケーター・スクリプトの違い
| 項目 | EA(Expert Advisor) | インジケーター | スクリプト |
|---|---|---|---|
| メインイベント | OnTick() | OnCalculate() | OnStart() |
| 実行タイミング | ティックごと | ティックごと | 1回だけ |
| 取引可能 | はい | いいえ | はい |
| チャートへの描画 | オブジェクトのみ | バッファ+オブジェクト | オブジェクトのみ |
| 同時実行 | チャートに1つ | 複数可 | チャートに1つ |
| OnTimer | 使用可 | 使用可 | 不可 |
| OnChartEvent | 使用可 | 使用可 | 不可 |
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 clrRed
double Buffer[];
int OnInit()
{
SetIndexBuffer(0, Buffer);
SetIndexStyle(0, DRAW_LINE);
SetIndexLabel(0, "MyIndicator");
return(INIT_SUCCEEDED);
}
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[])
{
int limit = rates_total - prev_calculated;
if(prev_calculated == 0)
limit = rates_total - 1;
for(int i = limit; i >= 0; i--)
{
Buffer[i] = (high[i] + low[i] + close[i]) / 3.0;
}
return(rates_total);
}// スクリプト — 1回だけ実行される
void OnStart()
{
// 全ポジションの情報を表示
Print("=== 保有ポジション一覧 ===");
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
Print("Ticket=", OrderTicket(),
" Type=", (OrderType() == OP_BUY ? "BUY" : "SELL"),
" Lots=", OrderLots(),
" Profit=", OrderProfit());
}
Print("=== 完了 ===");
}- prev_calculated引数を活用して、既に計算済みのバーを再計算しない
- 初回呼び出し時(prev_calculated==0)は全バーを計算、2回目以降は新しいバーだけ計算
- 戻り値にrates_totalを返すことで、次回呼び出し時のprev_calculatedに反映される
- インジケーターでは取引関数(OrderSend等)は使用できない
- prev_calculated引数を活用して、既に計算済みのバーを再計算しない
- 初回呼び出し時(prev_calculated==0)は全バーを計算、2回目以降は新しいバーだけ計算
- 戻り値にrates_totalを返すことで、次回呼び出し時のprev_calculatedに反映される
- インジケーターでは取引関数(OrderSend等)は使用できない
イベントハンドラ一覧(MQL5対応表)
| MQL4イベントハンドラ | MQL5対応 | 説明 |
|---|---|---|
| OnInit() / init() | OnInit() | 初期化 |
| OnDeinit() / deinit() | OnDeinit() | 終了処理 |
| OnTick() / start() | OnTick() | ティック受信 |
| OnTimer() | OnTimer() | タイマーイベント |
| OnChartEvent() | OnChartEvent() | チャートイベント |
| OnCalculate() | OnCalculate() | インジケーター計算 |
| OnTester() | OnTester() | テスター終了 |
| なし | OnTrade() | 取引イベント |
| なし | OnTradeTransaction() | 取引トランザクション |
| なし | OnBookEvent() | 板情報変更 |
まとめ
MQL4のイベントハンドラはEA、インジケーター、スクリプトそれぞれの実行モデルに合わせて設計されています。重要なポイントは以下の通りです。
- 新形式(OnInit/OnDeinit/OnTick)を使う — ビルド600+では旧形式より新形式が推奨
- OnDeinitでは必ずリソースを解放する — タイマー停止、オブジェクト削除、コメントクリア
- OnTickでは新バー検出を入れる — ティックごとの重複処理を防ぎ、バックテスト精度を向上
- OnInitの戻り値でエラーを適切に通知する — INIT_FAILEDを返すとEAが自動停止する
次のステップとして、MQLリファレンス総合ガイドでチャート操作やエラーハンドリングと組み合わせたプロ品質のEA開発を学びましょう。





